2. 现状分析与评估:如何对遗留代码进行“体检”?代码坏味道识别与量化指标
接手一套遗留系统,第一件事不是急着改,而是先做“体检”。
我见过不少团队,上来就重构,结果改到一半发现依赖关系乱成一锅粥,最后不得不回退。说白了,没有诊断就开刀,风险极高。
这一章,我们就聊聊怎么给遗留代码做一次全面的健康检查。我会从两个维度展开:定性识别(代码坏味道)和定量评估(量化指标)。
2.1 代码坏味道:一眼就能看出的“病相”
坏味道不是bug,但它预示着未来会出问题。我个人习惯把坏味道分成三类:结构型、命名型、依赖型。
2.1.1 结构型坏味道
| 坏味道 | 典型表现 | 我见过的真实案例 |
|---|---|---|
| 过长函数 | 一个函数超过200行,甚至上千行 | 某通信协议栈的解析函数,单函数1200行,包含状态机、日志、异常处理 |
| 过大类 | 一个类承担了太多职责,成员变量超过20个 | 一个“万能驱动类”,既管GPIO又管I2C还管定时器 |
| 重复代码 | 相同或相似的代码片段出现在多个地方 | 三个不同的模块各自实现了一遍CRC校验 |
| 过长的参数列表 | 函数参数超过5个,且难以理解 | 一个初始化函数有12个参数,调用时全靠猜 |
我的经验:遇到过长函数,先别急着拆。先画出它的控制流图,看看哪些逻辑是独立的。我曾经把一个800行的函数拆成12个小函数,测试覆盖率反而从40%升到了85%。
2.1.2 命名型坏味道
命名是代码的“脸面”。糟糕的命名会让阅读者抓狂。
- 魔法数字:代码里直接出现
0x1F、1000这样的数字,没有宏定义或枚举。 - 含义模糊的变量名:
tmp、data、flag、val这类名字,几乎等于没起名。 - 拼写错误:
recieve、adress、paramter,这些低级错误会降低代码的可信度。
避坑指南:我曾经在一个项目中看到变量名叫
a1、a2、a3……一直排到 a27。后来发现这些变量对应的是27个不同的传感器通道。改命名花了整整两天,但之后调试效率提升了至少30%。
2.1.3 依赖型坏味道
这类坏味道最隐蔽,也最致命。
- 循环依赖:模块A调用模块B,模块B又调用模块A。编译能过,但维护是噩梦。
- 全局变量滥用:多个模块通过全局变量通信,数据流完全不可控。
- 过度耦合:一个模块的修改会引发连锁反应,牵一发而动全身。
2.2 量化指标:用数据说话
光靠“感觉”不够,我们需要可量化的指标。我常用的指标有以下几个:
2.2.1 圈复杂度(Cyclomatic Complexity)
圈复杂度衡量的是代码中独立路径的数量。值越高,代码越难测试和维护。
| 圈复杂度范围 | 风险等级 | 建议 |
|---|---|---|
| 1 - 10 | 低风险 | 代码结构清晰,易于维护 |
| 11 - 20 | 中风险 | 建议考虑重构,拆分函数 |
| 21 - 50 | 高风险 | 必须重构,测试覆盖率极低 |
| 50+ | 极高风险 | 几乎不可测试,建议重写 |
计算方式:圈复杂度 = 判断节点数 + 1。判断节点包括 if、else if、while、for、case、&&、|| 等。大部分静态分析工具(如 PC-Lint、SonarQube)都能自动计算。
2.2.2 代码行数(Lines of Code, LOC)
这个指标简单直接,但容易被滥用。我关注的是函数级的代码行数。
- 单个函数超过 100 行:需要警惕。
- 单个函数超过 200 行:强烈建议重构。
- 单个函数超过 500 行:几乎可以断定它违反了单一职责原则。
2.2.3 耦合度与内聚度
这两个指标是衡量模块设计质量的核心。
- 耦合度:模块之间的依赖程度。低耦合是好设计的标志。
- 内聚度:模块内部元素之间的关联程度。高内聚是好设计的标志。
我常用一个简单的办法来评估:统计一个模块被多少个其他模块引用。如果超过 10 个模块依赖它,那这个模块很可能承担了过多的职责。
2.2.4 注释率与注释质量
注释不是越多越好。我见过注释率高达 40% 的代码,但全是废话。
| 注释类型 | 示例 | 评价 |
|---|---|---|
| 废话注释 | // 加1操作 写在 i++ 上面 |
毫无价值,应该删除 |
| 意图注释 | // 等待硬件FIFO非空,超时则返回错误 |
有价值,解释了“为什么” |
| 文档注释 | 函数头注释,说明参数、返回值、注意事项 | 必须保留,尤其是对外接口 |
2.3 体检流程:从扫描到报告
我建议按以下步骤进行代码体检:
- 静态扫描:使用工具(如 PC-Lint、Cppcheck、SonarQube)进行全量扫描,获取圈复杂度、代码行数、重复率等基础数据。
- 人工审查:针对扫描结果中的高风险模块,进行人工代码走读。重点关注循环依赖、全局变量、过长函数。
- 依赖分析:绘制模块依赖图,识别出“上帝模块”和“孤岛模块”。
- 生成报告:输出一份包含坏味道列表、量化指标、风险等级、重构建议的体检报告。
我的习惯:体检报告一定要有“红黄绿”三色标记。红色代表必须立即处理,黄色代表建议在迭代中优化,绿色代表健康。这样项目管理者一眼就能看出优先级。
2.4 知识体系图:代码体检全景
下面这张图总结了代码体检的核心要素和流程:
2.5 实战建议:从体检到行动
体检不是目的,改善才是。拿到体检报告后,我建议按以下优先级行动:
- P0(立即处理):圈复杂度超过 50 的函数、循环依赖、全局变量滥用。这些是定时炸弹。
- P1(本周内处理):圈复杂度 21-50 的函数、过长函数(超过 200 行)、重复代码。
- P2(迭代中优化):命名不规范、魔法数字、注释质量差。这些不影响功能,但影响长期维护。
避坑指南:不要试图一次性解决所有问题。我曾经在一个项目中试图“完美重构”,结果改了三个月,业务需求变了,重构代码全废了。记住:小步快跑,持续改善。