「コードを生み出すコストがゼロに近づくと、そのコードが正しいか確認するコストがすべてになる。」
プロローグ — ボトルネックはいまやレビューだ
数年前まで、ソフトウェアチームのボトルネックはコードを書くことだった。機能を一つ実装するには人間がキーボードの前に座って一行ずつタイプする必要があり、その時間がスケジュールの大半を占めていた。
2026年、そのボトルネックは消えた。コーディングエージェントは数分で数百行を生成する。機能一つがPRとして上がるまでの時間は10分の1になった。しかしチームのスループットはそれほど伸びていない。なぜならボトルネックは消えたのではなく移ったからだ。ボトルネックはいまやレビューだ。
生成は速くなったが、検証は速くなっていない。だからレビューキューにPRが積み上がり、レビュアーは自分が書いていないコードを毎日数百行読まされ、「とりあえず通そう」という誘惑と「全部見直さなければ」という負担のあいだで揺れる。
人間のコードレビューとは別のスキルだ
ここで核心の主張を先に投げる。AIが書いたコードをレビューすることは、人間のPRをレビューすることとは別のスキルだ。
人間のコードレビューはおおむね社会的な行為だ。PRエチケット、マージキュー、「これは好みの問題かもしれないが」というようなトーン調整、同僚の成長を助けるコメント — その半分はそれだ。人間のレビューは信頼を前提とする。「この人はこのドメインを知っているし、知らなければ聞いたはずだ」という仮定が下敷きにある。
AIコードレビューにはその仮定がない。エージェントは知らなければ聞かない。最もそれらしいもので埋める。自信と正確さが切り離されている。だからAIコードレビューは社会的な行為ではなく検証規律だ。トーンを調整する必要も、成長を助ける必要もない。代わりに「これは本当に正しいのか」を体系的に、疑いをデフォルトに置いて確認しなければならない。
この記事はその検証規律についての実践ガイドだ。一般的な人間のコードレビューの記事ではない — それは別の記事のテーマだ。この記事はただ一つ、エージェントが書いたコードをどう検証するかだけを扱う。
この記事で扱う内容:
| 章 | テーマ |
|---|---|
| 1章 | なぜAIコードは違うレビューのレンズが必要か |
| 2章 | AI生成コードの特有な失敗パターン |
| 3章 | 検証ループ — 型からテストから人間へ |
| 4章 | AIのdiffを効率的に読む |
| 5章 | 『AIスロップ』 — 正体とフィルタリング |
| 6章 | AIがAIコードをレビューするとき |
| 7章 | コードベースを検証可能にする |
| 8章 | 人間にしかできない、削れない仕事 |
| エピローグ | チェックリスト + アンチパターン + 次回予告 |
1章 · なぜAIコードは違うレビューのレンズが必要か
人間が書いたコードのバグと、AIが書いたコードのバグは分布が違う。 同じレンズで見ると見逃す。
1.1 それらしいが間違ったコード
人間のミスはたいてい「粗く」見える。変数名がおかしい、インデントがずれている、明らかに未完成だとわかる。レビュアーの目は自然にそこで止まる。
AIのミスは違う。表面が滑らかだ。 変数名は適切で、構造は慣例に従い、コメントまでついている。なのにロジックが微妙に間違っている。これが最も危険だ — コードが「うまく書けたように」見えるため、レビュアーの疑いセンサーがオンにならない。
人間のコードレビュー: 「おかしく見える場所」を探す。 AIコードレビュー: 「正しく見えるが間違っている場所」を探す。はるかに難しい。
1.2 自信に満ちたハルシネーション
エージェントは「よくわかりません」とは言わない。存在しないAPIを自信を持って呼び、ないパッケージをimportし、動作しない設定キーをそれらしい名前で作り出す。出力のトーンには不確実性の痕跡がない。
人間なら、知らない領域でためらう信号 — コメントの疑問符、「これで合っているか?」というコメント、ドラフトPR — が残る。エージェントの出力にはそれがない。確信に満ちたコードとハルシネーションに満ちたコードがまったく同じに見える。
1.3 抜け落ちたエッジケース
AIは「ハッピーパス(happy path)」をうまく書く。入力が正常で、ネットワークが健全で、配列が空でないときのコードはほぼ常に正しい。
問題はその外側だ。空の配列、null 入力、タイムアウト、並行性、部分的失敗、整数オーバーフロー — 人間のシニアなら反射的に思い浮かべるケースを、AIは明示的に要求しないと頻繁に抜かす。コードはデモで完璧に動き、本番の3週目に崩れる。
1.4 過剰設計
逆方向の失敗もある。「ユーザー名を大文字にして」と頼んだら、抽象ファクトリ、ストラテジーパターンのインターフェース、設定可能な変換パイプラインがついてくる。AIは学習データで見た「エンタープライズ級」のパターンを小さな問題に過剰に適用する傾向がある。
過剰設計はバグではないが負債だ。読むコードが増え、保守の表面が広がり、次の人(または次のエージェント)が迷う。
1.5 微妙なAPIの誤用
AIはAPIを「だいたい」正しく使う。関数名は合っているが引数の順序が違う、オプションオブジェクトのキーの一つが古いバージョンのもの、非同期関数を await なしで呼ぶ、戻り値の意味を微妙に誤解する。
型システムが強ければ、このうちかなりの部分がコンパイル段階で捕まる。だから1章の結論は次の章ではなく3章につながる — 機械が捕まえられるものは機械に任せるべきだ。
2章 · AI生成コードの特有な失敗パターン
1章が「なぜ違うか」なら、2章は「具体的に何を探すか」だ。レビューするときに頭に浮かべておくチェック項目だ。
2.1 パターンカタログ
| パターン | 症状 | どう捕まえるか |
|---|---|---|
| ハルシネーションAPI/パッケージ | 存在しない関数・オプション・ライブラリ | ビルド/型チェック、依存ロックファイルの確認 |
| コピペの不整合 | 同じロジックがファイルごとに微妙に違う | diff全体を一度にスキャン |
| エラー処理の欠如 | try/catch がない、失敗を無視 | すべての外部呼び出しに「失敗したら?」を問う |
| セキュリティの死角 | 未検証の入力、ハードコードされたシークレット、インジェクション | 信頼境界に沿ってデータフローを追跡 |
| 何も検証しないテスト | 通るが何も検証しない | テストを意図的に壊してみる |
| 過剰設計 | 小さな問題に大きな抽象 | 「本当に必要か」を問う |
2.2 ハルシネーションAPIとパッケージ
最も多く、最も捕まえやすいパターンだ。エージェントは「あればいいのに」というAPIを作り出す。
// AIが生成したコード — それらしいが間違っている
import { retryWithBackoff } from 'lodash' // lodashにこんな関数はない
import dayjs from 'dayjs'
const result = await fetchUser(userId, {
retry: 3, // fetchUserのオプションにretryはない — ハルシネーション
timeout: '5s', // timeoutはnumber(ms)を受け取る — 型の誤用
})
const formatted = dayjs(result.createdAt).format('YYYY-MM-DD')
上のコードは読んで自然だ。しかし lodash に retryWithBackoff はなく、fetchUser に retry オプションもない。良いニュースは、この種類は型チェックとビルドがほぼすべて捕まえるということだ。人間が目で探す必要はない。
2.3 コピペの不整合
エージェントは一つのPRの中でも同じことを複数の方法でやる。あるファイルでは async/await、別のファイルでは .then()。ある場所ではエラーを投げ、別の場所では null を返す。各断片だけ見れば全部問題ないが、全体で見ると一貫性がない。
これを捕まえるにはdiffをファイル単位で見てはいけない — PR全体を一度にスキャンしなければならない。「このPRは同じ種類のことを何通りの方法でやっているか?」が問いだ。
2.4 エラー処理の欠如
AIコードのハッピーパスはきれいだ。そしてハッピーでないパスは存在しない。 外部API呼び出しに try/catch がなく、ファイル読み込みの失敗が無視され、JSON.parse がガードなしで呼ばれる。
レビュールール: すべての外部境界(ネットワーク、ディスク、パース、サードパーティ呼び出し)に「ここで失敗したら何が起こるか?」を問う。AIのdiffでは、この問いの答えが「アプリが落ちる」であることが驚くほど多い。
2.5 セキュリティの死角
エージェントはセキュリティを「機能ではないもの」として扱う。要求どおり動作するコードを作るだけで、敵対的な入力は仮定しない。よくある死角:
- ユーザー入力を検証なしでクエリ・コマンド・パスに連結する
- シークレットをコードにハードコードする(デモで「とにかく動かす」ために)
- 認可チェックの欠如 — 「このユーザーはこれをできるか」を問わない
- ログに機微情報を出力する
信頼境界に沿って、データがどこから入ってどこへ行くかを指でたどる。AIはこれを自分ではやらない。
2.6 何も検証しないテスト
最も狡猾なパターンだ。エージェントに「テストも書いて」と頼むとテストを書く。通るテストを。しかしそのテストが何を検証するか見ると、しばしば何でもない。
// AIが生成した「テスト」 — 通るが無意味
test('calculateDiscount works', () => {
const result = calculateDiscount(100, 0.1)
expect(result).toBeDefined() // 何でも通る
expect(typeof result).toBe('number') // 90でも9999でも通る
})
このテストは calculateDiscount が 0 を返そうと -50 を返そうと通る。実際の値(90)を検証しないからだ。さらに悪い場合、実装をそのまま写して「実装が実装と等しい」を確認するテストもある。
検証法は単純だ。テストを意図的に壊してみる。 実装にバグを仕込んだのにテストが通るなら、そのテストは偽物だ。
3章 · 検証ループ — 型からテストから人間へ
2章のパターンを人間の目で一つずつ探すと疲れる。核心の原則はこれだ: 機械が捕まえられるものは機械に任せ、人間の注意力は機械が捕まえられないものに使う。
3.1 三層のフィルタ
検証を三段階のフィルタとして考える。安いフィルタを先に、高いフィルタを後に。
| 段階 | ツール | 捕まえるもの | コスト |
|---|---|---|---|
| 1. 型 | 型チェッカー、リンター、コンパイラ | ハルシネーションAPI、型の誤用、未使用変数 | ほぼゼロ(秒単位) |
| 2. テスト | ユニット・統合テスト、静的解析 | ロジックエラー、リグレッション、エッジケース | 低い(分単位) |
| 3. 人間 | レビュアーの判断 | アーキテクチャ、意図、「これは意味をなすか」 | 高い(人間の時間) |
原則: 1段階を通過しないコードは人間が見る価値がない。 型エラーのあるPRを人間がレビューするのは人間の時間の無駄だ。CIに先に通させる。
3.2 型を第一の防衛線に
強い型システムはAIコードレビューで最大のレバレッジだ。2章のハルシネーションAPI、APIの誤用、間違った引数 — このうち大きな割合が型チェックでコンパイルエラーとして落ちる。人間が一行も読む前に。
だから緩い型はAI時代にもっと高くつく。 any で塗りたくられたコードベースでは第一のフィルタが機能せず、その負担がすべて3段階(人間)に回ってくる。
3.3 テストを第二の防衛線に
型が「このコードは意味をなす形か」を見るなら、テストは「このコードは正しく動作するか」を見る。ただし2.6の罠を覚えておくこと — AIが書いたテスト自体も検証対象だ。
推奨の順序: テストを人間が先に書く(または人間が仕様を固める)、その次に実装をエージェントに任せる。テストが先にあれば、そのテストは「実装を写した偽物」になりえない。
3.4 人間は最後に、しかし最も重要に
三層のフィルタを通過したコードだけが人間の前に来る。このとき人間の注意力は機械が原理的に捕まえられないものに集中する — アーキテクチャが正しいか、この変更がチケットの意図と一致するか、そもそもこれが意味をなすアプローチか。これが8章のテーマだ。
検証ループの一行要約: 安い機械チェックをすべて通過させたあと、人間は機械が見えないものだけを見る。
4章 · AIのdiffを効率的に読む
検証ループを通過したPRでも、人間がdiffを読まなければならない。人間が書いたdiffとAIのdiffは読み方が違う。
4.1 最初の問い: チケットと一致するか
AIのdiffを開くと、コード品質から見たい衝動がわく。こらえる。最初の問いは常にこれだ: このdiffはチケットが要求したことをやるか?
エージェントはチケットを「だいたい」解釈する。要求されたことの80%をやり、20%を違うようにやるか、要求されていないことを追加でやる。コードがどんなにきれいでも、間違ったものを実装したなら意味がない。チケットの受け入れ基準を一行ずつdiffと照合する。
4.2 スコープクリープの検出
二番目の問い: このdiffは触ってはいけないものを触ったか?
エージェントは「ついでに」他のものも直す傾向がある。フォーマットを変え、無関係なファイルをリファクタリングし、依存バージョンを上げる。それぞれは善意かもしれないが、合わせるとレビュー不可能なdiffになる。
| 信号 | 疑うべきこと |
|---|---|
| 変更ファイル数がチケットの規模に比べて多い | スコープクリープ |
| 無関係なディレクトリに変更が散らばっている | 「ついで」のリファクタリング |
| ロックファイル・設定ファイルが理由なく変わっている | 意図しない依存変更 |
| フォーマットだけ変わった行が大量 | ノイズ — 実質的な変更を隠す |
こうしたdiffは差し戻す。「チケットのスコープだけ残してやり直し」は正当な要求だ。
4.3 diffを読む順序
効率的な順序がある:
- PRの説明 — 何をなぜ変えたと主張するか
- テスト — この変更が何を保証すると主張するか
- コアロジック — 主張が実際のコードと一致するか
- 境界とエラー処理 — ハッピーでないパスがあるか
- 残り — 設定、フォーマット、付随的な変更
テストをロジックより先に読む理由: テストは「このコードが何をすることになっているか」の仕様だ。仕様を先に読めば、ロジックを読むときにずれた場所が目に入る。
4.4 diffのサイズと信頼の反比例
小さなdiffは丁寧に読める。800行のdiffは人間が最後まで集中できない — AIでも人間でも。AIは大きなdiffを簡単に生み出すので、**「このPRをもっと小さく分割できるか」**を常に問わなければならない。レビュー可能性はdiffのサイズに反比例する。
5章 · 『AIスロップ』 — 正体とフィルタリング
5.1 AIスロップとは
**AIスロップ(AI slop)**は、それらしく見えるが実質的な価値が低いAI生成物を指す言葉だ。コードにおけるAIスロップは「コンパイルも通りテストも通るが、コードベースをより悪くするコード」だ。
スロップは明白なバグではない。バグはむしろ捕まえやすい。スロップは微妙に膨れ、微妙に一貫性がなく、微妙に不要なコードだ。一つのPRでは目立たないが、100個のPRが積み上がるとコードベースが沼になる。
5.2 スロップの兆候
| 兆候 | 説明 |
|---|---|
| 冗長さ | 5行で済むことを30行で。不要なヘルパー、ラッパー、抽象 |
| 空のコメント | // ユーザーを取得する の上に getUser() — コードを繰り返すコメント |
| 防御的なノイズ | 起こりえない条件に対するチェックが至る所に |
| 一貫性のなさ | 同じコードベースで5通りのスタイル |
| 死んだ抽象 | 一度だけ使われるインターフェース、実装が一つだけのファクトリ |
| それらしいダミー | 意味のないテスト、TODOだけの関数、placeholderのロジック |
5.3 スロップをフィルタする問い
レビューするときに投げる問いは単純だ:
- 「この行を消したら何が壊れるか?」 — 答えが「何も」ならスロップだ。
- 「この抽象は何箇所で使われているか?」 — 一箇所ならインライン化しろ。
- 「このコメントはコードが言わないことを言うか?」 — そうでないなら消せ。
- 「このPRを半分のサイズに減らせるか?」 — たいてい減らせる。
5.4 スロップは生成ではなく受容の問題だ
重要な視点: スロップを作るのはAIだが、スロップをコードベースに入れるのは人間だ。エージェントはスロップを提案するだけで、マージボタンを押すのはレビュアーだ。
だからスロップ問題の解法は「AIを使うな」ではなく「レビュー基準を下げるな」だ。AIコードだからといって人間のコードより寛大に通してはいけない。むしろ自信に満ちた表面のためにもっと厳しく見なければならない。
6章 · AIがAIコードをレビューするとき
検証ループに「AIレビュアー」を一段階追加するのは合理的だ。しかし罠がある。
6.1 生成者と検証者は分離されなければならない
核心の原則: コードを生成したエージェントが同じコードをレビューしてはいけない。
同じモデル、同じコンテキスト、同じ仮定を持つエージェントは、自分が作ったハルシネーションを自分では見えない。間違ったAPIを作り出した推論の過程が、そのまま「このAPIは正しい」と判断する。人間でいえば、自分の答案を自分で採点するのと同じだ。
検証者は分離されなければならない — 別のモデルか、少なくとも別のコンテキストと別のプロンプトを持つ別個のセッションでなければならない。
6.2 AIレビュアーが得意なことと苦手なこと
| AIレビュアーが得意なこと | AIレビュアーが苦手なこと |
|---|---|
| パターンマッチング — 既知のアンチパターン、よくあるバグ | アーキテクチャの判断 — 「このアプローチは正しいか」 |
| 一貫性チェック — スタイル、命名 | 意図の一致 — 「チケットが本当に望んだのはこれか」 |
| チェックリストの適用 — 欠けたエラー処理など | トレードオフ — 「この複雑さは見合うか」 |
| 表面的なセキュリティ — ハードコードされたシークレットなど | ドメインの整合性 — ビジネスルールは正しいか |
要約: AIレビュアーは検証ループの1.5段階だ — 型チェックより賢いが人間を代替しない。機械が捕まえられるものの範囲を広げるだけで、最後の判断は依然として人間のものだ。
6.3 実用的な配置
推奨の構成:
- エージェントAがコードを生成する
- 型チェック・テスト・リント(機械の1段階)
- エージェントB(別のコンテキスト)がレビューする — パターン・一貫性・チェックリスト
- 人間が最終レビューをする — アーキテクチャ・意図・判断
AIレビュアーのコメントは入力であって結論ではない。 人間はAIレビュアーが見逃したものと過敏に反応したものの両方をフィルタしなければならない。
7章 · コードベースを検証可能にする
検証ループの効率は、コードを検証するスキルよりもコードベースがどれだけ検証可能かに大きく左右される。検証しやすいコードベースはAI時代に複利で報われる。
7.1 強い型
すでに3章で言ったが、もう一度強調する価値がある。強い型は第一のフィルタの性能を決める。any を減らし、ドメインを型で表現し(プリミティブの代わりに UserId、Email のような型)、関数シグネチャを正直にする。型が強いほど人間が読むべき量が減る。
7.2 速いテスト
テストが遅いと検証ループが切れる。30分のテストスイートは「とりあえずマージして後で見よう」を呼ぶ。テストは速く、決定的で、信頼できなければならない。 フレーキーなテストはスロップと同じくらい有害だ — シグナルをノイズに変えるからだ。
7.3 明確な慣例
エージェントはコードベースのパターンを模倣する。コードベースに一貫した慣例があれば、エージェントの出力も一貫する。慣例がなければ、エージェントは毎回違うスタイルを持ってくる(2.3のコピペの不整合は、実は慣例の不在の症状でもある)。
慣例をドキュメントに、そして可能ならリンタールールに固定する。CONTRIBUTING ドキュメント、明確なディレクトリ構造、エージェント用のガイドファイル — これらがエージェントの出力品質を直接引き上げる。
7.4 検証可能性チェックリスト
| 項目 | 問い |
|---|---|
| 型 | any の割合は低いか? ドメインが型で表現されているか? |
| テスト | スイート全体が数分で終わるか? フレーキーなものがないか? |
| リント | スタイル・よくあるミスが自動的に捕まるか? |
| 慣例 | 新しいコードが従うべきパターンが文書化されているか? |
| 境界 | モジュール境界が明確でdiffの影響範囲が狭いか? |
この五つがそろったコードベースでは、AIコードレビューが速く信頼できる。そろっていないコードベースでは、すべてのPRが人間のフルレビューを要求する — そしてそれがまさにボトルネックだ。
8章 · 人間にしかできない、削れない仕事
機械が1段階と2段階を取り、AIレビュアーが1.5段階を取ると、人間に残るものは何か。縮んだが消えておらず、むしろもっと重要になった仕事だ。
8.1 判断
「このコードは正しい。」この文は二つを意味する。(1) コードが仕様どおり動作する — これは機械が検証する。(2) 仕様そのものが正しい — これは人間にしかできない。
エージェントはチケットが命じたことをやる。チケットが間違っていれば、間違ったものを完璧に実装する。「このチケットはそもそも解くべき正しい問題か」は機械が問わない。
8.2 アーキテクチャ
個々の関数が正しくても、システムの構造が間違っていることはありうる。この抽象が6か月後にも持ちこたえるか、この境界が正しい場所に引かれているか、この依存の方向が健全か — これはパターンマッチングでは答えられない問いだ。コードベース全体の未来を想像する仕事であり、それは人間の仕事だ。
8.3 「これは意味をなすか」
最も削れない仕事は最も単純な問いだ: これは意味をなすか。
エージェントが完璧に動作する、型が合った、テストを通すコードを持ってきた — そしてその機能はそもそも作るべきではなかったかもしれない。もっと単純な解法があったかもしれないし、問題を違うように定義すべきだったかもしれない。機械は「どうやって」を検証するが「なぜ」と「本当に?」は人間のものだ。
8.4 責任
最後に、マージボタンを押す人がそのコードの責任者だ。「AIが書いた」は本番障害の前で言い訳にならない。検証規律の本質はここにある — ツールが何を生成したにせよ、それをコードベースに入れると決めた判断は人間のものであり、その判断には責任が伴う。
人間の仕事は縮んだのではなく濃縮された。 タイピングは消え、判断だけが残った。
エピローグ — 検証は新しいコア能力だ
コードを生み出すコストがゼロに近づく時代、競争力は「どれだけ速く生成するか」ではなく「どれだけ信頼できる形で検証するか」だ。生成は商品になり、検証は希少になった。
この記事の一行結論: AIコードレビューは社会的な行為ではなく検証規律だ。 疑いをデフォルトに置き、機械が捕まえられるものは機械に任せ、人間の注意力は機械が原理的に見えないもの — 判断、アーキテクチャ、「これは意味をなすか」 — に集中する。
AIコードレビューチェックリスト
- 型を先に — 型チェックとビルドを通過しないPRは人間が見る価値がない
- テストを検証する — AIが書いたテストを意図的に壊して偽物かを確認する
- チケットと照合する — diffが受け入れ基準を一行ずつ満たすか見る
- スコープクリープ — 「ついで」の変更が混ざっているか見る
- 境界の点検 — すべての外部呼び出しに「失敗したら?」を問う
- セキュリティの追跡 — 信頼境界に沿ってデータフローを指でたどる
- スロップのフィルタ — 「この行を消したら何が壊れるか」を問う
- 生成者 != 検証者 — AIレビュアーをコードを生成したエージェントと分離する
- diffのサイズ — 大きなPRは分割しろ。レビュー可能性はサイズに反比例する
- 最終判断は人間 — マージボタンを押す人が責任者だ
避けるべきアンチパターン
- 表面への信頼 — コードが滑らかに見えるからと疑いを切る
- 型のスキップ — 型エラーのあるPRを人間が先に読む
- テストへの盲信 — 「テストが通る」を「検証された」と取り違える
- 自己採点 — 生成したエージェントが自分のコードをレビューする
- AI寛大主義 — AIコードを人間のコードより緩く通す
- 巨大なdiffの受容 — 800行のPRを最後まで読まずに承認する
- 責任の回避 — 「AIが書いた」を障害の言い訳に使う
次回予告
次の記事は**「AI時代のテスト戦略 — エージェントが書いたテストを信頼できるものにする方法」**だ。この記事で「AIが書いたテスト自体が検証対象だ」と言ったが、ならば信頼できるテストはどう設計すべきか。仕様を先に固める方法、ミューテーションテストでテストを検証する方法、エージェントにテストを任せるときと任せるべきでないときを扱う。
生成が無料になった世界で、最も高価なスキルは「いいえ」と言える検証の目だ。その目こそがエンジニアリングだ。
현재 단락 (1/173)
数年前まで、ソフトウェアチームのボトルネックは**コードを書くこと**だった。機能を一つ実装するには人間がキーボードの前に座って一行ずつタイプする必要があり、その時間がスケジュールの大半を占めていた。