呱呱聚合
← 返回基因库
大模型应用

当需要评估 .NET 方法因复杂度高且测试不足带来的变更风险…

对指定的 .NET 方法、类或文件计算 CRAP 分数,识别既复杂又缺乏覆盖的高风险代码并给出可执行的测试/重构建议。

被复用0 次
复用有效—
置信度85%
沉淀时间2026-09-23
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
来自 wangwren 的小呱沉淀
GDI C 49/100

进化评分(GDI)

综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」

内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15

适用场景

当需要评估 .NET 方法因复杂度高且测通用

解决思路

  1. 收集代码覆盖率数据:若尚无覆盖率数据,先对测试项目分类。SDK 风格项目运行 `dotnet test` 并收集覆盖率;经典非 SDK 项目(含 `ToolsVersion`、显式 compile items 或 `packages.config`)只能使用仓库自带的、能输出 Cobertura 的覆盖率命令,若没有则向用户索要 Cobertura XML 并停止,不要迁移项目或注入 SDK 风格覆盖率包。CRAP 分数始终需要真实覆盖率数据。
  2. 检查测试项目的 `.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` 路径。
  3. 绝不估算覆盖率:猜测的覆盖率会产生错误的 CRAP 分数,比没有答案更糟。对于没有仓库覆盖率命令或现有报告的经典项目,在此停止并索要 Cobertura,不要使用任何收集回退方案。
  4. 对于 SDK 风格项目,若第一条命令未产出 Cobertura XML,在放弃前依次尝试以下收集方案:1) 仅对 SDK 风格项目,若无 provider 则添加:`dotnet add <test.csproj> package coverlet.collector`,然后重跑;绝不要对 `packages.config` 或经典非 SDK 项目使用此回退。2) 使用独立收集器,即使测试宿主或共享程序集阻止进程内收集器也能工作:`dotnet tool install --global dotnet-coverage`,然后 `dotnet-coverage collect -f cobertura -o coverage.cobertura.xml "dotnet test <test.csproj>"`。
  5. 对任何项目类型,若已存在真实的二进制 `.coverage` 报告,用 ReportGenerator 转换或汇总该现有数据:3) 转换现有报告:`dotnet tool install --global dotnet-reportgenerator-globaltool`,然后 `reportgenerator -reports:<file> -targetdir:cov -reporttypes:Cobertura`。4) 测试失败但仍运行了?覆盖率来自已执行的测试——继续使用该数据并注明失败。
  6. 若所有路径都失败,报告无法收集覆盖率,展示尝试过的命令及其错误,并停止。如有用可单独报告复杂度,但绝不发布基于假设覆盖率百分比的 CRAP 数字。
  7. 使用报告前,验证其能解析、至少包含一个类和方法、且包含请求的目标。空报告或遗漏目标的报告是收集或过滤失败,不是 0% 覆盖率。尽可能重新生成覆盖率;否则停止,不发布 CRAP 分数。
  8. 若用户提供现有报告,说明其未被重新生成。除非其来源已通过本次分析中运行仓库覆盖率命令确立,否则不要将其数据描述为当前数据。
  9. 计算圈复杂度:优先使用仓库提供的代码度量报告中的机器生成的每方法复杂度,或当 Cobertura 方法的 `complexity` 属性映射到当前源码时使用该属性。Microsoft.CodeAnalysis.Metrics 可通过 `msbuild /t:Metrics` 生成方法级 `CyclomaticComplexity` 数据,但未经用户批准不要添加该包或修改项目。
  10. 若无机器生成的度量,分析当前目标源码并将结果标记为手工复杂度计数。统计以下决策点(每个在基础复杂度 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。
  11. 手工计数时,阅读源文件,报告逐构造的分解,不要用源码注释作为证据。若报告的 complexity 属性与当前源码计数不一致,报告冲突,且不要将任一结果 CRAP 分数作为权威。
  12. 从 Cobertura XML 提取每方法覆盖率:解析 Cobertura XML,在目标 `<class>` 元素下找到每个方法的 `line-rate` 属性。若方法级无 `line-rate`,从 `<lines>` 元素计算:cov(m) = hits > 0 的行数 / 总行数。Cobertura 中的方法名可能与源码不同(异步方法、lambda)。名称不一致时按行范围匹配。
  13. 当 `line-rate` 和 `<lines>` 同时存在时,重新计算命中率并比较。只允许正常报告舍入(一个百分点);若差异更大,报告自相矛盾。重新生成或报告冲突并停止计算 CRAP。绝不静默选择能产生预期分数的值。
  14. 计算 CRAP 分数:对范围内每个方法应用公式 CRAP(m) = comp(m)^2 × (1 - cov(m))^3 + comp(m)。使用计算器或脚本进行算术并展示代入的复杂度和覆盖率。不要心算公式。
  15. 呈现结果:展示按 CRAP 从高到低排序的表格,包含 Method、Complexity、Coverage、CRAP Score、Risk 列。包含:摘要(分析的方法总数、各风险类别数量);主要问题方法(CRAP > 30 的方法及具体建议);快速改进项(复杂度高但小幅覆盖率提升即可显著降低分数的方法)。
  16. 提供可执行建议:对高 CRAP 方法,建议以下一项或两项:1) 添加测试——识别未覆盖分支并建议具体测试用例;2) 降低复杂度——对深层嵌套逻辑建议提取方法重构。
  17. 计算将方法降到 CRAP 阈值 15 以下所需的覆盖率:cov_needed = 1 - ((15 - comp) / comp^2)^(1/3)。该公式仅在 comp < 15 时适用。当 comp >= 15 时,最小可能 CRAP 分数(100% 覆盖率下)就是 comp 本身,已达到或超过阈值。此时仅靠覆盖率无法将 CRAP 降到阈值以下——必须先重构降低圈复杂度。报告为:"To bring `ProcessOrder` (complexity 10) below CRAP 15, increase coverage from 45% to more than 63.2% (at least 64% when reporting whole percentages)." 对复杂度本身超过阈值的方法报告:"`ComplexMethod` (complexity 18) cannot reach CRAP < 15 through testing alone -- reduce complexity by extracting sub-methods."
  18. 验证:确认覆盖率数据成功收集(Cobertura XML 存在且含数据);确认目标方法存在,缺失不是 0% 覆盖率的证据;确认每个覆盖率数字都来自该 XML——无估算、假设或源码注释推导的值;当方法 `line-rate` 与行命中率同时存在时交叉核对;交叉核对覆盖率数据中的方法名与源码匹配;用计算器或脚本输出确认 CRAP 分数;确保 100% 覆盖方法的 CRAP 恰好等于其复杂度。
  19. 避免常见陷阱:收集失败时估算覆盖率——绝不要做,产生的 CRAP 分数在关键方向上错误,走完 Step 1 的回退后报告阻塞;将空报告或缺失方法当作 0% 覆盖率——这是收集、过滤或方法映射失败,不要制造分数;信任自相矛盾的 Cobertura 字段——比较 `line-rate` 与行命中率,若超出舍入范围则停止;信任源码中过时的复杂度注释——从当前代码计算圈复杂度,前作者留下的 `// complexity: 7` 注释不是证据;心算 CRAP——用计算器或脚本并展示代入输入;对共享程序集或测试宿主收集器错误放弃——`dotnet-coverage collect` 进程外运行,通常能在进程内收集器失败处成功;过时覆盖率数据——当用户要求当前结果或源码/二进制变化时重新生成,否则披露提供的报告未被重新生成;方法名不匹配——Cobertura XML 对异步方法、lambda 或局部函数可能使用改写/编译器生成名,名称不一致时按行范围匹配;生成代码——除非明确要求,从分析中排除自动生成文件(如 `*.Designer.cs`、`*.g.cs`)。

适用边界 · 注意事项