CRAP 是由 Alberto Savoia 和 Bob Evans 在 2007 年提出的程式碼品質指標,全名是 Change Risk Anti-Patterns(也有人稱 Change Risk Analysis and Predictions)。它的核心想法很直白:一段程式碼「危不危險」,取決於它有多複雜,以及它有多少測試保護。複雜又沒測試的程式碼,改起來風險最高——這就是所謂的 “crappy code”。
公式
CRAP(m) = comp(m)² × (1 − cov(m)/100)³ + comp(m)
其中:
comp(m):方法 m 的循環複雜度(cyclomatic complexity)cov(m):該方法的測試涵蓋率(通常用 branch coverage,以百分比計)
一般慣例的門檻值是 30:CRAP 分數超過 30 的方法,就該被視為高風險,需要重構或補測試。
公式設計得很巧妙:
- 涵蓋率的懲罰是三次方——涵蓋率從 0% 提升到 50%,懲罰項就從 1 降到 0.125,效果非常顯著。這鼓勵你「先補一些測試」,而不是追求 100%。
- 複雜度是平方——複雜度翻倍,風險大約變四倍。
- 即使涵蓋率 100%,CRAP 仍等於複雜度本身——一個複雜度 35 的方法,就算測好測滿,分數還是 35,依然超標。這在提醒你:測試救不了過度複雜的設計,該重構就要重構。
實際範例
假設有一個計算運費的方法:
java
public int shippingFee(Order order) {
if (order == null) throw new IllegalArgumentException();
int fee = 0;
if (order.total() < 1000) { // 分支 1
fee = 100;
if (order.isRemoteArea()) { // 分支 2
fee += 150;
}
}
if (order.isFragile()) { // 分支 3
fee += 50;
}
if (order.isVip() && fee > 0) { // 分支 4 (含 &&)
fee = fee / 2;
}
return fee;
}
這個方法的循環複雜度大約是 6(1 + 5 個判斷點)。
情境一:完全沒有測試(coverage = 0%)
CRAP = 6² × (1 − 0)³ + 6 = 36 + 6 = 42 → 超過 30,高風險
情境二:寫了兩個測試,涵蓋率達到 50%
CRAP = 36 × (0.5)³ + 6 = 36 × 0.125 + 6 = 10.5 → 安全
只補了一半的測試,分數就從 42 掉到 10.5——這正是三次方懲罰的威力,也說明 CRAP 分析對「遺留系統要從哪裡開始補測試」特別有指導性:優先處理複雜度高、涵蓋率低的方法,投資報酬率最高。
情境三:方法膨脹到複雜度 20,但只有 40% 涵蓋率
CRAP = 400 × (0.6)³ + 20 = 400 × 0.216 + 20 = 106.4 → 嚴重超標
此時就算把涵蓋率拉到 80%,分數是 400 × 0.008 + 20 = 23.2,勉強及格,但只要涵蓋率稍微退化就會再度爆表。正確做法是先拆解方法降低複雜度。
下圖可以看出不同複雜度下,涵蓋率對 CRAP 分數的影響:
CRAP 分數 vs. 測試涵蓋率
CRAP 分數 by 測試涵蓋率
複雜度 5複雜度 10複雜度 20Use the arrow keys to move between data points
圖中可以清楚看到兩件事:複雜度 5 的方法即使零測試也只是剛好貼在門檻 30 上;而複雜度 20 的方法,涵蓋率要拉到 80% 附近才降得下來,而且永遠降不到 20 以下。
工具支援
- Java:最早的實作是 Crap4j;現在多數人直接看 JaCoCo 的複雜度 + 涵蓋率報告自行判讀,或用 SonarQube 的類似指標
- PHP:PHPUnit 的 code coverage 報告內建 CRAP index,每個方法旁邊直接顯示
- 其他語言:即使工具沒直接算 CRAP,只要有 cyclomatic complexity 和 branch coverage 兩個數字,自己套公式就行
實務上怎麼用
CRAP 分析最大的價值不在於「追求所有方法都低於 30」,而是提供一個優先排序:面對一個遺留系統,你不可能一次補齊所有測試,但把方法按 CRAP 分數排序後,排在最前面的就是「最複雜 × 最沒保護 × 最可能改壞」的地方——先在那裡建立特徵測試(characterization tests),再進行重構,是最符合成本效益的切入點。這個思路和 Michael Feathers 在《Working Effectively with Legacy Code》裡的策略是相通的。
一個常見的提醒:CRAP 和所有指標一樣會被「玩弄」——為了降分數而寫一堆沒有斷言的假測試,涵蓋率上去了、分數下來了,但風險一點都沒少。指標是用來找熱點的,不是用來當 KPI 的。
發表迴響