Skip to content
Published on

AIが書いたコードをどうレビューするか — コードレビューの再設計

Authors

はじめに

2026年6月、テストライブラリjqwikのメンテナーであるJohannes Linkが書いた「The jqwik Anti-AI Affair」という記事が、GeekNewsとHacker Newsを沸かせました。AI貢献をめぐってOSSプロジェクトで起きた対立をメンテナー本人の視点で記録したこの記事は、多くの開発者が漠然と感じていた不安に正面から触れました。AIがコードを吐き出す速度と、人間がそれをレビューできる速度の間のギャップです。

数字で見ると状況は明確です。コーディングエージェントが普及した2026年現在、多くのチームで新しく書かれるコードの半分以上がAIの手を経ています。コードを書くコストは10分の1に下がったのに、コードをレビューするコストはほぼそのままです。ボトルネックはもはや作成ではなくレビューです。そして従来のコードレビュー慣行 — 人間が書いたコードを人間が行単位で読む方式 — は、この負荷を支えるようには設計されていません。

本記事では、AI生成コードの欠陥パターンが人間のコードとどう違うのか、その違いに合わせてレビュー戦略をどう再設計すべきか、AIでAIコードをレビューするパイプライン、OSSとチームレベルのポリシー、そして測定指標まで順に扱います。

なぜレビューがボトルネックになったのか

作成とレビューの非対称を図で見るとこうなります。

2023年: 人間が作成 + 人間がレビュー
  作成   ████████████████ (遅い)
  レビュー ████ (作成に比べ小さい比重)

2026年: AIが作成 + 人間がレビュー
  作成   ██ (速い、大量)
  レビュー ████████████████████████ (ボトルネック!)

  PR数3倍、PRあたりのdiffサイズ2倍、
  レビュアーの時間はそのまま

問題を悪化させる要因がさらに二つあります。

第一に、レビュー疲労の質が変わりました。人間のコードの欠陥はたいてい「どこかぎこちない」シグナルを伴います。一貫性のない命名、不自然な構造などがレビュアーの注意を引きます。AIコードは正反対です。表面的にきれいで、自信を持って間違えます。レビュアーが疑いの糸口をつかみにくいのです。

第二に、作成者が自分のコードを説明できないケースが増えました。「なぜこう実装したのですか」という質問に「エージェントがそうしました」という答えが返ってくるPRは、レビューの前提(作成者がコードを理解している)を崩壊させます。

人間のコードとAIコード — 欠陥パターンが違う

レビュー戦略を組み直すには、まず敵を知る必要があります。AI生成コードの欠陥は人間の欠陥と分布が異なります。

欠陥タイプ人間のコードAIコード
タイポ、文法ミスよくあるまれ
もっともらしいロジックエラーたまによくある (自信を持って間違える)
ハルシネーションAPIほぼないよくある (存在しないメソッド、パッケージ)
過剰な防御コードまれよくある (不要なtry-catch、null チェックの入れ子)
要件の誤解質問で解消沈黙のまま誤実装
コンベンション違反まれ (チーム学習)ガイドがなければよくある
デッドコード、重複実装たまによくある (既存ユーティリティを探さない)
セキュリティ脆弱性パターン多様学習データの古いパターンを再生産

特に注意すべき三つを見てみましょう。

もっともらしい誤り (plausible-but-wrong)

AIコードの代表的な欠陥です。型も合い、テストの一部も通り、変数名ももっともらしいのに、ビジネスロジックの境界条件が微妙に間違っているケースです。例えば日付範囲比較での境界の包含可否、通貨計算での丸めのタイミング、ページネーションのoff-by-oneなど。人間なら「確信がないので」コメントや質問を残す地点で、AIは滑らかに間違ったコードを生成します。

典型的な例をコードで見てみましょう。

# スペック: 「サブスクリプションは満了日当日まで有効」

def is_active(sub, today) -> bool:
    # AI生成 — 滑らかだが間違い: 満了日当日が除外される
    return sub.start_date <= today < sub.expires_on

def is_active_correct(sub, today) -> bool:
    # 正しい実装: 境界を含む
    return sub.start_date <= today <= sub.expires_on

このdiffは型チェックとハッピーパスのテストをすべて通過します。捕まえる方法は実装の読解ではなく、満了日当日を検証する境界テストが存在するかの確認です。

from datetime import date

def test_active_on_expiry_day():
    sub = make_sub(start="2026-06-01", expires="2026-06-30")
    assert is_active(sub, date(2026, 6, 30))  # 境界ケース

