代码审查与重构:代码规范、常见坏味道、重构手法

说实话,代码审查和重构这件事,很多工程师觉得是「锦上添花」。项目工期紧,能跑就行,谁还管代码好不好看?

但我做了十几年嵌入式开发,吃过太多亏了。有一次我接手一个老项目,里面有个函数足足800行,全局变量满天飞。我改一个bug,修好了这里,炸了那里。那段时间我做梦都在追变量。嗯,从那以后,我再也不敢轻视代码质量了。

今天我们就聊聊,怎么通过代码审查发现坏味道,再用重构手法把代码救回来。

一、代码规范:不是形式主义,是保命符

代码规范这东西,很多人觉得是「管得太宽」。但你想啊,一个团队五六个人,每个人写代码风格都不一样,那维护起来得多痛苦?

我个人习惯,规范分三层:

层级 内容 举例
命名规范 变量、函数、宏的命名规则 全局变量加 g_ 前缀,宏全大写
格式规范 缩进、括号、空行 函数之间空两行,缩进4空格
注释规范 什么该注释,什么不该注释 函数头说明功能、参数、返回值
我的小技巧: 别靠人肉检查。用工具!比如 clang-format 自动格式化,PC-Lint 做静态检查。人只负责逻辑,格式交给机器。

二、常见坏味道:一眼就能看出问题

坏味道这个词,我第一次听觉得挺玄乎。后来发现,其实就是「代码看起来不对劲」。我总结了嵌入式C语言里最常见的几种:

1. 函数太长

一个函数超过50行,基本就该拆了。超过100行,那绝对是灾难。我在项目中见过一个函数,里面既有I2C通信,又有数据处理,还有状态机切换。说白了,它什么都干,什么都干不好。

2. 全局变量泛滥

全局变量用起来方便,但维护起来要命。你想想,一个变量在10个文件里被修改,出bug了你都不知道是谁改的。

我曾经踩过的坑: 一个全局标志位,在中断里被置位,在主循环里被清零。结果因为时序问题,标志位被误清零,导致一个关键任务永远没执行。查了整整两天。

3. 重复代码

Ctrl+C、Ctrl+V 一时爽,维护火葬场。同样的逻辑出现在三四个地方,改一个漏一个,bug就这么来的。

4. 魔法数字

代码里直接写 0x55、100、2000,没人知道什么意思。我建议用宏定义或者枚举,给数字起个名字。

// 坏味道
if (status == 0x55) {
    delay_ms(100);
}

// 重构后
#define DEVICE_READY_STATUS  0x55
#define RETRY_DELAY_MS       100

if (status == DEVICE_READY_STATUS) {
    delay_ms(RETRY_DELAY_MS);
}

三、重构手法:小步快跑,别搞大跃进

重构最忌讳什么?一上来就大改。我见过有人重构一个模块,改了三天,结果编译都过不了。最后回退,白干。

我的原则是:每次只改一点点,改完就测试。具体手法有这些:

1. 提取函数(Extract Function)

把一段逻辑独立出来,变成一个新函数。好处是:代码变短了,逻辑更清晰,还能复用。

// 重构前
void process_data(void) {
    // 50行代码
    // ... 校验和计算
    uint8_t checksum = 0;
    for (int i = 0; i < len; i++) {
        checksum += buffer[i];
    }
    // ... 其他逻辑
}

// 重构后
static uint8_t calculate_checksum(uint8_t *buffer, int len) {
    uint8_t checksum = 0;
    for (int i = 0; i < len; i++) {
        checksum += buffer[i];
    }
    return checksum;
}

void process_data(void) {
    // 主逻辑变短了
    uint8_t checksum = calculate_checksum(buffer, len);
    // ... 其他逻辑
}

2. 引入变量(Introduce Variable)

复杂的表达式,拆成几个有意义的变量。可读性瞬间提升。

// 重构前
if ((status & 0x03) == 0x01 && timeout > 5000) {
    // ...
}

// 重构后
bool is_device_ready = ((status & 0x03) == 0x01);
bool is_timeout = (timeout > 5000);
if (is_device_ready && is_timeout) {
    // ...
}

3. 拆分条件(Decompose Conditional)

复杂的 if-else,拆成多个小函数。每个条件判断都一目了然。

// 重构前
if (temperature > 85 && humidity > 60 && fan_speed == 0) {
    // 过热保护
}

// 重构后
static bool is_overheat_condition(int temp, int hum, int fan) {
    return (temp > 85) && (hum > 60) && (fan == 0);
}

if (is_overheat_condition(temperature, humidity, fan_speed)) {
    // 过热保护
}

四、代码审查流程:别走过场

很多团队的代码审查就是走个形式。代码发出来,没人看,或者随便扫一眼就过了。这不行。

我建议的流程是这样的:

  1. 自审:提交前自己先看一遍。我经常发现,自己写的代码过两天再看,就能看出问题。
  2. 小范围审查:找1-2个同事,别找太多。人多了反而没人认真看。
  3. 关注逻辑,不纠结格式:格式问题交给工具。审查的重点是:逻辑对不对?有没有边界情况?有没有资源泄漏?
  4. 给出具体建议:别说「这段代码不好」,要说「这里建议用状态机代替 if-else,更清晰」。
核心思想: 审查不是为了挑刺,是为了让代码更好。大家都是队友,不是敌人。

五、知识体系总览

下面这张图,是我自己总结的代码审查与重构的知识结构。你可以把它当成一个检查清单:

代码审查与重构 代码规范 • 命名规范 • 格式规范 • 注释规范 • 工具自动化 常见坏味道 • 函数过长 • 全局变量泛滥 • 重复代码 • 魔法数字 重构手法 • 提取函数 • 引入变量 • 拆分条件 • 小步快跑 审查流程 1. 自审 2. 小范围审查 3. 关注逻辑 4. 给出具体建议 常用工具 • clang-format • PC-Lint • Cppcheck • Git diff 审查

你看,整个知识体系其实不复杂。核心就是:规范打底,坏味道做预警,重构手法当工具,审查流程做保障

最后说一句: 重构不是一次性的。每次改代码的时候,顺手做一点小重构。积少成多,代码质量自然就上去了。别想着一次搞定,那不现实。

公众号:蓝海资料掘金营,微信deep3321