25、代码审查:审查清单设计、常见缺陷模式、审查流程、自动化审查工具
代码审查这事儿,我做了快二十年了。说实话,刚入行那会儿我觉得审查就是走个过场——代码能跑就行,看那么仔细干嘛?直到有一次,我漏掉了一个空指针检查,线上直接挂了。嗯,从那以后,我再也不敢小看代码审查了。
今天咱们聊聊代码审查的四个核心话题:审查清单怎么设计、常见缺陷有哪些、审查流程怎么走、以及自动化工具怎么用。说白了,就是帮你建立一套靠谱的审查体系。
一、审查清单设计:别靠脑子记,靠清单查
我个人习惯,审查前先列个清单。为什么?因为人脑会漏。你想想看,一个函数几百行,你盯着屏幕看十分钟,眼睛就花了。这时候有个清单,逐项检查,效率高得多。
我设计的审查清单,一般分这么几类:
| 类别 | 检查项 | 说明 |
|---|---|---|
| 内存安全 | 指针是否判空?malloc后是否检查返回值?free后是否置NULL? | 这是C语言的头号杀手 |
| 边界检查 | 数组下标是否越界?循环终止条件是否正确? | 缓冲区溢出大多源于此 |
| 资源管理 | 文件句柄是否关闭?锁是否释放?中断是否恢复? | 资源泄漏是慢性病 |
| 错误处理 | 返回值是否检查?错误码是否覆盖所有分支? | 别让错误静默传播 |
| 并发安全 | 共享变量是否加锁?临界区是否最小化? | 多线程的坑,踩过才知道 |
二、常见缺陷模式:这些坑,我几乎都踩过
代码审查多了,你会发现缺陷是有规律的。我总结了几种最常见的模式,你审查时重点盯着看:
1. 空指针解引用
这个太常见了。我曾经在一个项目中,有个函数返回了NULL,调用方没检查,直接用了。结果呢?段错误。修复就加了一行 if (ptr == NULL) return -1;,但排查花了半天。
// 错误写法
void process_data(Data *d) {
d->value = 10; // 如果d是NULL,直接崩
}
// 正确写法
int process_data(Data *d) {
if (d == NULL) {
return -1; // 防御式检查
}
d->value = 10;
return 0;
}
2. 缓冲区溢出
C语言没有自动边界检查,所以全靠程序员自觉。我见过最离谱的,是一个 char buf[10],然后 sprintf(buf, "%s", user_input)。用户输入稍微长一点,栈就被破坏了。
sprintf、strcpy、gets 这类不检查边界的函数。用 snprintf、strncpy 替代。
3. 资源泄漏
打开文件忘了关,申请内存忘了释放。这种问题在嵌入式系统里尤其致命——内存就那么点,泄漏几次就耗光了。我习惯在审查时,每个 malloc 都要找到对应的 free,每个 fopen 都要找到对应的 fclose。
4. 错误码被忽略
很多函数返回错误码,但调用方直接无视了。比如:
// 错误写法
write(fd, buf, len); // 返回值呢?写失败了你不知道
// 正确写法
ssize_t ret = write(fd, buf, len);
if (ret != len) {
// 处理错误
}
三、审查流程:别走形式,走心
我参与过很多团队的审查流程。有的团队走形式,代码发出来没人看,直接点通过。有的团队又太死板,每行代码都要争论半天。我觉得好的流程应该是这样的:
- 提交前自审:开发者自己先过一遍,把明显的问题修掉。别把垃圾丢给别人看。
- 指定审查者:不要随机分配。找熟悉这块代码的人,或者找有经验的人。
- 逐行审查:审查者按清单逐项检查。发现问题就记录,不要当场改——改代码是开发者的事。
- 反馈与修改:审查者把问题列出来,开发者逐一修复。修复后最好再确认一遍。
- 合并:所有问题都解决了,才能合并到主分支。
四、自动化审查工具:让机器干机器的活
人眼会累,机器不会。所以我把重复性的检查交给工具,人只做需要判断力的审查。
常用的自动化工具:
| 工具 | 用途 | 我的评价 |
|---|---|---|
| cppcheck | 静态分析,检查内存泄漏、空指针等 | 轻量级,适合嵌入式项目 |
| Clang Static Analyzer | 深度分析,能发现复杂路径上的问题 | 准确率高,但慢一点 |
| Coverity | 商业工具,功能全面 | 贵,但值得 |
| SonarQube | 代码质量平台,持续集成 | 适合团队使用 |
我建议的流程是:提交代码前,先跑一遍静态分析工具。工具报的警告,能修的修掉,不能修的加注释说明。这样审查者看到的代码,已经是经过一轮过滤的了。
知识体系总览
下面这张图,我把代码审查的核心逻辑画出来了。你可以把它当作审查时的思维导图:
这张图把审查的四个维度串起来了。你审查时,可以对照着看:清单有没有覆盖?缺陷模式有没有遗漏?流程走对了没?工具用上了没?