ハルシネーションAPIとパッケージ

存在しないライブラリメソッドを呼び出したり、存在しないパッケージを依存関係に追加する欠陥です。後者はセキュリティ問題に直結します。攻撃者がAIの頻繁にハルシネーションするパッケージ名を先に登録しておくスロップスクワッティング(slopsquatting)攻撃が実際に観測されており、2026年6月のnpmサプライチェーン攻撃事態は、依存関係の追加がすなわちセキュリティ決定であることを改めて思い知らせました。arXivで公開された「We Have a Package for You!」研究は、商用モデルでさえ相当な割合で存在しないパッケージを推薦することを定量的に示しました。

過剰な防御コード

AIは批判を回避する方向に学習された傾向があり、不要な場所にtry-catchを巻き、到達不可能なnullチェックを入れ子にし、すべての関数に防御ロジックを複製します。これは単なるスタイルの問題ではありません。エラーを飲み込むcatchブロックは障害を沈黙させ、重複防御は「この値は実際にnullになり得るのか」という設計情報を破壊します。

レビュー戦略の再設計

欠陥分布が違えば、レビューの注意配分も変わるべきです。三つの原則を提案します。

原則1 — 行ではなくスペックと照合せよ

人間のコードレビューでは「コードを読みながら欠陥を探す」ボトムアップが通用しました。AIコードは表面がきれいなので、ボトムアップの読解は効率が低い。代わりにトップダウンで行きます。まず要件(イシュー、スペック、受け入れ条件)を読み、「この要件はコードのどこで満たされているか」を追跡します。スペックにないコード(スコープ爆発)とコードにないスペック(欠落)を見つけることが第一目標です。

ボトムアップ (従来): コード --> 読む --> 欠陥発見 --> スペック確認
トップダウン (再設計): スペック --> 受け入れ条件をリスト化
                              --> コードで充足地点を追跡
                              --> 残ったコード = 疑いの対象

原則2 — テストを先にレビューせよ

実装よりテストを先に読みます。確認することは二つ。第一に、テストがスペックの受け入れ条件を実際に検証しているか(テストが実装を写しただけのトートロジーではないか)。第二に、境界条件のケースがあるか。AIのもっともらしい誤りはほとんど境界に隠れているので、「空の入力、最大値、並行性、タイムゾーン」を扱うテストの有無は強力な品質シグナルです。テストが信頼できれば、実装読解の負担を大きく減らせます。

原則3 — diffサイズを強制せよ

レビュー品質はdiffサイズに反比例します。研究でも実務でも繰り返し確認された事実ですが、AI時代には強制装置が必要です。エージェントは指示すれば3,000行のPRも一度に作ってしまうからです。実用的な上限はPRあたり400行前後で、CIで機械的に検査するのが良いでしょう。

三つの原則を総合すると、レビュアーの時間配分も変わります。60分レビュー基準の推奨配分です。

レビュー時間配分ガイド (60分基準、AI関与PR)

スペック照合           ████████████████ 20分
テストレビュー         ████████████ 15分
セキュリティ/依存の点検 ██████████ 12分
実装の読解 (サンプリング) ██████ 8分
設計フィードバック作成  ████ 5分
# .github/workflows/pr-size-gate.yml
name: pr-size-gate
on: [pull_request]
jobs:
  check-size:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Fail if diff exceeds limit
        run: |
          LINES=$(git diff --shortstat origin/${{ github.base_ref }}... \
            | awk '{print $4 + $6}')
          echo "changed lines: $LINES"
          if [ "$LINES" -gt 400 ]; then
            echo "PR too large. Split it."
            exit 1
          fi

AIでAIコードをレビューする — 自動一次レビュー + 人間の最終判断

AIが生み出した負荷を人間だけで処理できないなら、レビューにもAIを投入するのが自然な帰結です。ただし役割分担が核心です。

PR作成
  |
  v
[ゲート0] CI: ビルド、テスト、リント、diffサイズ、シークレットスキャン
  |
  v
[ゲート1] AI一次レビュー (機械が得意なこと)
  - ハルシネーションAPI検証: すべてのimportと呼び出しが実在するか
  - 新規依存の検証: レジストリ存在、ダウンロード数、ライセンス
  - スペック照合: イシューの受け入れ条件ごとの充足表を生成
  - 過剰防御コード、デッドコード、重複実装のフラグ
  - テスト弱体化の検出 (assert削除、skip追加)
  |
  v
