crap-score

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

CRAP Score Analysis

CRAP得分分析

Calculate CRAP (Change Risk Anti-Patterns) scores for .NET methods to identify code that is both complex and undertested.
为.NET方法计算CRAP(变更风险反模式)得分,识别既复杂又缺乏测试的代码。

Background

背景

The CRAP score combines cyclomatic complexity and code coverage into a single metric:
$$\text{CRAP}(m) = \text{comp}(m)^2 \times (1 - \text{cov}(m))^3 + \text{comp}(m)$$
Where:
  • $\text{comp}(m)$ = cyclomatic complexity of method $m$
  • $\text{cov}(m)$ = code coverage ratio (0.0 to 1.0) of method $m$
CRAP ScoreRisk LevelInterpretation
< 5LowSimple and well-tested
5-15ModerateAcceptable for most code
15-30HighNeeds more tests or simplification
> 30CriticalRefactor and add coverage urgently
A method with 100% coverage has CRAP = complexity (the minimum). A method with 0% coverage has CRAP = complexity^2 + complexity.
CRAP得分将圈复杂度代码覆盖率结合为单一指标:
$$\text{CRAP}(m) = \text{comp}(m)^2 \times (1 - \text{cov}(m))^3 + \text{comp}(m)$$
其中:
  • $\text{comp}(m)$ = 方法$m$的圈复杂度
  • $\text{cov}(m)$ = 方法$m$的代码覆盖率(取值范围0.0到1.0)
CRAP得分风险等级释义
< 5代码简单且测试覆盖完善
5-15中等对大部分代码来说可接受
15-30需要补充测试或简化代码
> 30严重急需重构并补充测试覆盖率
覆盖率100%的方法CRAP得分等于其复杂度(最低值)。覆盖率为0%的方法CRAP得分等于复杂度的平方加复杂度。

When to Use

适用场景

  • User wants to assess which methods are risky due to low coverage and high complexity
  • User asks for CRAP score of specific methods, classes, or files
  • User wants to prioritize which code to test next
  • User wants to evaluate test quality beyond simple coverage percentages
  • 用户需要评估哪些方法因覆盖率低、复杂度高存在风险
  • 用户要求获取指定方法、类或文件的CRAP得分
  • 用户需要确定后续测试的优先级
  • 用户需要在简单覆盖率百分比之外评估测试质量

When Not to Use

不适用场景

  • User just wants to run tests (use
    run-tests
    skill)
  • User wants to write new tests (use
    writing-mstest-tests
    skill or general coding assistance)
  • User only wants a coverage percentage without complexity analysis
  • 用户仅需要运行测试(使用
    run-tests
    skill)
  • 用户需要编写新测试(使用
    writing-mstest-tests
    skill或通用代码协助能力)
  • 用户仅需要覆盖率百分比,不需要复杂度分析

Inputs

输入参数

InputRequiredDescription
Target scopeYesMethod name, class name, or file path to analyze
Test project pathNoPath to the test project. Defaults to discovering test projects in the solution.
Source project pathNoPath to the source project under analysis
输入项是否必填描述
目标范围要分析的方法名、类名或文件路径
测试项目路径测试项目的路径。默认会自动发现解决方案中的测试项目。
源项目路径待分析的源项目路径

Workflow

工作流程

Step 1: Collect code coverage data

步骤1:收集代码覆盖率数据

If no coverage data exists yet (no Cobertura XML available), always run
dotnet test
with coverage collection first
and mention the exact command in your response. Do not skip this step -- CRAP scores require coverage data.
Check the test project's
.csproj
for the coverage package, then run the appropriate command:
Coverage PackageCommandOutput Location
coverlet.collector
dotnet test --collect:"XPlat Code Coverage" --results-directory ./TestResults
Typically under
TestResults/<guid>/coverage.cobertura.xml
. Search recursively under the results directory (for example,
TestResults/**/coverage.cobertura.xml
) or use any explicit coverage path the user provides.
Microsoft.Testing.Extensions.CodeCoverage
(.NET 9)
dotnet test -- --coverage --coverage-output-format cobertura --coverage-output ./TestResults
--coverage-output
path
Microsoft.Testing.Extensions.CodeCoverage
(.NET 10+)
dotnet test --coverage --coverage-output-format cobertura --coverage-output ./TestResults
--coverage-output
path
如果尚无覆盖率数据(无可用的Cobertura XML),必须先运行带覆盖率收集功能的
dotnet test
命令
,并在响应中说明具体命令。不得跳过此步骤——CRAP得分必须基于覆盖率数据计算。
检查测试项目的
.csproj
文件确认使用的覆盖率包,然后运行对应命令:
覆盖率包命令输出位置
coverlet.collector
dotnet test --collect:"XPlat Code Coverage" --results-directory ./TestResults
通常位于
TestResults/<guid>/coverage.cobertura.xml
下。在结果目录下递归搜索即可(例如
TestResults/**/coverage.cobertura.xml
),也可以使用用户提供的明确覆盖率文件路径。
Microsoft.Testing.Extensions.CodeCoverage
(.NET 9)
dotnet test -- --coverage --coverage-output-format cobertura --coverage-output ./TestResults
--coverage-output
指定的路径
Microsoft.Testing.Extensions.CodeCoverage
(.NET 10+)
dotnet test --coverage --coverage-output-format cobertura --coverage-output ./TestResults
--coverage-output
指定的路径

