簡単な答えは「いいえ」だが、詳しい答えは時間を節約する

本格的なフェイガン査読には、進行役、読み手、2〜4人の査読者、そして著者が必要だ。チームは1時間あたり125行のペースで、おおむね250行のコードを2時間かけてレビューする。小さな変更でも、8〜12人時の工数がかかるのだ。

大規模言語モデルは250行を1秒もかからず読める。チェックリストを実行し、怪しいパターンを指摘し、人間がファイルを開く前に構造化された欠陥記録を生成できる。

それがチームを代替できるという意味ではない。チームは、大規模言語モデルが宿題を終えるまで会議を始めるべきではないという意味だ。

大規模言語モデルがフェイガン査読で実際にできること

フェイガン査読には4つの明確な役割がある。それぞれの役割は、大規模言語モデルができることとできないことに対して異なる形で対応する。

進行役は査読を計画し、参加基準の適用を徹底し、会議を軌道に乗せる。大規模言語モデルはチェックリストを生成し、査読前にテストが通ることを確認し、行数に応じて所要時間を見積もれる。しかし、エンジニアが設計論争に脱線していないかを察知することはできない。進行役の役割は社会的なものであり、大規模言語モデルには社会性がない。

読み手は会議中にコードを声に出して言い換えることで、グループに素早く流し読みするのではなく、理解の速度で論理を処理させる。大規模言語モデルはコードを要約できるが、人間が速度を落とすよう強制することはできない。読み手の真の価値は社会的な強制力にある。大規模言語モデルにはその力がない。

査読者はチェックリストと領域知識に照らしてコードを調べ、欠陥を見つける。ここが大規模言語モデルが最も役立つ場所だ。null参照の有無、リソース漏洩、未処理のエラーパス、境界値の誤りなどを、人間より速く検出できる。疲れない。安全に見えるからと退屈な補助関数を見逃すこともない。

著者は質問に答え、欠陥を修正する。大規模言語モデルは著者を代替できない。コードを書いたのではないし、意図を確認できないからだ。

したがって現実的な答えは「代替」ではなく「役割の移譲」だ。大規模言語モデルは、人間が入室する前に査読者の機械的な部分をこなし尽くす、疲れ知らずの事前査読者として機能できる。

大規模言語モデルが本当に役立つ場所:準備段階

フェイガン査読で最も重要な段階は準備だ。すべての査読者は会議前に個別に資料をレビューする。調査によると、準備で見つかる欠陥は全体のおおむね半分を占める。大規模言語モデルがこの段階を強化できれば、人間の会議は短く鋭くなる。

実用的なアプローチを紹介しよう。大規模言語モデルにコードと、使用言語に合わせたチェックリスト、そして構造化された出力を強制するプロンプトを渡す。大規模言語モデルの出力は、人間の査読者にとっての出発点として使い、代替とはしない。

プロンプトはモデルより重要だ。「このコードをレビューして」といった曖昧な依頼では、ありふれた一般論が返ってくるだけだ。チェックリストと出力形式を備えた構造化されたプロンプトであれば、実践的な欠陥が得られる。

以下は、OpenAI互換APIを使って大規模言語モデルによる事前査読を実行するPythonスクリプトだ。ソースファイルを読み込み、フェイガン式チェックリストを適用し、構造化された欠陥記録を出力する。

#!/usr/bin/env python3
"""
LLM pre-inspection for Fagan-style review.
Produces structured defect log from a checklist.
"""

import argparse
import os
from pathlib import Path


def build_prompt(code: str, filepath: str, language: str) -> str:
    checklist = {
        "python": [
            "Missing null/None checks before dereferencing",
            "Unclosed file handles or missing context managers",
            "Bare except clauses that swallow exceptions",
            "Mutable default arguments in function signatures",
            "Race conditions in shared mutable state",
            "Missing input validation on public functions",
        ],
        "typescript": [
            "Non-null assertions (e.g., !) without justification",
            "Missing await on async calls",
            "Any types that should be narrowed",
            "Unhandled promise rejections",
            "Missing input validation on public functions",
        ],
        "rust": [
            "Unwrap or expect without comment explaining invariants",
            "Missing error propagation where Result is returned",
            "Unsafe blocks without safety comments",
            "Clone on large data structures in hot paths",
            "Missing input validation on public functions",
        ],
    }

    items = checklist.get(language, checklist["python"])
    checklist_text = "\n".join(f"  - {item}" for item in items)

    return (
        "You are a Fagan inspection assistant. Review the following code "
        "against the checklist. For each defect found, output a JSON object "
        "with keys: line, category, severity (minor/major/critical), description. "
        "If no defect is found for a checklist item, omit it. "
        "Be specific about line numbers and exact variable names. "
        "Do not suggest fixes. Fagan inspections only log defects, they do not solve them.\n\n"
        f"File: {filepath}\n"
        f"Language: {language}\n\n"
        "Checklist:\n"
        f"{checklist_text}\n\n"
        "Code:\n```\n"
        f"{code}\n"
        "```\n\n"
        "Output strict JSON array of defects only. No markdown, no preamble."
    )


