5.2 代码质量信号、静态分析与评审
代码质量无法被一个分数完整表示。复杂度、重复率、覆盖率和告警数量都是观察窗口:它们帮助团队定位值得阅读的区域,不能代替对业务风险、设计边界和运行结果的判断。
圈复杂度衡量控制流路径
Cyclomatic Complexity 基于控制流图的独立路径数量。教学中常近似写成:
复杂度约为 1 + 决策点数量但 case、短路布尔表达式、异常处理和不同语言结构的计数方式会随工具规则而异。应让同一工具持续测量趋势,不要手算后与另一个工具硬比较。
EligibilityResult check(Registration r) {
if (r.isBanned()) return REJECTED;
if (r.level() < 18) return REJECTED;
if (!r.hasPaid() && !r.hasWaiver()) return PENDING_PAYMENT;
return ACCEPTED;
}这个函数有多个路径,但提前返回和清晰命名可能仍比一个低复杂度、却把规则藏在晦涩表达式中的版本易读。复杂度上升时应追问:是否混合职责、是否缺少领域概念、测试案例是否足够,而不是见到阈值就机械拆函数。
组合多个质量信号
| 信号 | 能提示什么 | 不能直接证明什么 |
|---|---|---|
| 圈/认知复杂度 | 分支和理解负担可能较高 | 一定存在缺陷 |
| 代码重复 | 修改可能需要同步多处 | 两段逻辑一定应抽象为一个 |
| 依赖与耦合 | 变化可能传播 | 依赖数量少就设计良好 |
| 覆盖率 | 哪些代码未被测试执行 | 断言充分、需求正确 |
| 变更频率/churn | 哪些区域经常被触碰 | 高频变化一定是坏设计 |
| 缺陷与事故记录 | 哪些模块真实造成损失 | 没有事故就没有潜在风险 |
把“高复杂度 + 高频修改 + 多次事故”组合起来,通常比按全仓库统一阈值排序更有行动价值。
静态分析是可重复的自动评审
编译器之外的分析器可以发现:
- 明确的错误模式,如错误空值判断或资源未关闭;
- API 误用与并发风险;
- 重复代码、复杂度和未使用代码;
- 团队约定的依赖和命名规则;
- 部分安全与数据流问题。
规则应按风险管理:
阻断:高置信度正确性、安全、兼容问题
告警:需要人工判断的设计与维护性问题
信息:趋势观察或渐进治理项抑制告警时写清原因和范围。全局关闭规则会让真正问题一起消失;一条长期存在却从不处理的告警,也会训练团队忽略 CI。
升级分析器或规则集时,先固定版本、阅读变更说明,在独立变更中处理新增告警,避免工具升级与业务改动混在一起。
代码评审补充自动化看不到的部分
评审应优先确认:
- 需求和方案是否解决了正确问题;
- 设计边界、数据所有权和失败语义是否合理;
- 正确性、安全、并发与兼容风险是否被处理;
- 测试是否覆盖本次变化的关键风险;
- 名称、注释和文档是否让后来者理解“为什么”;
- 复杂度是否与当前需求相称。
格式、导入顺序和可自动修复的规则交给工具。人的注意力应留给需要上下文和判断的内容。
小变更按“一个概念”衡量
评审友好的变更通常只做一件可独立理解、测试和回退的事。行数只是参考:自动生成文件可能很大却易核对,跨五十个文件的两百行手工修改可能很难审。
较大的功能可以拆成:
1. 增加特征测试
2. 纯重命名/移动
3. 引入新端口但仍走旧实现
4. 增加新实现及测试
5. 切换调用路径
6. 删除旧实现每一步都保持系统工作,比在长期分支里一次交付全部变化更容易发现错误和回退。
写出可执行的评审说明
变更描述至少回答:
## Why
当前问题、影响用户/系统的方式,以及为什么现在处理。
## What
本次改变的边界;明确没有改变什么。
## Risk
失败模式、迁移与兼容影响、回滚方式。
## Verification
自动测试、手工检查、指标或截图。评论要区分阻断问题和非阻断建议,并解释影响:
[blocking] 两次重试会重复扣减名额;请让命令以 registrationId 幂等。
[suggestion] 这个局部变量可改为 deadline,使时区语义更清楚。
[question] 取消后退款失败时,报名状态由哪个组件恢复?评审讨论针对代码与约束,不针对作者。遇到分歧时回到需求、质量属性、团队规范或实验数据;如果形成长期规则,把结论写入 ADR 或工程规范,避免每个 PR 重复争论。
指标怎样回到行动
生产事故与交付阻力
-> 找到高风险代码区域
-> 小步重构与补充测试
-> 静态规则和评审防止回归
-> 观察缺陷率、交付时间和恢复能力是否改善如果指标改善却没有减少缺陷、理解时间或修改风险,应重新检查指标是否选错。工具要帮助团队获得反馈,指标本身不是目标。