Step 2: Compute cyclomatic complexity

步骤2:计算圈复杂度

Analyze the target source files to determine cyclomatic complexity per method. Count the following decision points (each adds 1 to the base complexity of 1):
ConstructExample
if
if (x > 0)
else if
else if (y < 0)
case
(each)
case 1:
for
for (int i = 0; ...)
foreach
foreach (var item in list)
while
while (running)
do...while
do { } while (cond)
catch
(each)
catch (Exception ex)
&&
if (a && b)
||
(OR)
if (a || b)
??
value ?? fallback
?.
obj?.Method()
? :
(ternary)
x > 0 ? a : b
Pattern match arm
x is > 0 and < 10
Base complexity is 1 for every method. Each decision point adds 1.
When analyzing, read the source file and count these constructs per method. Report the breakdown.
分析目标源文件,确定每个方法的圈复杂度。统计以下决策点(每出现一个,就在基础复杂度1的基础上加1):
语法结构示例
if
if (x > 0)
else if
else if (y < 0)
case
(每个分支)
case 1:
for
for (int i = 0; ...)
foreach
foreach (var item in list)
while
while (running)
do...while
do { } while (cond)
catch
(每个分支)
catch (Exception ex)
&&
if (a && b)
||
(或)
if (a || b)
??
value ?? fallback
?.
obj?.Method()
? :
(三元运算符)
x > 0 ? a : b
模式匹配分支
x is > 0 and < 10
每个方法的基础复杂度为1,每出现一个上述决策点加1。
分析时读取源文件,按方法统计上述结构的数量,并给出统计明细。

Step 3: Extract per-method coverage from Cobertura XML

步骤3:从Cobertura XML中提取每个方法的覆盖率

Parse the Cobertura XML to find each method's
line-rate
attribute under the target
<class>
element. If
line-rate
is not available at method level, compute it from the
<lines>
elements:
$$\text{cov}(m) = \frac{\text{lines with hits} > 0}{\text{total lines}}$$
Method names in Cobertura may differ from source (async methods, lambdas). Match by line ranges when names don't align.
解析Cobertura XML,找到目标
<class>
元素下每个方法的
line-rate
属性。如果方法层级没有
line-rate
属性,可通过
<lines>
元素计算:
$$\text{cov}(m) = \frac{\text{命中次数>0的行数}}{\text{总行数}}$$
Cobertura中的方法名可能和源码中的不一致(异步方法、lambda表达式等),当名称不匹配时按行范围匹配。

Step 4: Calculate CRAP scores

步骤4:计算CRAP得分

For each method in scope, apply the formula:
$$\text{CRAP}(m) = \text{comp}(m)^2 \times (1 - \text{cov}(m))^3 + \text{comp}(m)$$
对范围内的每个方法应用如下公式:
$$\text{CRAP}(m) = \text{comp}(m)^2 \times (1 - \text{cov}(m))^3 + \text{comp}(m)$$

Step 5: Present results

步骤5:展示结果

Present a sorted table (highest CRAP first):
| Method                          | Complexity | Coverage | CRAP Score | Risk     |
|---------------------------------|------------|----------|------------|----------|
| OrderService.ProcessOrder       | 12         | 45%      | 28.4       | High     |
| OrderService.ValidateItems      | 8          | 90%      | 8.1        | Moderate |
| OrderService.CalculateTotal     | 3          | 100%     | 3.0        | Low      |
Include:
  • Summary: total methods analyzed, how many in each risk category
  • Top offenders: methods with CRAP > 30, with specific recommendations
  • Quick wins: methods with high complexity but where small coverage improvements would drop the score significantly
