CRAP 指数:用圈复杂度与测试覆盖量化代码风险
CRAP(Change Risk Anti-Patterns)指数把圈复杂度与测试覆盖率合成一个分数:CRAP(m) = CC² × (1 − cov)³ + CC。由 Alberto Savoia 与 Bob Evans 提出,用于量化"又复杂又没测试"的代码风险。本文讲清公式与应用。
直接回答:CRAP 指数(Change Risk Anti-Patterns)把圈复杂度和测试覆盖率合进一个风险分数,公式为 CRAP(m) = CC² × (1 − cov)³ + CC——复杂度平方、未覆盖比例立方,非线性放大"复杂且没测试"的函数。该指标由 Alberto Savoia 与 Bob Evans 于 2007 年提出,Google Testing Blog 于 2011 年介绍,不能混为同一发布日期。
两个成分
圈复杂度(Cyclomatic Complexity):数函数内线性独立的执行路径。通常以决策点与控制流计算,具体 switch、异常和语言表达式的计数依工具而异;普通 else 不必独立加一。
| 分值 | 含义 |
|---|---|
| 1–5 | 简单易懂 |
| 6–10 | 中等复杂 |
| >10 | 理解困难 |
| >15 | 建议重点审查(经验分档,非故障概率) |
高复杂度本身不致命——复杂函数可通过有效测试降低部分回归风险:测试记录了预期行为,能够帮助发现所覆盖行为的回归。
测试覆盖率:自动测试执行过的代码比例。低复杂度也可能隐藏高影响错误;复杂代码覆盖低才是 CRAP 瞄准的问题。注意 100% 行覆盖不等于正确——只说明代码被执行过,不说明每条路径都断言了正确输出。CRAP 用"未覆盖的复杂度比例"来缩放风险。
公式的设计巧思
CRAP(m) = CC² × (1 - cov)³ + CC
这里 cov 取 0 到 1,例如 80% 写为 0.8;不要将 80 直接代入。覆盖口径与复杂度工具需保持一致。
- 100% 覆盖:(1 − 1) = 0,首项归零,CRAP = CC——风险即固有复杂度,但视为已被测试管理
- 0% 覆盖:(1 − 0)³ = 1,CRAP = CC² + CC——复杂度被平方放大
指数设计让公式非线性:给复杂函数补测试,分数断崖式下降;简单函数覆盖合格,分数就贴着复杂度本身。
计算示例与 CI 集成
选择能输出函数级复杂度与覆盖率的工具,将数据按同一函数边界合并后计算 CRAP。示例:CC=10、cov=0.8 时,CRAP=100×0.2³+10=10.8。
CI 可对新增或修改函数设提醒,但不应只追分而拆函数或补无断言测试。工具选择需核对语言版本、函数边界和覆盖统计口径。
与 AI 生成代码的关系
无论代码由人还是 Agent 编写,复杂且缺少有效测试的函数都值得优先审查。可把 CRAP 作为验收时的风险提示,但不要根据作者身份推断质量,也不能用单个分数替代行为测试、安全检查和代码评审。
常见问题(FAQ)
Q:CRAP 分多少算超标?
A:常见阈值约定是 30——可用来提示进一步审查,而非强制重构或故障概率。团队可按代码库现状先统计分布再定线。
Q:覆盖率高但断言很弱怎么办?
A:这是 CRAP 的盲区——它只认执行覆盖,不认断言质量。配合变异测试(mutation testing)可帮助发现弱断言,但也不是完整正确性证明。
Q:哪些语言有现成工具?
A:Java(Crap4j)、Python(配合 radon/coverage 计算)等主流生态都有实现,公式本身语言无关。
参考资料
资料核对日期:2026 年 9 月 29 日。本文基于公开文档整理,代码片段和评估方案未作独立运行或性能验证;厂商测试结果已注明来源。