[ゲート2] 人間の最終レビュー (人間が得意なこと)
  - 設計の適切さ: このアプローチ自体が正しいか
  - プロダクト判断: エッジケースの優先順位
  - セキュリティ設計: 信頼境界、権限モデル
  - AI一次レビュー結果のサンプル検証

AI一次レビュアーに与える指示の例です。

# レビュアーエージェント指示 (抜粋)

おまえは一次レビュアーだ。マージ権限はない。出力は以下の形式の表のみ。

検査項目:
1. diffのすべての外部API呼び出しを列挙し、該当バージョンの公式文書に
   存在するか個別に確認せよ。確認不可なら UNVERIFIED と表記。
2. 新たに追加された依存ごとに: レジストリ登録日、週間ダウンロード、
   ライセンスを照会して表に整理せよ。
3. イシューの受け入れ条件を行に、充足根拠(ファイル:行)を列に表を作れ。
   根拠が見つからない条件は MISSING と表記せよ。
4. テストdiffで削除または弱体化されたassertがあればすべて列挙せよ。

禁止: 称賛、些細なスタイル指摘、推測に基づく承認意見。

重要な運用原則が一つあります。AIレビュアーの出力は「判定」ではなく「証拠収集」であるべきです。承認/差し戻しをAIに任せると、作成エージェントとレビューエージェントが同じ盲点を共有するとき、欠陥がそのまま通過します。AIが表を作り、人間が判断します。

ハルシネーション依存の決定的検証

ゲート1の検査のうち「依存の実在検証」は、LLMなしでもスクリプトで決定的にできます。

#!/usr/bin/env bash
# check-new-deps.sh — 新規依存の実在と衛生を検査
set -euo pipefail

BASE_REF="origin/main"
NEW_DEPS=$(git diff "$BASE_REF"...HEAD -- package.json \
  | grep '^+ ' | grep -oE '"[@a-z0-9/._-]+"\s*:' \
  | tr -d '": ' | sort -u)

for dep in $NEW_DEPS; do
  created=$(npm view "$dep" time.created 2>/dev/null) || {
    echo "FAIL: $dep — レジストリに存在しない (ハルシネーション/タイポ疑い)"
    exit 1
  }
  license=$(npm view "$dep" license 2>/dev/null || echo "UNKNOWN")
  echo "OK: $dep (created: $created, license: $license)"
done
echo "dependency check passed"

存在しないパッケージはここで決定的に遮断され、登録日とライセンス情報は人間レビューの入力として渡されます。決定的に検査できるものはLLMに任せない — ゲート1設計の基本原則です。

実装の選択肢比較

自動一次レビューを構成する選択肢は大きく三つあります。

選択肢長所短所
商用レビューボット即導入、管理不要カスタム検査の制限、コードの外部送信
CIからエージェントを直接呼ぶ検査項目の完全な統制プロンプトとコスト管理の負担
自前パイプライン決定的検査とLLMの混合最適化構築と保守のコスト

どれを選んでも核心の要件は同じです。検査結果が構造化された形式でPRに残り、人間のレビュアーがそれを入力として使えなければなりません。

レビューチェックリスト — セキュリティ、ライセンス、性能

AI生成コードに特化したチェックリストです。チームWikiにそのまま転載できるように書きました。

セキュリティ

  • 新規依存: パッケージがレジストリに実在するか、登録日が直近数週間以内で怪しくないか、メンテナーが信頼できるか
  • 入力検証: AIが書いた正規表現にReDoSの可能性はないか
  • 認証/認可: 学習データの古いパターン(弱いハッシュ、ハードコードされたシークレット、旧式TLS設定)が再生産されていないか
  • エラー処理: catchブロックがセキュリティ関連のエラーを飲み込んでいないか
  • プロンプト由来の検証: 外部コンテンツを読むエージェントが書いたコードなら、インジェクションで誘導された変更でないか、diffの意図をイシューと照合

ライセンス

  • 生成コードが特定のOSS実装と事実上同一でないか (大きな関数単位での類似度検査)
  • 新規依存のライセンスが組織ポリシー(例: GPL系禁止)と衝突しないか
  • コード出所の表記が必要な社内ポリシーがあるなら、AI生成の有無が記録されているか

性能

  • ループ内のN+1クエリ、繰り返しソート、不要なディープコピー — AIがよく作るパターン
  • 過剰防御コードによるホットパスの不要な検査
  • 並行性: AIはロック範囲を保守的に大きく取る傾向 — ボトルネックの可能性を確認

