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 体检流程:从扫描到报告

我建议按以下步骤进行代码体检:

  1. 静态扫描:使用工具(如 PC-Lint、Cppcheck、SonarQube)进行全量扫描,获取圈复杂度、代码行数、重复率等基础数据。
  2. 人工审查:针对扫描结果中的高风险模块,进行人工代码走读。重点关注循环依赖、全局变量、过长函数。
  3. 依赖分析:绘制模块依赖图,识别出“上帝模块”和“孤岛模块”。
  4. 生成报告:输出一份包含坏味道列表、量化指标、风险等级、重构建议的体检报告。
我的习惯:体检报告一定要有“红黄绿”三色标记。红色代表必须立即处理,黄色代表建议在迭代中优化,绿色代表健康。这样项目管理者一眼就能看出优先级。

2.4 知识体系图:代码体检全景

下面这张图总结了代码体检的核心要素和流程:

代码体检全景图 代码体检全景图 遗留代码 定性识别:坏味道 结构型 命名型 依赖型 定量评估:量化指标 圈复杂度 代码行数 耦合/内聚 注释质量 静态扫描 人工审查 依赖分析 生成报告 说明:定性识别与定量评估相辅相成。坏味道告诉你“哪里有问题”,量化指标告诉你“问题有多严重”。

2.5 实战建议:从体检到行动

体检不是目的,改善才是。拿到体检报告后,我建议按以下优先级行动:

  • P0(立即处理):圈复杂度超过 50 的函数、循环依赖、全局变量滥用。这些是定时炸弹。
  • P1(本周内处理):圈复杂度 21-50 的函数、过长函数(超过 200 行)、重复代码。
  • P2(迭代中优化):命名不规范、魔法数字、注释质量差。这些不影响功能,但影响长期维护。
避坑指南:不要试图一次性解决所有问题。我曾经在一个项目中试图“完美重构”,结果改了三个月,业务需求变了,重构代码全废了。记住:小步快跑,持续改善。