def inspect_file(filepath: str, api_key: str, base_url: str) -> list:
    """Run LLM pre-inspection and return parsed defect list."""
    from openai import OpenAI

    path = Path(filepath)
    code = path.read_text()

    # Infer language from extension
    ext_map = {
        ".py": "python",
        ".ts": "typescript",
        ".tsx": "typescript",
        ".rs": "rust",
    }
    language = ext_map.get(path.suffix, "python")

    client = OpenAI(api_key=api_key, base_url=base_url)
    response = client.chat.completions.create(
        model=os.environ.get("INSPECTION_MODEL", "gpt-4.1-mini"),
        messages=[
            {"role": "system", "content": "You are a precise code inspection tool."},
            {"role": "user", "content": build_prompt(code, filepath, language)},
        ],
        temperature=0.2,
        max_tokens=2048,
    )

    raw = response.choices[0].message.content.strip()

    # Some models wrap JSON in markdown fences. Strip them.
    if raw.startswith("```json"):
        raw = raw[7:]
    if raw.startswith("```"):
        raw = raw[3:]
    if raw.endswith("```"):
        raw = raw[:-3]
    raw = raw.strip()

    import json
    try:
        defects = json.loads(raw)
        if not isinstance(defects, list):
            return []
        return defects
    except json.JSONDecodeError:
        print("Warning: LLM returned non-JSON output:")
        print(raw[:500])
        return []


def main():
    parser = argparse.ArgumentParser(description="LLM pre-inspection for Fagan review")
    parser.add_argument("files", nargs="+", help="Source files to pre-inspect")
    parser.add_argument("--api-key", default=os.environ.get("OPENAI_API_KEY"))
    parser.add_argument(
        "--base-url",
        default=os.environ.get("OPENAI_BASE_URL", "https://api.openai.com/v1"),
    )
    args = parser.parse_args()

    if not args.api_key:
        raise SystemExit("Error: set OPENAI_API_KEY or pass --api-key")

    all_defects = []
    for f in args.files:
        defects = inspect_file(f, args.api_key, args.base_url)
        all_defects.extend({"file": f, **d} for d in defects)

    if not all_defects:
        print("No defects flagged by LLM pre-inspection.")
        return

    print(f"LLM pre-inspection found {len(all_defects)} defect(s):\n")
    for d in all_defects:
        print(
            f"[{d['severity'].upper()}] {d['file']}:{d.get('line', '?')} "
            f"({d['category']}) {d['description']}"
        )


if __name__ == "__main__":
    main()

OpenAI SDKを pip install openai でインストールし、APIキーを設定して python inspect.py src/auth.py を実行すればよい。このスクリプトは、人間の査読者が出発点のチェックリストとして使える構造化された欠陥記録を出力する。

これは査読そのものではない。準備なのだ。欠陥には依然として人間の判断が必要だ。

大規模言語モデルが見落とすもの:本当に金を食う欠陥

最も高価な欠陥は構文エラーではない。誤った前提だ。

大規模言語モデルはnullチェックの漏れを見つけるだろう。しかし、上流サービスがそのフィールドを保証しているためnullケースは起こり得ないはずであり、真のバグは3週間前にチームが契約を変更したのに下流のコードを更新し忘れたことだ、ということには気づかない。

大規模言語モデルは未処理の例外を指摘するだろう。しかし、著者がデータベーストランザクションが自動的にロールバックされると想定したため例外ハンドラが欠けており、その想定がチームが使用している特定の分離レベルでは誤りであることには気づかない。

これらは意図レベルの欠陥だ。領域知識、履歴的文脈、そして「著者が何を真だと思い込んでいるのか?」と問う能力を必要とする。大規模言語モデルは、チームの文書化されていない前提にアクセスできない。前四半期のアーキテクチャ判断の記憶もない。要件文書を見て、コードが誤った要件を実装していることに気づくこともできない。

フェイガン査読は、まさにこうした欠陥を捕捉するために設計された。人間の査読者は異なる思考モデルと異なる文脈を持ち込む。彼らがコードが何をすべきかで意見を食い違わせたとき、その前提の隙間に本物のバグが潜んでいる。

大規模言語モデルは一つの思考モデルしか持たず、しかもそれは公開コードで訓練されたもので、あなたのコードベースではない。

本当のトレードオフ:網羅性対理解度

