メトリクスの式と根拠
dowsing のスコアはすべて公開されています。このページは式の一覧です。各数値の出典は なぜこの指標か を参照してください。
すべての閾値は仮説です
文献に基づいていますが、あなたのリポジトリに合っている保証はありません。すべて設定で変更できます。
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| 方向性(オプション)既定フィルタ:
| 項目 | 既定値 | 出典 |
|---|---|---|
| 最小リビジョン数 | 5 | Code Maat |
| 最小共有コミット | 5 | Code Maat |
| 最小結合度 | 30% | Code Maat |
| 巨大 changeset 除外 | 50 ファイル超 | CodeScene |
巨大 changeset を除外するのは、tangled commit(1 コミットに複数の関心事)が偽の結合を生むためです。churn には含めますが coupling では使いません。
degree の分母
|A| は「A が変更された全リビジョン数」です。単独変更のコミットも含みます。これを落とすと分母が縮んで degree が過大評価されます(実装時に実際にこのバグを出しました)。
隠れた結合
パッケージ依存グラフと突き合わせて分類します。
| kind | 意味 |
|---|---|
hidden | 依存グラフに辺がない = 構造に現れない結合 |
type-only | 型でしか繋がっていない(値の依存がない) |
declared-only | package.json に宣言はあるが import されていない |
obvious | 依存関係があり、共変更も自明 |
依存グラフは package.json の宣言依存と、oxc が抽出した実 import の両方を辺として持ちます。
Churn の検証
AST を正規化ハッシュ化して、リビジョン間で比較します。
fingerprint(src) = hash(AST から識別子ノードの name/value だけ除外した構造)
※ 演算子・リテラル値・修飾子は意味を持つので構造に含める| 判定 | 条件 |
|---|---|
identical | 内容が完全一致(ファイル移動・モード変更) |
format-only | AST が名前まで一致(空白・コメント・セミコロン) |
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 Complexity | cognitive > 15 | 3.0 | 理解コストの直接の代理指標 |
| Complex Method | cyclomatic > 10 | 2.5 | テストで網羅すべき経路数(McCabe) |
| Large File | LoC > 400 | 1.5 | 間接的な指標なので軽め |
| Many Arguments | args > 4 | 1.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 です。
dowsing はこれをパッケージ単位で計算します。 既存ツールはリポジトリ全体かファイル単位しか出さないため、「コードベースのどの部分が保守不能になるか」が分かりません。
前提条件
.mailmap による同一人物の統合と bot 除外を必ず先に行う必要があります。同じ人が複数のメールアドレスを使っていると bus factor を過大評価します(実際より安全に見える)。dowsing は git 履歴収集の時点でこれを済ませています。
除外されるもの
数字を信じてよいか に詳しく書いていますが、要点は次のとおりです。
| 対象 | 理由 |
|---|---|
| merge コミット | 変更の実体を持たない |
| bot コミット | ownership を歪める |
| format-only コミット | 空白だけの変更が churn を膨らませる |
.git-blame-ignore-revs 記載 | GitHub も尊重する標準 |
| 生成コード・ロックファイル | 偽 churn の代表例 |
| 巨大 changeset | tangled commit(coupling のみ除外) |
除外件数は必ず出力に表示されます。 silent に除外すると「全部見た結果」だと誤解されるためです。