按CRAP得分从高到低排序,展示表格:
| Method                          | Complexity | Coverage | CRAP Score | Risk     |
|---------------------------------|------------|----------|------------|----------|
| OrderService.ProcessOrder       | 12         | 45%      | 28.4       | High     |
| OrderService.ValidateItems      | 8          | 90%      | 8.1        | Moderate |
| OrderService.CalculateTotal     | 3          | 100%     | 3.0        | Low      |
包含以下内容:
  • 摘要:已分析的方法总数,各风险等级的方法数量
  • 最高风险项:CRAP得分>30的方法,附带具体优化建议
  • 低成本优化项:复杂度高,但小幅提升覆盖率就能大幅降低得分的方法

Step 6: Provide actionable recommendations

步骤6:提供可落地的优化建议

For high-CRAP methods, suggest one or both:
  1. Add tests -- identify uncovered branches and suggest specific test cases
  2. Reduce complexity -- suggest extract-method refactoring for deeply nested logic
Calculate the coverage needed to bring a method below a CRAP threshold of 15:
$$\text{cov}_{\text{needed}} = 1 - \left(\frac{15 - \text{comp}}{\text{comp}^2}\right)^{1/3}$$
This formula only applies when comp < 15. When comp >= 15, the minimum possible CRAP score (at 100% coverage) is comp itself, which already meets or exceeds the threshold. In that case, coverage alone cannot bring the CRAP score below the threshold -- the method must be refactored to reduce its cyclomatic complexity first.
Report this as: "To bring
ProcessOrder
(complexity 12) below CRAP 15, increase coverage from 45% to at least 72%." For methods where complexity alone exceeds the threshold, report: "
ComplexMethod
(complexity 18) cannot reach CRAP < 15 through testing alone -- reduce complexity by extracting sub-methods."
对高CRAP得分的方法,建议以下一种或两种优化方案:
  1. 补充测试——识别未覆盖的分支,建议具体的测试用例
  2. 降低复杂度——建议对深层嵌套的逻辑执行提取方法的重构
计算将方法CRAP得分降低到15以下所需的覆盖率:
$$\text{cov}_{\text{needed}} = 1 - \left(\frac{15 - \text{comp}}{\text{comp}^2}\right)^{1/3}$$
该公式仅适用于复杂度<15的情况。当复杂度>=15时,(覆盖率100%情况下的)最低CRAP得分等于复杂度本身,已经达到或超过阈值。这种情况下仅靠提升覆盖率无法将CRAP得分降低到阈值以下——必须先重构方法降低圈复杂度。
展示方式示例:"要将
ProcessOrder
(复杂度12)的CRAP得分降到15以下,需要将覆盖率从45%提升到至少72%。" 对于复杂度本身超过阈值的方法,展示为:"
ComplexMethod
(复杂度18)无法仅通过测试将CRAP降到15以下——请通过提取子方法降低复杂度。"

Validation

校验项

  • Verify that coverage data was collected successfully (Cobertura XML exists and contains data)
  • Cross-check that method names in coverage data match the source code
  • Confirm CRAP scores by spot-checking the formula on one method manually
  • Ensure a 100%-covered method's CRAP equals its complexity exactly
  • 验证覆盖率数据已成功收集(Cobertura XML存在且包含有效数据)
  • 交叉核对覆盖率数据中的方法名与源代码是否匹配
  • 手动抽查一个方法的公式计算结果,确认CRAP得分正确
  • 确保覆盖率100%的方法CRAP得分完全等于其复杂度

Common Pitfalls

常见误区

  • Stale coverage data: Always regenerate coverage before computing CRAP scores. Old coverage files will produce misleading results.
  • Method name mismatches: Cobertura XML may use mangled/compiler-generated names for async methods, lambdas, or local functions. Match by line ranges when names don't align.
  • Generated code: Exclude auto-generated files (e.g.,
    *.Designer.cs
    ,
    *.g.cs
    ) from analysis unless explicitly requested.
  • 覆盖率数据过期:计算CRAP得分前务必重新生成覆盖率数据,旧的覆盖率文件会产生误导性结果
  • 方法名不匹配:Cobertura XML可能对异步方法、lambda、本地函数使用编译器生成的混淆名称,名称不匹配时按行范围匹配
  • 自动生成代码:除非用户明确要求,否则排除自动生成的文件(例如
    *.Designer.cs
    *.g.cs
    )的分析