PR単位の再設計とコミット衛生

レビュー可能なPRは作成段階で作られます。エージェントに次をガイドファイルで強制します。

  • 1 PR = 1 意図: 機能追加とリファクタリングを一つのPRに混ぜない。エージェントは「ついで」の修正を好むため、明示的に禁止する必要があります。
  • コミットは物語の順序で: 「テスト追加 → 実装 → リファクタ」のように、レビュアーが追える順序でコミットを構成させます。
  • PR説明テンプレート: 何を(スペックリンク)、どう(アプローチ要約)、検証(実行したテスト)、AI関与度(全部/一部/なし)を必須項目に。
  • 機械的変更と意味的変更の分離: フォーマット、インポート整理のような機械的変更は別コミットに分離し、レビューノイズを除去します。

PR説明テンプレートの例です。

## 何を (スペック)
- イシュー142 — 決済Webhookのリトライロジック

## どう (アプローチ要約)
- 冪等性キー検証 + 指数バックオフのリトライキュー

## 検証
- ./verify.sh 通過
- 新規テスト8件 (境界ケース4件を含む)
- ステージングでWebhook再送を手動確認

## AI関与度
- partial (実装: エージェント、テスト設計とレビュー: 人間)

## レビュアーへの案内
- 重要な判断地点: 最大リトライ5回と仮定 (スペック空白、確認依頼)
- 意図的に除外したもの: デッドレター処理 (後続PR予定)

特に最後の「レビュアーへの案内」項目が重要です。作成者自身が不確かだと感じた地点を表面化すれば、レビュアーの注意を最も危険な場所に集中させられます。AIが書いたコードであるほど、この項目は人間が自分で書くべきです。

OSSメンテナーの視点 — jqwik事件が残したもの

jqwik事件の顛末はこうです。メンテナーがAI貢献への保守的なポリシーを表明すると、激しい反発と論争が続き、その過程でメンテナー個人への攻撃まで起きました。この事件が示すのは技術問題ではなくガバナンス問題です。

OSSメンテナーにとってAI貢献は非対称コストの問題です。貢献者はエージェントで10分でPRを作りますが、メンテナーはそのレビューに1時間を使います。curlのDaniel StenbergがAIが量産した偽のセキュリティレポート(「AI slop」)について公に警告したのも同じ文脈です。レビューコストを払う側と生成コストを節約する側が別の人間であること — これが対立の経済学です。

健全なプロジェクトが収束しつつあるポリシーの方向は次のとおりです。

  • 禁止ではなく開示: AI使用自体を防ぐのではなく、PRにAI関与度を明示させます。隠蔽が発覚したらその時に制裁します。
  • 貢献者責任の原則: 「あなたが提出したコードはあなたが説明できなければならない」。ツールが何であれ責任は提出者にあります。
  • 信頼の段階制: 初回貢献者の大型PRは受けず、小さな貢献で信頼を積んでから範囲を広げさせます。
  • メンテナー保護: レビュー拒否は正当な権利です。「レビューされる権利」は存在しません。

チームポリシーのテンプレート

社内チーム用ポリシーの出発点として使えるテンプレートです。

# AI生成コードポリシー (チーム標準 v1)

## 許可
- すべての業務コードにAIツール使用可
- ただし提出者はdiffのすべての行を説明できなければならない

## 義務
- PR説明にAI関与度を表記: full / partial / none
- AI関与PRは自動一次レビュー(ゲート1)の通過必須
- 新規依存の追加は別コミット + 依存検証チェックの通過

## 禁止
- テストファイルとCI設定のエージェント修正
- レビューコメントへのAI自動応答 (人間が読んで答えること)
- 400行超のPR (分割必須、例外はリード承認)

## レビュアー保護
- AI関与PRのレビューSLAは通常PRの1.5倍
- レビュアーは「説明不能コード」を理由に差し戻せる

導入ロードマップ

