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 端开发的时候,代码也没这么乱啊。为什么嵌入式特别容易烂?”

原因有三点,我一个个说:

  1. 资源受限,逼你写“面条代码”
    早期 MCU 只有几 KB RAM,你不敢用函数指针、不敢分层、不敢用动态分配。所有代码都往一个文件里塞,因为“省空间”。
    我见过一个老工程师,为了省 100 字节,把 3 个功能模块写在一个 switch-case 里。结果后来需求一变,那个 switch-case 膨胀到 800 行。
  2. 硬件依赖太强,模块天然耦合
    嵌入式代码天生就和硬件绑在一起。你的温度传感器驱动里,可能直接调用了 I2C 总线操作、GPIO 拉高拉低、甚至延时函数。这些硬件操作一旦和业务逻辑混在一起,解耦就难了。
  3. 项目周期紧,没人愿意重构
    “先跑起来再说”——这句话我听了不下 50 次。老板催着出货,你只能打补丁。补丁摞补丁,三年下来,代码就成了一座山。

我的建议:别等到“屎山”成型了再想解耦。从项目第一天起,就做好模块划分。哪怕多花两天时间设计接口,也比后期重构省 10 倍时间。

1.4 解耦到底在解什么?——一张图看懂核心逻辑

说了这么多痛点,咱们得聊聊解耦的本质。说白了,解耦就是把“你中有我、我中有你”变成“你管你的、我管我的”。

下面这张图,是我在培训团队时经常画的。它展示了紧耦合代码和松耦合代码的核心区别:

紧耦合 vs 松耦合:代码依赖关系对比 紧耦合(屎山) 温度采集 LCD 显示 电机控制 串口通信 全局变量池 松耦合(解耦后) 温度模块 显示模块 电机模块 通信模块 标准化接口层 所有模块直接读写全局变量 改一个模块,其他全受影响 模块通过接口层通信 改一个模块,不影响其他模块

你看明白了吗?左边是“一团乱麻”,右边是“各司其职”。解耦的核心就是在模块之间加一层清晰的接口,让每个模块只关心自己的事。

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 本章小结——解耦的“道”与“术”

这一章咱们聊了“为什么”。说白了,紧耦合代码就是一颗定时炸弹。你不拆它,它迟早炸你。

记住三个核心认知:

  1. 紧耦合的代价:改一个 bug 出三个 bug,新人不敢碰,测试没法做,换硬件等于重写
  2. 解耦的本质:把“全局变量满天飞”变成“模块接口清晰”,让每个模块只做一件事
  3. 解耦的收益:代码可维护、可测试、可移植,你才能从“救火队员”变成“架构师”