Skip to content

メトリクスの式と根拠

dowse のスコアはすべて公開されています。このページは式の一覧です。各数値の出典は なぜこの指標か を参照してください。

すべての閾値は仮説です

文献に基づいていますが、あなたのリポジトリに合っている保証はありません。すべて設定で変更できます。

Hotspot

change_frequency(f) = 期間内に f を変更したコミット数(merge/bot/整形を除外後)
complexity(f)       = 選択した複雑度プロキシ(既定: 実効 LoC)
hotspot_score(f)    = rank(change_frequency) × rank(complexity)

rank はパーセンタイル正規化(0〜1)。両方が高いものだけが上位に来ます。min-max 正規化の値も内訳として保持されます。

変更が一度もないファイルの score は 0 です。複雑でも触られていないコードは投資対効果がないためです。

パッケージへの集約は 2 通りを両方出します。

hotspot_sum(pkg)          = Σ hotspot_score(f)
hotspot_loc_weighted(pkg) = Σ(hotspot_score(f) × loc(f)) / Σ loc(f)

複雑度

指標定義
実効 LoCコメント・空行を除いた行数。既定のプロキシ
Cyclomatic決定点 +1(ESLint complexity 相当)。if / ループ / case / catch / 三項演算子
Cognitiveネストにペナルティを課す(Campbell / SonarSource 仕様)。論理演算子の連なりは 1 回だけ数える

--complexity=loc|cyclomatic|cognitive で切り替えます。

Change coupling

Tornhill / Code Maat 方式(対称)を既定にしています。

degree(A,B)      = 2·|A∩B| / (|A|+|B|)     対称結合度
confidence(A⇒B)  = |A∩B| / |A|             方向性(オプション)

既定フィルタ:

項目既定値出典
最小リビジョン数5Code Maat
最小共有コミット5Code Maat
最小結合度30%Code Maat
巨大 changeset 除外50 ファイル超CodeScene

巨大 changeset を除外するのは、tangled commit(1 コミットに複数の関心事)が偽の結合を生むためです。churn には含めますが coupling では使いません。

degree の分母

|A| は「A が変更されたリビジョン数」です。単独変更のコミットも含みます。これを落とすと分母が縮んで degree が過大評価されます(実装時に実際にこのバグを出しました)。

隠れた結合

パッケージ依存グラフと突き合わせて分類します。

kind意味
hidden依存グラフに辺がない = 構造に現れない結合
type-only型でしか繋がっていない(値の依存がない)
declared-onlypackage.json に宣言はあるが import されていない
obvious依存関係があり、共変更も自明

依存グラフは package.json の宣言依存と、oxc が抽出した実 import の両方を辺として持ちます。

Churn の検証

AST を正規化ハッシュ化して、リビジョン間で比較します。

fingerprint(src) = hash(AST から識別子ノードの name/value だけ除外した構造)
                   ※ 演算子・リテラル値・修飾子は意味を持つので構造に含める
判定条件
identical内容が完全一致(ファイル移動・モード変更)
format-onlyAST が名前まで一致(空白・コメント・セミコロン)
rename-only構造ハッシュ一致かつノード数一致(識別子のリネーム)
changed上記以外
unknownパース失敗 / ノード数 < 5
effective_commits(f) = |changed| + |unknown| + 1

意図的な非対称性

unknown実変更として数えます。判定不能な変更を「偽 churn」と誤って除外する(偽陰性)ほうが、churn をやや過大評価する(偽陽性)より危険だからです。

型情報は不要で、構文 AST の比較だけで成立します。

Code Health

health(f)   = 10 − Σ_smell (weight × severity)      下限 1.0
health(pkg) = LoC 重み付き平均

severity(value, threshold) = min(1, (value − threshold) / (threshold × 2))

severity は閾値ちょうどで 0、閾値の 3 倍で 1 に飽和します。外れ値 1 つでスコアが振り切れるのを防ぐためです。

実装済みの smell と既定の重み:

smell閾値重み根拠
High Cognitive Complexitycognitive > 153.0理解コストの直接の代理指標
Complex Methodcyclomatic > 102.5テストで網羅すべき経路数(McCabe)
Large FileLoC > 4001.5間接的な指標なので軽め
Many Argumentsargs > 41.0呼び出し側との結合

重みの合計は 8.0 なので、既定では最悪でも health 2.0 が下限です(smell を増やすほど下限が下がります)。

LoC ベースラインが必ず併記されます。LoC だけで決めた場合のスコアと比べて差が小さいなら、その health は実質ファイル長の言い換えです。

Ownership / Bus factor

main developer は追加行数ベース(deep history)で決めます。blame は「最後に触った人」を著者にしてしまうためです。

minor contributor = ownership < 5% の貢献者(Bird et al.)

bus factor は Avelino et al. (ICPC 2016) の Degree-of-Authorship + 貪欲法。係数は Fritz et al. (ICSE 2010) 由来です。

DOA(d,f) = 3.293 + 1.098·FA + 0.164·DL − 0.321·ln(1 + AC)
  FA = first authorship(d が f を作成なら 1)
  DL = d 自身の f への変更回数
  AC = d 以外による f への変更回数

DOA_N(d,f) = DOA(d,f) / max_d' DOA(d',f)
author(d,f) ⇔ DOA_N > 0.75 かつ DOA ≥ 3.293

貪欲法で「担当ファイル数が最大の著者」を順に除去し、著者を持つファイルの割合が 50% を切った時点の除去人数が bus factor です。

dowse はこれをパッケージ単位で計算します。 既存ツールはリポジトリ全体かファイル単位しか出さないため、「コードベースのどの部分が保守不能になるか」が分かりません。

前提条件

.mailmap による同一人物の統合と bot 除外を必ず先に行う必要があります。同じ人が複数のメールアドレスを使っていると bus factor を過大評価します(実際より安全に見える)。dowse は git 履歴収集の時点でこれを済ませています。

除外されるもの

数字を信じてよいか に詳しく書いていますが、要点は次のとおりです。

対象理由
merge コミット変更の実体を持たない
bot コミットownership を歪める
format-only コミット空白だけの変更が churn を膨らませる
.git-blame-ignore-revs 記載GitHub も尊重する標準
生成コード・ロックファイル偽 churn の代表例
巨大 changesettangled commit(coupling のみ除外)

除外件数は必ず出力に表示されます。 silent に除外すると「全部見た結果」だと誤解されるためです。

MIT ライセンスで公開されています