再設計全体を一度に導入する必要はありません。効果に対する摩擦が小さい順に推奨します。

  1. 第1週 — diffサイズゲートとAI関与度の表記: CIジョブ一つとPRテンプレート一行から始めます。摩擦が最も少なく、効果は即座に出ます。
  2. 第2週 — 依存検証スクリプト: check-new-deps.shをCIに追加します。ハルシネーションパッケージとスロップスクワッティングを決定的に遮断します。
  3. 第3〜4週 — AI一次レビュー(ゲート1)の導入: 最初はコメントのみでブロックしない観察モードで運用し、的中率を測定します。
  4. 第2ヶ月 — ポリシー文書化と指標ダッシュボード: チームポリシーのテンプレートを自チームに合わせて修正して公式化し、次節の指標を週次で見ます。
  5. 四半期ごと — ポリシーのふりかえり: 的中率の低い検査は削除し、マージ後の欠陥から新しい検査項目を逆算します。

よくある質問

  • AI一次レビューのコストが負担だ — ゲート0(CI)を通過したPRだけにゲート1を回し、diffが小さければ検査項目を減らす条件付き実行から始めてください。
  • 作成者がAIを使ったかどうかをどう確認するか — 確認しようとしないでください。申告ベースのポリシー(表記義務 + 虚偽表記時の制裁)の方が、検出ベースのポリシーより運用コストがはるかに低く、紛争も少ない。
  • レビュアーが絶対的に足りない — レビュアーの追加よりdiffサイズ上限の強化が先です。400行PR1件より100行PR4件の方が、同じレビュー時間でより高い品質を生みます。
  • 緊急修正は例外にすべきか — 例外パスを作りつつ、事後レビューを義務化し、例外の使用頻度を指標として追跡してください。例外が常態化したらポリシーは死にます。

測定指標 — レビュー再設計が効いているかをどう知るか

ポリシーは測定なしには改善されません。推奨指標は次のとおりです。

指標定義望む方向
レビューリードタイムPR作成から最初のレビューまで減少
マージ後欠陥率マージ後N日以内のrevert、hotfix比率減少
diffサイズ中央値PRあたりの変更行数400行以下を維持
AI一次レビュー的中率AIフラグのうち人間が有効と判定した割合60%以上
レビュアー負荷分散レビュアー別週間レビュー行数の偏差減少
ハルシネーションAPI流出数マージ後に発見された存在しないAPI呼び出し0を維持

特に「マージ後欠陥率」をAI関与度別に分けて見ると、ポリシーが実際に機能しているかが明らかになります。AI関与PRの欠陥率が有意に高ければゲート1を強化すべきで、差がなければ過剰な手続きを減らせます。

指標収集を最初から自動化する必要はありません。週次で次の三つを手で数えるだけでも方向はつかめます。

  • 今週マージされたPRのうち400行超の比率
  • AI一次レビューのフラグのうち人間が採用した比率
  • revertまたはhotfixにつながったマージ件数

落とし穴と批判的視点

この再設計自体にも落とし穴があります。

第一に、自動化バイアスです。AI一次レビューが「合格」を出すと、人間がレビューを飛ばす傾向が生まれます。AIレビューは証拠収集であって免罪符ではないという原則を、プロセスで強制すべきです(例: 人間レビュアーがAIレポートの項目のうち最低2つを直接検証)。

第二に、形式主義のリスクです。AI関与度の表記、チェックリスト、diff上限が増えるほど、「手続きは守ったが誰も考えていない」レビューになり得ます。指標が良くなるのに障害が増えるなら、形式主義を疑うべきです。

第三に、貢献文化の萎縮です。jqwik事件の裏には、善意の貢献者が萎縮する副作用もありました。ポリシーの目的はAIの排除ではなくレビュー可能性の回復であることを、ポリシー文書自体に明記するのが良いでしょう。

最後に根本的な問いが残ります。コード作成がほぼ無料になった世界で、行単位レビューという行為自体が有効なのか。長期的には「コードを読むレビュー」から「スペック、テスト、プロパティを検証するレビュー」へと、レビューの対象自体が移動する可能性が高い。本記事の再設計はその過渡期の戦略です。

おわりに

コードレビューは元々二つの仕事を同時にしていました。欠陥を捕まえる仕事と、チームがコードベースへの共有された理解を維持する仕事。AIは一つ目の仕事の負荷を爆増させ、二つ目の仕事の前提(作成者が理解している)を揺るがしました。

だから再設計の本質はツールではなく責任の再配置です。機械が検証できるものは機械に(ハルシネーションAPI、依存関係、diffサイズ、テスト弱体化)、人間にしかできないものは人間に(設計判断、プロダクト感覚、最終責任)。そして誰が作ったものであれ「提出者が説明できないコードはマージされない」という原則を守ること。それがAI時代にもコードレビューが意味を保つ道だと考えます。

参考資料