コンテンツにスキップ

Code · メトリクス

Code ファミリには 7 つのメトリクスがあります。どれも AI 向けに作ったものではなく、何十年もの文献に裏付けられた、昔からあるコードの健全性のシグナルです。heal が加えるのは、それぞれを、対象のコードベース自身の分布に合わせて calibration することです。200 行のスクリプトと 20 万行のサービスでは、同じ値でも判定が変わります。

すべての Finding は Critical、High、Medium、Ok のどれかになります。判定は次の 2 段階で、この順に行います。

  1. 絶対値のフロア(文献に基づく): value ≥ floor_critical なら Critical、value < floor_ok なら Ok。全体が荒れたコードベースでも最悪の箇所は赤のまま残り、きれいなコードベースはパーセンタイルの段階から卒業できます。
  2. コードベースのパーセンタイル(2 つのフロアのあいだ): ≥ p95 なら Critical、≥ p90 なら High、≥ p75 なら Medium、それ以外は Ok。

パーセンタイルの区切りは .heal/calibration.toml に入っています(heal calibrate で作り直します)。フロアは、メトリクスごとに設定で上書きできます。設定 を参照してください。

heal status は、Ok 以外のすべての Finding を、[policy.drain] に従って 3 つの Tier に分けます。

  • T0 — 解消キュー(既定 ["critical:hotspot"])— 必ず直す一覧。/heal:refactor はここから先に提案します。
  • T1 — 解消すべきもの(既定 ["critical", "high:hotspot"])— 別に表示しますが、自動では解消の対象にしません。
  • Advisory — それ以外の、Ok より上のもの。--all を付けたときだけ表示します。

T0 は目標、T1 は日ごろの手入れ、Advisory は手が空いたときに見るもの、という位置づけです。

「このコードベースは何でできているか?」

言語ごとのコード行、コメント行、空行の数を tokei で数えます。ほかのメトリクスは、ここでの言語の判定に頼っています。Markdown と Org は主な言語の判定から外すので、ドキュメントの多いリポジトリでも、実装に使っている言語が主な言語になります。常に有効で、Severity はありません。

「どの関数が追いにくいか?」

関数ごとの 2 つのメトリクスを、1 回の走査で計算します。

  • CCN(循環的複雑度)— McCabe の、分岐の数。
  • Cognitive Complexity — Sonar の、読みやすさのメトリクス。ネストが深いほど重く数えます。

どちらも、文献に基づくフロア(CCN は McCabe、Cognitive は SonarQube)を使って別々に calibration します。この 2 つは代理指標です。floor_ok のフロアによって、きれいなコードベースは段階から卒業できます。

対応言語: TypeScript / JavaScript / Python / Go / Scala / Rust。

「何が動いているか?」

直近 since_days(既定 90 日)の期間に、各ファイルに入ったコミットの数です。first-parent の履歴だけを数えます。Churn そのものには Severity がなく、Hotspot と post-commit の通知の材料になります。

Change Coupling — 一緒に動くファイル

Section titled “Change Coupling — 一緒に動くファイル”

「どのファイルどうしが、暗黙のうちに頼り合っているか?」

各コミットで触られたファイルの組を「一緒に現れた」ものとして数え、ペアごとの回数から、import の関係には表れない依存を見つけます。ペアは Symmetric(互いに一緒に変わる。最も強いシグナル)と OneWay に分類します。

結合のように見えて実はそうではないペアは、自動で取り除きます。lockfile の更新、生成されたコード、mod.rs とその隣のファイルなどです。テストとソース、ドキュメントとソースのペアは、既定では Advisory に下げます。[features.test] が有効なら、ずれていくテストのペアを change_coupling.drift として格上げし直します(Test › メトリクス を参照)。

Duplication — コピーされたブロック

Section titled “Duplication — コピーされたブロック”

「重複はどこにあるか?」

同じトークンが長く続く箇所(Type-1 のクローン)を、min_tokens(既定 50)の幅の窓で構文木をたどって見つけます。整形し直してもクローンは隠れませんが、変数名を変えると別物として扱われます。

[features.docs] が有効なら、Markdown / RST のファイルにも同じ検出を並行して行います。Docs › メトリクス を参照してください。

対応言語: Complexity と同じです。

「どのクラスが機械的に分けられるか?」

heal はクラスごとにグラフを作ります。同じフィールドを参照するメソッドどうし、呼び合うメソッドどうしを線でつなぎ、つながったまとまりの数を LCOM の値とします。cluster_count ≥ 2 なら、そのクラスは分けられる関心事を抱えていて、クラスの抽出(Extract Class)の候補です。

今の構文ベースの解析には、見えない部分があることが分かっています(継承、動的なプロパティアクセスなど)。表示されたクラスは、自動で決めてよいものではなく、人が見直す候補として扱ってください。

対応言語: TypeScript / JavaScript / Python / Rust のクラスの範囲。(Go にはクラスの範囲がなく、Scala は LSP を使う解析を待っています。)

Hotspot は churn と複雑度を掛け合わせ、読みにくく、しかもよく書き換えられるファイルを浮かび上がらせます。有限の候補が 5 件以上あれば、フラグを立てるには p90 と Code のフロア(22)の両方を超える必要があります。候補が 1〜4 件のときは、絶対値のフロアだけを使います。有限でないスコアにはフラグを立てません。ファイルごとの 🔥 は、そのファイルのほかの Finding に付く装飾です。そのため Critical 🔥、High 🔥、Medium 🔥、さらには Ok 🔥 もありえます。

誰も触らない複雑なファイルは負債です。チームが 1 日おきに書き換えている複雑なファイルは、もっと早く確かめる価値があります。既定の解消キュー (critical:hotspot) は、まさにその交わりです。Hotspot が、heal の出すシグナルの中でいちばん行動につなげやすいのはこのためです。

Code の同じ Tier と Severity の中では、hotspot_score の高いものを先に並べます。これは優先順位を決めるための目安で、欠陥が起きる確率でも、ある修正の効果がより大きいことの保証でもありません。

「Ok 🔥」のもの、つまりよく触られているのに Severity の付く Finding がないファイルは、heal status --all で専用のセクションに表示されます。「なぜまだここを編集し続けているのか」を考える候補です。

もっと詳しい理由は コンセプト › Hotspot を参照してください。