crap-score について
.NET メソッドの CRAP スコアを分析するスキルです。循環的複雑度とテストカバレッジの両面からリスクの高いメソッドを特定し、実際の Cobertura データを必須としてカバレッジを推定しません。
主な機能
- 01メソッドごとの CRAP スコア計算
- 02複雑度とカバレッジによるリスク評価
- 03Cobertura からメソッドカバレッジを抽出
- 04循環的複雑度の手動カウント
- 05リスク順の整理と改善案
活用シーン
- 01指定メソッドのテストリスク評価
- 02クラスやファイル内の高リスク箇所の比較
- 03カバレッジと複雑度に基づくテスト優先順位付け
- 04低カバレッジによるリスク上昇の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dotnet/ skills --skill crap-scoreインストール後、Claude Code で/crap-scoreを入力して読み込まれたか確認します。
詳細
CRAP スコア分析
.NET メソッドの CRAP(Change Risk Anti-Patterns)スコアを計算し、複雑でテストが不十分なコードを特定します。
背景
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 to < 15 | 中程度 | ほとんどのコードで許容範囲です |
| 15 to 30 | 高 | テストの追加または簡素化が必要です |
| > 30 | 重大 | 早急にリファクタリングし、カバレッジを追加してください |
カバレッジが100%のメソッドの CRAP スコアは、複雑度と同じ値(最小値)になります。カバレッジが0%のメソッドの CRAP スコアは、複雑度^2 + 複雑度になります。
使用する場面
- カバレッジの低さと複雑度の高さによって、どのメソッドにリスクがあるかを評価したい場合
- 特定のメソッド、クラス、ファイルの CRAP スコアを確認したい場合
- カバレッジと複雑度に基づくリスクから、名前を指定したメソッド、クラス、ファイル内で次にテストすべき対象の優先順位を決めたい場合
- 単純なカバレッジ率だけでなく、テスト品質を評価したい場合
使用しない場面
- テストの実行だけをしたい場合(
run-testsスキルを使用してください) - 新しいテストを作成したい場合(
code-testingを使用してください) - 複雑度の分析は不要で、カバレッジ率だけを確認したい場合
- プロジェクト全体のカバレッジ/CRAP 分析や優先順位付けをしたい場合(
coverage-analysisを使用してください)
入力
| 入力 | 必須 | 説明 |
|---|---|---|
| 対象範囲 | はい | 分析するメソッド名、クラス名、またはファイルパス |
| テストプロジェクトのパス | いいえ | テストプロジェクトへのパス。既定では、ソリューション内のテストプロジェクトを検出します。 |
| ソースプロジェクトのパス | いいえ | 分析対象のソースプロジェクトへのパス |
ワークフロー
ステップ 1:コードカバレッジデータを収集する
ユーザーが対象を含む有効な Cobertura レポートを提供した場合は、それを直接使用し、テストを再実行しないでください。提供されたレポートに不備がある、空である、内部に矛盾がある、または対象が含まれていない場合は、入力失敗として扱います。可能であればリポジトリと互換性のあるコマンドでレポートを再生成し、収集できない場合は有効なレポートを依頼してください。それ以外の場合は、run-tests を実行してリポジトリのテストプラットフォームを分類し、互換性のあるコマンド形式を確認してから、Cobertura を出力するコマンドを必須とします。
| カバレッジプロバイダー | Cobertura コマンド |
|---|---|
VSTest を使用する coverlet.collector | dotnet test <test.csproj> --collect:"XPlat Code Coverage" --results-directory <results-dir> |
.NET 9 ブリッジ MTP を使用する Microsoft.Testing.Extensions.CodeCoverage | dotnet test <test.csproj> -- --coverage --coverage-output-format cobertura --coverage-output <output-path> |
.NET 10 以降のネイティブ MTP を使用する Microsoft.Testing.Extensions.CodeCoverage | dotnet test --project <test.csproj> --coverage --coverage-output-format cobertura --coverage-output <output-path> |
プロジェクトで定義されている場合は、リポジトリ独自の同等のコマンドを使用してください。コレクターが GUID のサブフォルダーを作成する場合は、結果ディレクトリを再帰的に検索してください。変換ツールがない場合、汎用のバイナリ .coverage コマンドで代用しないでください。
最初の復元、コンパイル、テスト、またはコレクターの失敗で処理を止めないでください。失敗したレイヤーを分類し、コマンドが出力したすべてのレポートを調べ、永続的な変更を伴わない再試行をすべて尽くしてから、入力を求めてください。安全な再試行には、ソースやマニフェストを変更しないコマンドライン MSBuild プロパティや、すでにインストール済みまたはリポジトリから提供されている代替コレクターの使用が含まれます。元に戻せるコマンドライン設定によって同じソースをコンパイルできる場合、単純なソースエラーは収集を妨げる要因であり、最終的な分析ではありません。空の Cobertura ファイルを、正常な代替手段として扱ってはいけません。
クラシックな非 SDK プロジェクト(ToolsVersion、明示的なコンパイル項目、または packages.config を使用するプロジェクト)では、Cobertura を出力する、リポジトリから提供されているカバレッジコマンドだけを使用してください。そのようなコマンドがない場合は、Cobertura XML を依頼して停止してください。プロジェクトの移行や SDK スタイルのプロバイダーの追加は行わないでください。CRAP スコアには、必ず実際のカバレッジデータが必要です。
カバレッジを推測しない
推測したカバレッジは誤った CRAP スコアにつながり、回答しないよりも悪い結果になります。 リポジトリにカバレッジコマンドも既存レポートもないクラシックなプロジェクトでは、ここで停止して Cobertura を依頼してください。
ユーザーが依存関係やツールの変更を明示的に許可していない限り、カバレッジパッケージの追加、プロジェクトマニフェストの変更、グローバルツールのインストールは行わないでください。リポジトリに使用可能なプロバイダーまたは変換ツールがない場合は、必要な前提条件をそのまま報告し、run-tests で確認した互換性のあるコマンド形式を示してから停止してください。既存のバイナリ .coverage レポートがある場合は、すでにインストール済みまたはリポジトリから提供されている変換ツールだけを使用して変換してください。該当するツールがない場合は、許可または Cobertura 形式でのエクスポートを依頼してください。テストが失敗を伴いながらも実行され、有効なカバレッジが出力された場合は、そのデータを使用して処理を続け、失敗について注記してください。必要であれば複雑度のみを報告しても構いませんが、カバレッジを仮定して算出した CRAP スコアは決して公表しないでください。
レポートを使用する前に、パース可能であること、少なくとも 1 つのクラスとメソッドが含まれていること、要求された対象が含まれていることを確認してください。空のレポートや対象を含まないレポートは、カバレッジ 0% ではなく、収集またはフィルタリングの失敗です。可能であればカバレッジを再生成し、それができない場合は CRAP スコアを公表せずに停止してください。
ユーザーが既存のレポートを提供した場合は、再生成していないことを明記してください。この分析でリポジトリのカバレッジコマンドを実行して出所を確認できていない限り、そのデータを最新のものとして説明しないでください。
ステップ 2:サイクロマティック複雑度の計算
リポジトリで提供されているコードメトリクスレポート、またはレポートが現在のソースに対応している場合は Cobertura のメソッドの complexity 属性から取得した、機械生成のメソッドごとの複雑度を優先してください。Microsoft.CodeAnalysis.Metrics を使うと、msbuild /t:Metrics によってメソッドレベルの CyclomaticComplexity データを生成できますが、ユーザーの承認なしにパッケージを追加したり、プロジェクトを変更したりしないでください。
機械生成のメトリクスがない場合は、現在の対象ソースを分析し、結果が手動で数えた複雑度であることを明記してください。次の判定ポイントを数えます(それぞれ、基本複雑度の 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) |
||(OR) | if (a || b) |
?? | value ?? fallback |
?. | obj?.Method() |
? :(三項演算子) | x > 0 ? a : b |
| パターンマッチのアーム | x is > 0 and < 10 |
すべてのメソッドの基本複雑度は 1 です。判定ポイントごとに 1 を加算します。
手動で数える場合はソースファイルを読み、構文ごとの内訳を報告してください。また、ソースコメントを根拠として使用しないでください。引数や戻り式の内部にある演算子も含め、すべての出現箇所を数えます。競合があると判断する前に、&&、||、??、?.、三項演算子、switch/パターンのアームを重点的に再確認してください。提供されたレポートが対象のメソッドに対応し、慎重に再集計した結果とも一致する場合は、そのメトリクスを明確に採用してください。実際に不一致が残る場合は、両方の情報源を明記してください。ユーザーがそのレポートを明示的に使用するよう求めた場合は、その機械生成メトリクスを使って主要な CRAP 結果を計算し、手動の集計は結果を保留する理由とせず、注意事項として示してください。
ステップ 3:Cobertura XML からメソッドごとのカバレッジを抽出する
Cobertura XML を解析し、対象の <class> 要素の下にある各メソッドの line-rate 属性を取得してください。メソッドレベルで line-rate が利用できない場合は、<lines> 要素から次のように計算します。
$$\text{cov}(m) = \frac{\text{hits} > 0 の行数}{\text{行の総数}}$$
Cobertura ではメソッド名がソースと異なる場合があります(非同期メソッド、ラムダなど)。名前が一致しない場合は行範囲を使って対応付けてください。
line-rate と <lines> の両方がある場合は、ヒット数の比率を再計算して比較してください。通常のレポート上の丸め誤差(1 パーセントポイント)までしか許容しません。それを超える差がある場合、レポートには矛盾があります。CRAP を計算せずにレポートを再生成するか、矛盾を報告して処理を停止してください。期待するスコアが得られるほうを黙って選んではいけません。
ステップ 4:CRAP スコアを計算する
対象の各メソッドについて、次の式を適用してください。
$$\text{CRAP}(m) = \text{comp}(m)^2 \times (1 - \text{cov}(m))^3 + \text{comp}(m)$$
計算機またはスクリプトを使って計算し、代入した複雑度とカバレッジを示してください。式を暗算で計算してはいけません。名前を指定されたメソッドには直接回答し、クラスまたはファイルが対象として指定された場合に限り、無関係なメソッドも分析してください。求められた結果が得られたら、ユーザーから求められていない限り、仮定に基づくリファクタリング後のスコアやカバレッジ目標を付け加えないでください。数値例もすべて、計算機またはスクリプトから得たものでなければなりません。
ステップ 5:結果を提示する
CRAP の高い順に並べた表を提示してください。
| Method | Complexity | Coverage | CRAP Score | Risk |
|---------------------------------|------------|----------|------------|----------|
| OrderService.ProcessOrder | 10 | 45% | 26.6 | High |
| OrderService.ValidateItems | 8 | 90% | 8.1 | Moderate |
| OrderService.CalculateTotal | 3 | 100% | 3.0 | Low |
以下を含めてください。
- 概要:分析したメソッドの総数、各リスクカテゴリのメソッド数
- 主な問題箇所:CRAP > 30 のメソッドと、具体的な改善提案
- すぐに効果が出る改善:複雑度が高く、カバレッジを少し改善するだけでスコアが大きく下がるメソッド
ステップ 6:実行可能な推奨事項を提示する
CRAP 値が高いメソッドについて、次のいずれか、または両方を提案します。
- テストを追加する -- カバーされていない分岐を特定し、具体的なテストケースを提案します
- 複雑さを軽減する -- 深くネストしたロジックに対して、メソッド抽出によるリファクタリングを提案します
メソッドの CRAP 値をしきい値の 15 未満にするために必要なカバレッジを計算します。
$$\text{cov}_{\text{needed}} = 1 - \left(\frac{15 - \text{comp}}{\text{comp}^2}\right)^{1/3}$$
この式は comp < 15 の場合にのみ適用されます。comp >= 15 の場合、カバレッジ 100% における最小の CRAP 値は comp 自体であり、すでにしきい値以上です。この場合、カバレッジだけでは CRAP 値をしきい値未満にできません。まずメソッドをリファクタリングして、サイクロマティック複雑度を下げる必要があります。
次の形式で報告します:"ProcessOrder(複雑度 10)の CRAP 値を 15 未満にするには、カバレッジを 45% から 63.2% より高くする必要があります(整数のパーセンテージで報告する場合は、少なくとも 64%)。" 複雑度だけでしきい値を超えるメソッドについては、"ComplexMethod(複雑度 18)は、テストだけでは CRAP < 15 にできません。サブメソッドを抽出して複雑度を下げてください。" と報告します。
検証
- カバレッジデータが正常に収集されたことを確認します(Cobertura XML が存在し、データが含まれていること)
- 対象のメソッドが存在することを確認します。存在しないことは、カバレッジが 0% である証拠にはなりません
- すべてのカバレッジ値がその XML に基づいていることを確認します。推定値、仮定値、ソースコードのコメントから導いた値は使いません
- メソッドの
line-rateと行ヒット率の両方がある場合は、相互に照合します - カバレッジデータ内のメソッド名がソースコードと一致することを確認します
- CRAP 値を計算機またはスクリプトの出力で確認します
- カバレッジが 100% のメソッドの CRAP 値が、その複雑度と正確に一致することを確認します
よくある落とし穴
- 収集に失敗したときにカバレッジを推定する:決して行わないでください。算出される CRAP 値が、重要な方向に誤ってしまいます。
run-testsで確認したリポジトリ互換の Cobertura 生成手順を使い、代わりに障害要因を報告してください。 - 空のレポートや見つからないメソッドをカバレッジ 0% とみなす:これは収集、フィルタリング、またはメソッドのマッピングの失敗です。値を捏造してはいけません。
- 矛盾する Cobertura フィールドをそのまま信頼する:
line-rateと行ヒット率を比較し、丸め誤差を超える不一致があれば処理を中止します。 - ソース内の古い複雑度コメントを信頼する:現在のコードからサイクロマティック複雑度を計算します。以前の作成者が残した
// complexity: 7というコメントは証拠になりません。 - CRAP 値を暗算する:計算機またはスクリプトを使い、代入した値を示します。
- コレクターのエラーを回避するためにツールを変更する:リポジトリ互換のコマンドの失敗と不足している前提条件を報告します。明示的な許可なしに、グローバルなコレクターをインストールしたり、マニフェストを編集したりしてはいけません。
- 古いカバレッジデータ:ユーザーが最新の結果を求めている場合、またはソースコード/バイナリが変更された場合は、再生成します。それ以外の場合は、提供されたレポートを再生成していないことを明記します。
- メソッド名の不一致:Cobertura XML では、非同期メソッド、ラムダ式、ローカル関数に対して、名前がマングルされたものやコンパイラーが生成した名前が使われている場合があります。名前が一致しない場合は、行番号の範囲で照合します。
- 生成コード:明示的に要求されない限り、自動生成ファイル(例:
*.Designer.cs、*.g.cs)を分析対象から除外します。