大規模言語モデルによる事前査読は網羅性を与える。すべての行を読み、すべての関数を調べ、Slackの通知で気を散らされることもない。完璧な狭義の査読者だ。

人間の査読は理解度を与える。コードを仕様に、仕様をデータベーススキーマに、スキーマを前回のスプリント計画会議で変更された業務ルールに結びつける。コードの外側に存在する欠陥を見つける唯一の方法だ。

トレードオフは人間対機械ではない。深さ対広さなのだ。

査読チームを大規模言語モデルに置き換えれば、構文レベルの欠陥はより多く見つかり、意図レベルの欠陥はより多く見逃される。純結果は、どの種類のバグがあなたを苦しめているかに依存する。本番インシデントの多くがnull参照やリソース漏洩なら、大規模言語モデルは役立つ。誤った業務ロジックや見落とされた境界ケースが多いなら、大規模言語モデルは虚勢を張らせるだけだ。

実際にハイブリッド査読を運用する方法

以下は、大規模言語モデルをその得意分野で活用し、人間だけができることは人間のチームに委ねる実用的なワークフローだ。

会議前に、大規模言語モデルの事前査読スクリプトをコードに対して実行する。各査読者に、その出力と元のコードを渡す。個別の準備の際に、大規模言語モデルの各指摘を検証または却下することを義務付ける。これにより、見逃していたであろう行を調べさせることができる。

会議中も、読み手は依然としてコードを声に出して言い換える。査読者も依然として問題を提起する。しかし今や、機械的な欠陥の精選されたリストから出発できる。会議時間は2時間から90分に短縮できるか、同じ時間枠でより多くのコードをレビューできる。

進行役には新たな責任が加わる:大規模言語モデルの精度を追跡すること。大規模言語モデルが指摘した欠陥のうち実在した数、誤検出数、大規模言語モデルが見逃した人間のみの欠陥数を記録する。3〜4回の査読を経れば、チェックリストのプロンプトに微調整が必要かどうかが分かるだろう。

これは、1990年代にNASAがツール支援査読を実験した際に用いたのと同じプロセスだ。自動チェッカーが簡単な欠陥を見つけ、難しいものは人間に残した。人間は、簡単な欠陥を探して精神的に疲弊していなかったため、より良いパフォーマンスを示した。

結論

大規模言語モデルは査読チーム全体を代替できない。なぜなら、査読は単なるコード読みではないからだ。それは、著者が想定したものとコードが実際に行っていることの間の齟齬を浮き彫りにするよう設計された、構造化された社会的プロセスなのだ。

大規模言語モデルにできるのは、機械的な負荷を取り除くことで人間チームをより効果的にすることだ。第5の役割、すなわち人間が到着する前に戦場を整える疲れ知らずの事前査読者として機能できる。

査読が高価すぎるために飛ばしているのであれば、大規模言語モデルはそれを無料にはしない。短くはする。それで十分なことも多い。

よくある質問

フェイガン査読とは何ですか?

1976年にIBMのマイケル・フェイガンが発案した、6段階の構造化レビュープロセスです。進行役、読み手、査読者、著者といった定義された役割、義務的な個別準備、時間制限付きの会議を用いて、テスト前に欠陥を発見します。一貫して60〜90パーセントの欠陥除去率を報告しています。

大規模言語モデルは人間のコードレビュアーを代替できますか?

最も重要な欠陥については代替できません。大規模言語モデルは、nullチェックの欠落やリソース漏洩といった機械的な問題を捕捉します。上流契約に関する誤った前提、欠落している業務ロジック、コードと要件の不整合といった意図レベルの欠陥は見逃します。

フェイガン査読に大規模言語モデルを統合するには?

準備段階で大規模言語モデルを使用します。チェックリストに対して実行し、その出力を会議前に人間の査読者に渡し、各指摘を検証することを義務付けます。これにより会議時間が短縮され、人間の集中度が向上しますが、人間の判断は排除されません。

大規模言語モデルのチェックリストプロンプトを効果的にするには?

具体性です。漠然としたプロンプトは漠然とした出力を生みます。効果的なプロンプトは正確な欠陥カテゴリを指定し、構造化されたJSON出力を要求し、修正案の提案を禁止し、正確な行番号と変数名を求めます。チェックリストは使用言語と、チームが最も見落としがちな欠陥に合わせるべきです。

1つのファイルで試してみよう

先月に本番インシデントを引き起こしたファイルを1つ選ぼう。チームが気にしている5つの具体的な欠陥タイプのチェックリストを書こう。上記のスクリプトをそのチェックリストで実行しよう。出力を、そのファイルを書かなかった2人のエンジニアに渡そう。

大規模言語モデルの発見と人間の発見を比較しよう。重なりを数え、それぞれが見逃したものを数えよう。その隙間が、あなたの答えだ。