誰も口にしたがらない数字
非公式なコードレビューは、レビュー対象コードに存在する欠陥の15〜30%を検出する。これは意見ではない。40年以上、複数の企業、数十の研究を通じて再現された知見だ。
マイケル・フェイガンは1976年にIBMでこれを記録した。1987年のAT&Tベル研究所の研究では20%が検出された。1996年のHPの研究では25%が検出された。2013年のMicrosoft Researchの現代のコードレビューに関する論文でも、ほぼ同じ範囲が示された。ツールはパンチカードからGitHubへと変わったが、人間の性能曲線は動かなかった。
もしチームがプルリクエストレビューを品質の最後の砦だと思っているなら、データはこう告げている。検出できているのは約4分の1のバグであり、残りの3分の4はリリースされている、と。
数字の出どころ
フェイガンの当初の方法論は単純かつ過酷だった。彼はコードに既知の欠陥を注入し、レビュープロセスを実行して、レビュアーがいくつ見つけたかを数えた。次に、テストや本番インシデント、顧客からの報告を通じてチームが最終的に見つけた欠陥の総数と比較した。レビューで見つかった数を総欠陥数で割った比率が、除去率(removal rate)となった。
重要な洞察は、分母が重要だということだ。10個の欠陥を見つけたレビューは良さそうに聞こえるが、そのファイルに50個あったと知れば話は別だ。フェイガンは分母全体を測定した。現在のほとんどのチームはそうしていない。
その後の研究も同様の設計を用いた。研究者は欠陥を埋め込み、レビュー方式を比較したり、本番環境から欠陥を逆追跡してどこで捕捉できたかを調べたりした。結果はきわめて近い範囲に集まる。
| レビュー方式 | 欠陥除去率 | 主要研究 |
|---|---|---|
| レビューなし | 0% | ベースライン |
| 非公式 / PRレビュー | 15-30% | Fagan 1976, Porter 1995, Microsoft 2013 |
| 構造化ウォークスルー | 30-50% | Yourdon 1979, Weller 1993 |
| フェイガン・インスペクション | 60-90% | Fagan 1976, Russell 1991, NASA 2002 |
非公式レビューと構造化レビューの差は小さくない。欠陥流出率において3倍の差がある。
PRレビューがなぜこれほど性能が悪いのか
問題はレビュアーが仕事が下手だということではない。問題は、プルリクエストレビューが欠陥を見つけるよう設計されていないことだ。それは2人がコードのマージに合意できるように設計されている。
典型的なPRレビューで実際に起きていることを示す。レビュアーはdiffを開く。概要を読み、追加部分をスキャンし、テストが通るか確認し、明らかに間違っている箇所を探す。これに5〜15分かかる。そして承認する。
このプロセスは徹底性ではなく速度に最適化されている。準備時間はない。レビュアーは周囲のコードを読んでいないし、データフローを追跡していないし、変更のメンタルモデルを構築していない。彼らは画面上のdiffに反応しているだけだが、人間の脳はその形式でバグを見つけるのが苦手だ。
2015年のBacchelliとBirdによるMicrosoftでの研究では、最も一般的なレビューコメントのカテゴリは欠陥とはまったく関係ないものだった。上位カテゴリは意図に関する質問、補足の要請、改善提案だった。実際の欠陥発見はコメントの少数派だった。このツールは品質ゲートではなく、コミュニケーション・チャネルとして機能していた。
ツールが何をするか分かっていれば問題ない。別のことをすると思っていれば危険だ。
構造化レビューが異なる点
フェイガン・インスペクションやその他の構造化レビュー手法は、人ではなくプロセス設計を変えることで、より高い除去率を達成する。
最大のレバーは個人の準備だ。フェイガン・インスペクションでは、グループ討議の前に各レビュアーが資料に集中して時間を費やす。フェイガンは、準備をした査察者が、準備なしで入った査察者の約2倍の欠陥を見つけたことを発見した。PRモデルは、デフォルトで準備なしで入るモデルだ。
2番目のレバーはペースだ。フェイガンはレビュー1時間あたり100〜125行を推奨した。ほとんどのPRレビュアーはその10倍の速度で処理する。速度は検出を殺す。脳は実際にあるものを読むのではなく、期待されるパターンを補完してしまう。
3番目のレバーは焦点だ。フェイガンの会議には単一の目的がある。欠陥を記録することだ。設計論争はない。解決案の提案はない。スタイル議論はない。PRスレッドは日常的にアーキテクチャ意見に逸脱し、それはnull dereferenceを見つけられたかもしれない同じ認知予算を消費する。
4番目のレバーは読み手の役割だ。コードを書いていない人がそれを声に出して言い換えることで、グループはスキャン速度ではなく理解速度で処理することを強いられる。画面上のdiffは退屈な部分を飛ばさせる。話している人は飛ばさない。
コスト論は逆だ
構造化レビューに対する通常の反論はコストだ。フェイガン・インスペクションは数百行のコードに対して4〜6人時を消費する。PRレビューは1人の15分だ。計算は明らかに見える。
明らかに見えて、間違っている。
フェイガンは、異なる段階で欠陥を見つけるコストも測定した。インスペクション中に見つかった欠陥のコストは、テスト中に見つかった場合の約10分の1だった。本番に流出すると比率は20〜30倍に膨れ上がった。2002年のNASAの研究では、インスペクションに費やした1時間あたり、平均33時間の後続メンテナンス作業を防止したことが判明した。
節約は目に見えない。防止したバグは測定できない。会議のコストは即座に明らかだ。そのため、組織は最終的により高くつくとデータが示していても、見えない品質より見える速度を最適化する。
データに基づく中間点
IBMの会議文化を持たなくても、大部分の利益は得られる。数字を実際に動かすプロセス特性を借りて、動かさないものは捨てればよい。
データが重要だと示しているものは以下だ。
-
準備時間。 コメントする前にレビュアーがコードに時間を費やすことを要求する。たった10分の集中読書でも、ざっと目を通すより勝る。
-
ペース制限。 重要なファイルについては、最大レビュー速度を強制する。レビュアーが500行の変更を5分で承認したら、それは勤勉ではなくデータだ。
-
欠陥のみに焦点。 スタイルやアーキテクチャのフィードバックを欠陥発見から切り離す。前者には自動フォーマッタを使う。後者には人間の注意力を割く。
-
チェックリスト。 フェイガンは、チェックリスト駆動のレビュアーが、直感駆動のレビュアーが見落とした欠陥を見つけたことを発見した。チェックリストは、専門知識が盲点を生むために存在する。
以下はGit履歴からレビューの深さを測定する軽量スクリプトだ。レビューが徹底するのに十分な時間があったかを推定する。
#!/usr/bin/env python3
"""Estimate review depth from git history."""
import subprocess
import sys
from datetime import datetime, timezone
def get_commit_info(commit_hash: str) -> dict:
"""Return author, committer, and timestamps for a commit."""
fmt = "%H|%an|%cn|%ad|%cd"
result = subprocess.run(
["git", "log", "-1", f"--format={fmt}", commit_hash],
capture_output=True,
text=True,
check=True,
)
parts = result.stdout.strip().split("|")
return {
"hash": parts[0],
"author": parts[1],
"committer": parts[2],
"author_date": datetime.strptime(parts[3], "%a %b %d %H:%M:%S %Y %z"),
"commit_date": datetime.strptime(parts[4], "%a %b %d %H:%M:%S %Y %z"),
}
def get_lines_changed(commit_hash: str) -> int:
"""Count total lines added + deleted in a commit."""
result = subprocess.run(
["git", "diff", f"{commit_hash}^", commit_hash, "--stat"],
capture_output=True,
text=True,
check=True,
)
# Last line of --stat contains totals like "3 files changed, 42 insertions(+), 7 deletions(-)"
for line in reversed(result.stdout.strip().splitlines()):
line = line.strip()
if "insertions" in line or "deletions" in line:
# Extract numbers roughly
parts = line.split(",")
total = 0
for part in parts:
digits = "".join(ch for ch in part if ch.isdigit())
if digits:
total += int(digits)
return total
return 0
def estimate_review_depth(commit_hash: str) -> dict:
"""Estimate whether a commit had time for thorough review.
Returns lines changed, time between author and commit dates
(a rough proxy for review duration), and a verdict.
"""
info = get_commit_info(commit_hash)
lines = get_lines_changed(commit_hash)
# Time between author date and commit date is a proxy for review time
# In many workflows, commit date reflects when the merge happened
review_seconds = (info["commit_date"] - info["author_date"]).total_seconds()
review_hours = review_seconds / 3600
# Fagan recommended 100-125 lines/hour for thorough review
fagan_rate = 125
needed_hours = lines / fagan_rate if lines else 0
verdict = "insufficient"
if review_hours >= needed_hours:
verdict = "adequate"
if review_hours >= needed_hours * 2:
verdict = "thorough"
return {
"hash": commit_hash[:8],
"lines": lines,
"review_hours": round(review_hours, 2),
"needed_hours": round(needed_hours, 2),
"verdict": verdict,
}
if __name__ == "__main__":
commit = sys.argv[1] if len(sys.argv) > 1 else "HEAD"
result = estimate_review_depth(commit)
print(f"Commit: {result['hash']}")
print(f"Lines: {result['lines']}")
print(f"Review time: {result['review_hours']} hours")
print(f"Fagan time: {result['needed_hours']} hours")
print(f"Verdict: {result['verdict']}")
review_depth.pyとして保存し、実行する。
python review_depth.py abc1234
出力は、そのコミットがフェイガンの基準で徹底的にレビューされるのに十分な時間があったかを示す。ほとんどのコミットはinsufficientと出るだろう。それが要点だ。データは何十年も前から私たちにこれを告げてきたが、私たちはより良いパイプラインではなく、より速いパイプラインを作り続けている。
チームにとっての意味
プルリクエストレビューは無駄ではない。共有コンテキストを構築し、知識を広め、明らかなミスを捕捉する。しかしデータは、それが何をしないかについて明確だ。それは大多数の欠陥を捕捉しない。
品質戦略がPRレビューを主要フィルターとして依存しているなら、ふるいで濾しているに等しい。15〜30%の除去率は、レビュアーの失敗ではない。それはプロセスの特性だ。
この数字を上回るチームは、より賢い人材を採用するわけではない。彼らはプロセスを変える。準備時間を追加し、ペース制限を強制し、欠陥発見を設計議論から切り離し、直感が見落とす箇所に注意力を向けるチェックリストを使う。
すべてのdiffに対して完全なフェイガン・インスペクションを行う必要はない。現在のプロセスが実際に何を達成しているかを知り、それ以上を達成していると見せかけるのをやめる必要がある。
FAQ
データはプルリクエストレビューの効果について何を言っているか?
IBM、AT&T、HP、Microsoftでの複数の研究は一貫して、非公式なコードレビューがコードに存在する欠陥の15〜30%を検出すると示している。この範囲は、1970年代からGitHubベースのワークフローに関する現代の研究まで安定している。
なぜプルリクエストレビューはこれほど少ない欠陥を見つけるのか?
PRレビューは、体系的な欠陥検出ではなく、速度とマージ承認に最適化されている。レビュアーは通常、1レビューあたり5〜15分を費やし、準備時間はなく、研究が推奨する速度の10倍でコードを処理し、その形式は深い分析ではなくざっと目を通すことを促進する。
フェイガン・インスペクションのような構造化レビューはどれほど優れているか?
フェイガン・インスペクションは一貫して60〜90%の欠陥除去率を報告しており、非公式レビューよりおおよそ3〜4倍優れている。その差は、個人の準備、強制されたペース制限、役割の分離、チェックリスト駆動の焦点、そして解決策を議論するのではなく欠陥を記録する会議から生じる。
PRレビューの効果を向上させる最も安価な方法は何か?
コメントする前に個人の準備を義務付け、チームの一般的な欠陥タイプに合わせたチェックリストを使用し、スタイルフィードバックを欠陥発見から切り離し、重要なファイルのレビュー速度を制限する。準備と焦点へのわずかな変更でも、会議のオーバーヘッドを追加することなく、数字を大幅に動かすことができる。
チームの実際の欠陥除去率をどう測定するか?
レビューで見つかった欠陥と、後のテストや本番で見つかった欠陥を追跡する。早期捕捉数を総発見数で割った比率が除去率だ。ほとんどのチームはこれを追跡していないため、レビューの効果を過大評価している。各欠陥がどこで見つかったかを記録し始め、月次で比率を計算する。