本当に効くのか
「hotspot から直せば投資対効果が最大」は仮説です。このページは、それを反証しようとした記録です。対象は誰でも確認できる公開リポジトリです。
条件
成熟した TypeScript リポジトリ 8 件、計 202,604 コミット。
| リポジトリ | 履歴 | ファイル | test 期間の fix 対象 |
|---|---|---|---|
| vuejs/core | 2018– | 478 | 185 |
| jestjs/jest | 2014– | 931 | 111 |
| vitejs/vite | 2020– | 560 | 220 |
| microsoft/playwright | 2019– | 1398 | 657 |
| nrwl/nx | 2017– | 4712 | 2200 |
| angular/angular | 2014– | 5604 | 371 |
| nestjs/nest | 2017– | 1676 | 21(除外) |
| storybookjs/storybook | 2016– | 3986 | 22(除外) |
手法: 分割日以前の 12 か月(dowsing の既定期間)で学習し、直後の 12 か月で評価します。この分割が重要で、同一期間で測ると fix 自身が churn に入り、指標が自分の入力を予測しているだけになります。
欠陥ラベルは、conventional commit の fix: 接頭辞 かつ issue 参照を要求しました。nest と storybook は評価から除外しています —— どちらも squash merge で規約が守られておらず、厳格なラベルでは fix 対象が 30 件を切ったためです。これは結果ではなく制約です。
負ける場所: 全体の順位
露出量の交絡を除くため「test 期間に触られたファイル」を母集団とし、AUC で並べます。
| リポジトリ | hotspot | churn | LoC |
|---|---|---|---|
| vuejs/core | 0.641 | 0.589 | 0.685 |
| jestjs/jest | 0.605 | 0.594 | 0.625 |
| vitejs/vite | 0.679 | 0.648 | 0.719 |
| microsoft/playwright | 0.637 | 0.636 | 0.722 |
| nrwl/nx | 0.641 | 0.629 | 0.684 |
| angular/angular | 0.700 | 0.674 | 0.724 |
8 件中 7 件で、LoC 単独が hotspot を上回りました。 欠陥予測の研究ではファイルサイズが「打ち破れない基準線」として繰り返し報告されており、既知の現象です。train 期間を 20 年に広げても、ラベルを緩めても変わりませんでした。
リポジトリ全体の順位しか見ないのであれば、wc -l は十分に競争力のある道具であり、dowsing を入れる理由は薄いということです。
勝つ場所: 上位
しかし AUC は順序全体を測る指標で、実際の使われ方ではありません。実際には「上位 10〜20 件を見る」ので、そこを測ります。基準率がリポジトリごとに 0.4〜41% と大きく違うため、リフト(基準率の何倍当てたか)で揃えます。
| リポジトリ | 基準率 | hotspot | churn | LoC |
|---|---|---|---|---|
| vuejs/core | 38.7% | 2.3x | 1.9x | 2.2x |
| jestjs/jest | 11.9% | 4.2x | 4.6x | 4.2x |
| vitejs/vite | 37.1% | 2.6x | 2.4x | 2.7x |
| microsoft/playwright | 37.8% | 2.6x | 2.6x | 2.4x |
| nrwl/nx | 41.2% | 2.3x | 2.4x | 1.2x |
| angular/angular | 5.7% | 10.4x | 10.4x | 5.2x |
| 平均 | 4.1x | 4.1x | 3.0x |
上位 20 件では hotspot が LoC を明確に上回ります(4.1x vs 3.0x)。差が最大なのは nx(2.3x vs 1.2x —— LoC はほぼランダム)と angular(10.4x vs 5.2x)です。サイズは「大きいが安定したファイル」を上位に積み上げますが、変更頻度を掛けるとそれが落ちます。
相関は全体にあり、価値は上位にあります。 だから評価指標は AUC ではなく precision@K であるべきです。
複雑度は何に効いているのか
このデータでは churn 単独が hotspot と同点(4.1x vs 4.1x)で、複雑度を掛ける意味が観測できません。これは中心となる式そのものを否定します。
原因は正例の定義でした。何を欠陥と数えるかを変えると:
| 正例の定義 | hotspot | churn | LoC |
|---|---|---|---|
| 1 回以上 fix された | 4.1x | 4.1x | 3.0x |
| 3 回以上 fix された(慢性) | 14.4x | 12.2x | 7.7x |
| 5 回以上 fix された | 16.3x | 15.5x | 8.4x |
慢性ファイルでは、5 件中 4 件で hotspot が churn を上回ります(vue +2.0 / vite +1.4 / playwright +0.4 / angular +8.5、負けは nx の −1.0 のみ)。
読み方はこうです。複雑度は「壊れるかどうか」ではなく「一度で直しきれるかどうか」に効いています。 単純なファイルは 1 回直して終わり、複雑なファイルは同じ場所を何度も直すことになる。これは churn × complexity という式が何のためにあるかについての、検証可能な具体的主張であり、このツールが存在意義を持つ条件でもあります。
使うときに何を意味するか
- 順位ではなく上位を読む。 このデータが支持するのは「上位 10〜20 件は見る価値がある」であって、「400 番目は 380 番目より安全」ではありません。
- 狙うのは慢性ファイル。 繰り返し直されていて、まだ直され続けているなら、そこで複雑度がコストになっています。1 回壊れただけのファイルは、このツールを使う理由になりません。
- 全体の相関は導入理由になりません。 それは LoC でほぼ得られます。LoC にできないのは、何度も戻ってくるファイルを見つけることと、依存関係がないのに一緒に変わるパッケージを見つけることです。
この検証の限界
- 採点対象は 6 リポジトリで、いずれも人気の OSS ライブラリ・フレームワークです。アプリケーションのコードベースや非公開のモノレポでは違う挙動になる可能性があります。
fix:+ issue 参照は「ここに欠陥があった」の代理指標にすぎません。規約に従わずに直された欠陥は数えられず、欠陥でない fix は数えてしまいます。- 分割日はリポジトリごとに 1 つだけで、複数の期間での交差検証はしていません。
- ランキングに従って行動した結果、実際に改善したかは、ここでは一切測っていません。それには変更の前後を測る必要があり、
dowsing diffがその役目です。
dowsing の閾値と重みはすべて仮説であり、そう明示しています。再現して違う答えが出たなら、それは有用な結果です —— issue を立ててください。