1. 课程导入
说实话,我见过太多嵌入式项目从「能跑就行」变成「谁都不敢动」。
刚入行那几年,我接手过一个智能家居网关项目。代码跑了三年,功能倒是全的,但每次加一个新传感器驱动,都要花两周去梳理中断、定时器和状态机之间的纠缠关系。项目经理催得紧,我只能硬着头皮往上堆代码。结果呢?修一个 bug 引出三个新 bug。
这就是典型的「技术债」——你欠下的每一行烂代码,最后都要连本带利还回去。
1.1 嵌入式系统的「三座大山」
我总结了一下,嵌入式系统走到需要重构的地步,通常逃不过下面三个问题。
| 问题类型 | 典型表现 | 后果 |
|---|---|---|
| 意大利面条式代码 | 全局变量满天飞,函数动辄上千行,goto 语句到处跳 | 改一处崩一片,新人根本不敢接手 |
| 接口耦合 | 驱动层直接操作业务逻辑,硬件变更导致上层代码全改 | 换一个传感器型号,要改五个模块 |
| 维护噩梦 | 没有单元测试,文档和代码对不上,编译警告当没看见 | 版本迭代基本靠猜,线上问题定位以天为单位 |
1.2 意大利面条式代码:从「能跑」到「不敢跑」
什么叫意大利面条式代码?我举个例子你就明白了。
// 典型的嵌入式「面条」代码
void main_task(void *param) {
uint32_t flags = 0;
int sensor_value = 0;
while(1) {
if(HAL_GPIO_ReadPin(BTN_GPIO, BTN_PIN) == GPIO_PIN_SET) {
flags |= 0x01;
if(flags & 0x02) {
sensor_value = read_adc();
if(sensor_value > 1000) {
// 这里居然直接操作了显示和通信
lcd_show_number(sensor_value);
uart_send_byte(sensor_value & 0xFF);
// 还顺带改了一个全局变量
g_system_state = STATE_ALERT;
}
}
}
// 500 行后还有类似的逻辑
// 而且全局变量 g_system_state 在 8 个地方被修改
}
}
你看,一个函数里塞了按键检测、ADC 读取、LCD 显示、UART 通信、状态管理。这还算好的——我见过更夸张的,一个中断服务程序里做了 300 行的业务逻辑处理。
为什么会这样?
说白了,就是「赶工期」和「缺设计」的产物。项目经理说「这周必须出功能」,你想着「先跑起来再说」。结果这一跑,就是三年。
1.3 接口耦合:牵一发而动全身
接口耦合是嵌入式系统最隐蔽的杀手。它不像面条代码那么明显,但破坏力更大。
我参与过一个车载 OBD 诊断仪的项目。硬件团队说「先按这个传感器型号做,后面可能会换」。我当时图省事,直接在业务代码里调用了传感器的 I2C 读写函数。
// 耦合版本:业务逻辑直接依赖硬件
void read_engine_temp(void) {
uint8_t buf[2];
// 直接调用 I2C 驱动
i2c_master_read(I2C1, 0x76, buf, 2);
int temp = (buf[0] << 8) | buf[1];
if(temp > 120) {
// 业务逻辑
set_alarm(ALARM_OVERHEAT);
}
}
三个月后,传感器换成了 SPI 接口的。我傻眼了——所有调用 i2c_master_read 的地方都要改。全局搜索下来,23 个文件、47 处调用。改了两天,还改出了三个 bug。
这就是接口耦合的代价。硬件层、驱动层、业务层揉在一起,任何一层的变更都会引发连锁反应。
1.4 维护噩梦:当代码变成「黑盒」
维护噩梦是什么感觉?
我记得有一次,凌晨两点被电话叫醒——产线上的设备批量死机。我远程连上去一看,日志里只有一行「Error: 0x03」。没有堆栈信息,没有上下文,连哪个模块报的错都不知道。
这就是典型的「维护噩梦」症状:
- 没有单元测试——改完代码只能靠人工点按,测不测得到全看运气
- 文档和代码脱节——注释写的是「v1.0 版本逻辑」,实际代码已经迭代了五个版本
- 编译警告成堆——我见过一个项目编译时有 200 多个 warning,团队已经「习惯」了
- 全局变量滥用——一个全局变量在 10 个以上地方被读写,根本没法追踪
你想想看,这样的项目,新人接手要多久才能上手?我见过最夸张的,一个工作了半年的工程师,还在问「这个全局变量到底是谁改的」。
1.5 重构的价值:不只是「改代码」
说了这么多问题,那重构到底能带来什么?
我用一张图来总结一下重构的核心价值:
重构不是重写。这是很多团队容易搞混的地方。
重写是「推倒重来」,风险极高——业务逻辑可能丢失,新 bug 层出不穷,项目周期不可控。重构是「逐步改善」,每次只改一小块,改完就编译、测试、验证,确保系统始终处于可用状态。
1.6 什么时候该重构?
不是所有项目都需要重构。我总结了三个「重构信号」:
- 修改成本 > 重写成本——加一个小功能要改五个模块,修一个 bug 要花三天定位
- 新人上手周期 > 3 个月——新来的工程师三个月了还不敢独立改代码
- 线上问题定位时间 > 1 天——出问题了要翻代码、加日志、复现,一天过去了还没找到根因
如果你现在的项目符合上面任意一条,那就要认真考虑重构了。