1. 课程导入
各位做嵌入式的兄弟,咱们开门见山。
你接手过一个项目吗?打开代码仓库,发现一个 .c 文件 8000 行,全局变量满天飞,一个函数里嵌套了 6 层 if-else。你改一个定时器中断的优先级,结果 LCD 显示乱了,串口也不出数据了。
这就是我今天要聊的——紧耦合的“屎山”代码。
1.1 什么是“屎山”?——我亲眼见过的代码地狱
先别笑,这个词不是我发明的。圈子里管这种代码叫“屎山”,意思是它像一座山一样堆在那里,又臭又硬,谁都不敢动。
我 2016 年在一家做工业控制器的公司,接手过一个温控项目。前任工程师离职了,留给我一份“遗产”:
- 一个
main.c文件,4500 行 - 全局变量 200 多个,命名从
g_ucTempFlag1到g_ucTempFlag20 - 所有外设驱动、业务逻辑、UI 绘制全写在一个
while(1)循环里 - 没有注释,或者注释是“这里不要动,动了会死机”
我当时年轻,心想“不就是重构吗?干就完了”。结果改了三天,板子直接变砖。为什么?因为一个全局变量被 7 个函数同时读写,我根本不知道谁依赖谁。
核心痛点:紧耦合代码的本质是“牵一发而动全身”。你改 A,B 崩了;你修 B,C 又挂了。最后你只能小心翼翼地打补丁,代码越来越臃肿,直到没人敢碰。
1.2 紧耦合的 5 大“维护噩梦”
咱们不扯虚的,直接列干货。我在多个项目里总结过,紧耦合代码会带来以下 5 个典型问题:
| 噩梦编号 | 现象 | 真实案例(我踩过的坑) |
|---|---|---|
| 噩梦 1 | 改一个 bug,引出三个新 bug | 修改 ADC 采样滤波系数,结果电机 PWM 波形乱了——因为滤波函数和电机控制函数共用了同一个全局数组 |
| 噩梦 2 | 新人上手周期极长 | 团队来了个 3 年经验的工程师,看了一个月代码,还是不敢改。他说“我看不懂这个函数到底影响哪些模块” |
| 噩梦 3 | 单元测试几乎不可能 | 你想单独测试一个温度采集模块?不行,它直接调用了串口打印、LCD 刷新、甚至 EEPROM 写入。你测不了 |
| 噩梦 4 | 硬件更换成本极高 | 客户要求换一颗 MCU,结果发现驱动层和应用层完全耦合在一起。换芯片等于重写整个项目 |
| 噩梦 5 | 代码审查变成“吵架会” | 每次 Code Review,大家都在争论“你这个全局变量到底该不该改”,而不是讨论业务逻辑本身 |
注意:如果你现在的项目已经出现了以上 3 个或更多症状,说明你的代码架构已经亮红灯了。别等到产品量产了再重构,那时候成本会翻 10 倍。
1.3 为什么嵌入式代码特别容易变成“屎山”?
你可能会问:“我做 PC 端开发的时候,代码也没这么乱啊。为什么嵌入式特别容易烂?”
原因有三点,我一个个说:
- 资源受限,逼你写“面条代码”
早期 MCU 只有几 KB RAM,你不敢用函数指针、不敢分层、不敢用动态分配。所有代码都往一个文件里塞,因为“省空间”。
我见过一个老工程师,为了省 100 字节,把 3 个功能模块写在一个 switch-case 里。结果后来需求一变,那个 switch-case 膨胀到 800 行。 - 硬件依赖太强,模块天然耦合
嵌入式代码天生就和硬件绑在一起。你的温度传感器驱动里,可能直接调用了 I2C 总线操作、GPIO 拉高拉低、甚至延时函数。这些硬件操作一旦和业务逻辑混在一起,解耦就难了。 - 项目周期紧,没人愿意重构
“先跑起来再说”——这句话我听了不下 50 次。老板催着出货,你只能打补丁。补丁摞补丁,三年下来,代码就成了一座山。
我的建议:别等到“屎山”成型了再想解耦。从项目第一天起,就做好模块划分。哪怕多花两天时间设计接口,也比后期重构省 10 倍时间。
1.4 解耦到底在解什么?——一张图看懂核心逻辑
说了这么多痛点,咱们得聊聊解耦的本质。说白了,解耦就是把“你中有我、我中有你”变成“你管你的、我管我的”。
下面这张图,是我在培训团队时经常画的。它展示了紧耦合代码和松耦合代码的核心区别:
你看明白了吗?左边是“一团乱麻”,右边是“各司其职”。解耦的核心就是在模块之间加一层清晰的接口,让每个模块只关心自己的事。
1.5 一个真实的解耦案例——从 4500 行到 800 行
回到我前面说的那个温控项目。后来我花了 3 个月,硬是把那个 4500 行的 main.c 拆成了 6 个模块:
- drv_temp.c:温度传感器驱动(硬件相关)
- drv_lcd.c:LCD 显示驱动(硬件相关)
- drv_motor.c:电机驱动(硬件相关)
- hal_temp.c:温度采集硬件抽象层(屏蔽硬件差异)
- app_control.c:温控业务逻辑(纯算法,不碰硬件)
- app_ui.c:用户界面逻辑(纯业务,不碰硬件)
每个模块对外只暴露 3-5 个函数接口,内部变量全部 static。结果呢?
- 代码从 4500 行降到 800 行(核心逻辑)
- 新人上手时间从 1 个月缩短到 1 周
- 后来客户要求换 STM32 为 GD32,我只改了驱动层,业务层一行没动
关键结论:解耦不是“炫技”,它是为了让你睡得着觉。代码解耦了,你才敢改、才敢测、才敢交付。
1.6 本章小结——解耦的“道”与“术”
这一章咱们聊了“为什么”。说白了,紧耦合代码就是一颗定时炸弹。你不拆它,它迟早炸你。
记住三个核心认知:
- 紧耦合的代价:改一个 bug 出三个 bug,新人不敢碰,测试没法做,换硬件等于重写
- 解耦的本质:把“全局变量满天飞”变成“模块接口清晰”,让每个模块只做一件事
- 解耦的收益:代码可维护、可测试、可移植,你才能从“救火队员”变成“架构师”