Post
JA EN

AIがコーディング中に間違うのにレビューで発見できる理由:研究と技術的解説

AIがコーディング中に間違うのにレビューで発見できる理由:研究と技術的解説

概要

AIにコードを書かせると、時々エラーや非効率な実装が混入します。しかし興味深いことに、そのコードを同じAIにレビューさせると、自分で問題を発見して修正案を出してくれることがよくあります。本記事では、この現象の背後にある仕組みを、最新の研究知見(Kamoi et al., 2024; Pan et al., 2024)とTransformerアーキテクチャの技術的動作原理から解説します。対象読者はAIを開発業務で活用しているソフトウェアエンジニアです。

本記事について

本記事は、LLMの自己修正に関する最新研究(2024-2025年)の知見と、Transformerアーキテクチャの技術的動作原理を統合した技術解説です。研究で明らかになった事実と、技術的解釈を組み合わせて、実務での活用方法まで説明します。

対象読者: AIを開発業務で活用しているソフトウェアエンジニア

本記事の構成:

  • 現象の概要と要因
  • 技術的な動作原理の解説
  • 最新研究からの重要な知見
  • 実践的な活用方法

現象の概要:なぜAIは「書く時」と「見る時」で精度が変わるのか

まず、この現象が起こる主な要因を整理しましょう。

1. コンテキストウィンドウと注意の分散

コーディング中、AIは複数の要素を同時に考慮しています。アーキテクチャ設計、ロジック実装、構文の正確性、エッジケース処理、パフォーマンス最適化、命名規則の統一など。このマルチタスク状態では、細かいミスを見落としやすくなります。

一方、レビュー時は既存のコードを分析することに集中できるため、より注意深く細部を観察できます。

2. 生成フェーズと評価フェーズの違い

これが最も重要なポイントです。 コードを生成する時と評価する時では、AIの内部的な処理プロセスが根本的に異なります。詳しくは後述します。

3. プロンプトの明示性

「コードを書いて」というリクエストは比較的オープンエンドですが、「このコードをレビューして」というリクエストは、エラーや改善点を探すという明確な目的を持っています。この目的の明確さが、AIの注意を問題発見に集中させます。

4. 検証タスクと生成タスクの難易度差

生成時は「正しいコードを作る」という難しいタスクですが、評価時は「間違いパターンを探す」という、比較的簡単なタスクになります。これは後述する研究でも裏付けられています。

5. 段階的な推論の可能性

レビュー時には、コード全体を見渡して段階的に推論できます:

  1. このコードは何をしようとしているのか
  2. 実際に何をしているのか
  3. ギャップや問題はあるか

生成時は逐次的に進むため、この俯瞰的な視点が持ちにくいのです。

技術解説:生成フェーズと評価フェーズの違い

ここからは、Transformerアーキテクチャの動作原理に基づいて、生成と評価の違いを技術的に解説します。

生成フェーズの仕組み

言語モデルは自己回帰的(autoregressive)に動作します(Vaswani et al., 2017)。これはTransformerアーキテクチャの基本的な特性です。

1. 逐次的な予測

一度に1トークンずつ、「次に来る最も確率の高い単語/記号」を予測します。

1
2
3
def calculate_sum(    次は 'numbers' が来る確率が高い
                     次は ')' が来る
                     次は ':' が来る...

既に生成したトークンが次の予測のコンテキストになります。

2. パス依存性(技術的解釈)

一度あるトークンを生成すると、そのトークンが次の予測の入力になります。これにより、最初の選択が後続の生成に影響を与える「パス依存性」が生じます。

例:変数名を total_sum と書き始めたら、後続のコードでもその名前を使い続ける傾向があります。たとえ途中で total_count の方が適切だと「判断」しても、既に生成した内容との一貫性を保とうとします。

3. 局所的な最適化(技術的解釈)

各ステップで「今この瞬間に最も良さそうな」選択をしますが、それが全体として最適とは限りません。いわば「木を見て森を見ず」状態になりやすいのです。

4. コミットメントの連鎖(実例)

1
2
3
4
5
6
# 最初に間違った前提でコードを書き始めると...
def process_data(items):
    result = []  # listを使うとコミット
    for item in items:
        result.append(item * 2)  # list前提で続く
    return result[0]  # ここで論理エラー

最初の result = [] という選択が、後続のコード構造を規定してしまいます。全要素を処理したのに最初の要素だけ返すという矛盾が生じても、既に生成した流れに引きずられます。

評価フェーズの仕組み

一方、レビュー時は異なるアプローチが可能になります。

1. 完全なコンテキスト

コード全体が既に存在し、全体を一度にアテンション(attention)の対象にできます。開始と終了、入力と出力の関係を同時に見渡せるのです。

2. 双方向的な推論(技術的解釈)

1
2
3
4
5
レビュー時の思考プロセス:
「この関数は何をする?」    →  関数名とdocstringを見る
「実装は意図と一致する?」  →  ロジックを追う
「エッジケースは?」        →  条件分岐を確認
「変数名は一貫している?」  →  全体をスキャン

前から後ろだけでなく、後ろから前への推論も可能です。

3. 制約の確認

「このコードはXをすべき」という明確なゴールがあるため、それに照らして評価できます。生成時の「何を書くべきか」より、評価時の「これは正しいか」の方が判断しやすい問題です。

4. パターンマッチング

既知のバグパターンを探すことに集中できます:

  • off-by-one error
  • null pointer exception
  • type mismatch
  • division by zero

具体例で比較

生成フェーズでの失敗例

プロンプト: 「リストの平均を計算する関数を書いて」

AIの処理(簡略化):

1
2
3
4
5
6
7
→ def が必要
→ 関数名... calculate_average
→ 引数... numbers
→ 合計を計算... sum(numbers)
→ 長さで割る... / len(numbers)
→ 返す... return
→ 完成!

この過程で「numbersが空リストの場合」を考慮し忘れる可能性があります。各ステップで「正しい次のトークン」を生成することに集中しているためです。

評価フェーズでの発見

プロンプト: 「このコードをレビューして」

AIの処理:

1
2
3
4
5
→ 関数全体を読む
→ 入力の型を確認:リスト
→ エッジケースを考える:空リスト?
→ len(numbers)が0の場合、ZeroDivisionErrorになる!
→ 問題発見

評価時は、コード全体を見た上で「何が悪いか」を探すため、発見しやすいのです。

Transformerアーキテクチャの観点から

技術的な観点では、言語モデルの内部動作として(Vaswani et al., 2017):

  • 生成時: 左から右への因果的attention(causal attention)のみ使用。未来のトークンは見えない(causal maskingにより制限)
  • 評価時: 完成したテキスト全体に対してattentionを適用できる。全トークン間の関係を同時に評価可能

因果的attentionは、GPTのような自己回帰型モデルで使用される仕組みで、各トークンが自分より前のトークンにしかアクセスできないようにマスキングします。これは生成時に未来の情報を「見る」ことを防ぐために不可欠ですが、同時に全体的な文脈を把握する能力を制限します。

最新研究からの重要な知見

ここまでは技術的な動作原理の解説でしたが、最新の研究結果も理解しておくことが重要です。

「認識することは避けることより簡単」という仮説の真実

Kamoi et al. (2024) の “When Can LLMs Actually Correct Their Own Mistakes?” という研究は、自己修正に関する重要な発見をしています。

この仮説(Saunders et al., 2022)は、長い間自己修正研究の基礎となっていましたが、最新の研究では以下のことが明らかになっています:

この仮説は条件付きでのみ真実です。

研究の主な発見

  1. 純粋な自己修正は限定的

    外部ツールやフィードバックなしで、LLMが自分の間違いを単純なプロンプトだけで修正することは、一般的なタスクでは成功していません。

  2. 検証が簡単なタスクでのみ有効

    自己修正が機能するのは、「検証タスクが元のタスクより大幅に簡単」な特定のタスクに限られます。

  3. 外部フィードバックが重要

    コード実行結果、unit testの結果、コンパイラエラーなどの外部フィードバックがある場合、自己修正は効果的に機能します。

  4. ファインチューニングの効果

    大規模なファインチューニングを行うことで、自己修正能力を向上させることができます。

コード生成における自己修正の実態

Pan et al. (2024)Chen et al. (2024) の研究も合わせると、コード生成の文脈では以下のように整理できます:

成功するケース:

  • unit testの実行結果がある
  • コンパイラエラーメッセージがある
  • linterの指摘がある
  • 実行時エラーのトレースがある

限定的なケース:

  • 単純なプロンプトだけでレビューを求める(本記事で説明している現象)
  • 明らかな構文エラーやロジックミス

失敗するケース:

  • 複雑なロジックエラー
  • パフォーマンス問題
  • セキュリティ脆弱性
  • 外部フィードバックなしの自己発見

実務への示唆

これらの研究結果から、実務では以下の理解が重要です:

  1. 単純なレビュー依頼は限定的な効果 - 本記事で説明した現象は存在しますが、万能ではありません
  2. 外部検証の重要性 - テスト実行、コンパイル、静的解析などの客観的なフィードバックが不可欠
  3. イテレーションの価値 - 生成→検証→修正のサイクルを回すことで品質が向上

実践的な活用法

研究知見と技術的理解を踏まえた、実務で使えるワークフローを紹介します。

基本ワークフロー

1
2
3
4
5
6
7
8
9
1. コードを生成させる
   ↓
2. 自動テスト/静的解析を実行
   ↓
3. 結果をAIにフィードバック
   ↓
4. AIにレビュー+修正を依頼
   ↓
5. 必要に応じて繰り返し

レベル別の実装方法

Level 1: 基本的なレビュー依頼(限定的な効果)

1
2
3
プロンプト例:
「このコードをレビューして、問題点を指摘してください。
特にエッジケース、エラーハンドリング、パフォーマンスの観点で。」

効果が期待できる問題:

  • 明らかな構文エラー
  • 単純なロジックミス
  • 基本的なエッジケースの見落とし

効果が期待できない問題:

  • 複雑なロジックエラー
  • パフォーマンス問題
  • セキュリティ脆弱性

Level 2: 外部フィードバック活用(推奨)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 1. コード生成
code = ai.generate("リストの平均を計算する関数")

# 2. テスト実行
test_result = run_tests(code)

# 3. 結果をフィードバック
improved_code = ai.improve(
    code=code,
    feedback=f"""
    テスト結果:
    {test_result}

    エラーがある場合は修正してください。
    """
)

効果が期待できる問題:

  • テストで検出可能なすべてのバグ
  • コンパイルエラー
  • 実行時エラー

Level 3: CI/CDパイプライン統合(本格運用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# GitHub Actions例
- name: Generate Code
  run: ai-generate --prompt "$"

- name: Run Tests
  run: pytest tests/
  continue-on-error: true

- name: Static Analysis
  run: |
    pylint src/
    mypy src/

- name: AI Review and Fix
  if: failure()
  run: |
    ai-review \
      --code src/ \
      --test-results test-results.xml \
      --lint-results pylint-results.txt \
      --auto-fix

- name: Re-run Tests
  run: pytest tests/

効果的なプロンプト設計

研究結果を踏まえた効果的なプロンプトの構成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[コンテキスト]
以下のコードは○○を実行するための関数です。

[コード]
{生成されたコード}

[外部フィードバック](重要!)
テスト結果:{test_output}
静的解析結果:{lint_output}
実行結果:{execution_output}

[依頼内容]
1. 上記のフィードバックを分析してください
2. 問題がある場合、原因を特定してください
3. 修正したコードを提供してください
4. 修正内容を簡潔に説明してください

アンチパターン

避けるべき使い方:

単純なレビュー依頼のみ

1
2
「このコード、問題ないですか?」
→ 限定的な効果しかない

外部検証なし

1
2
生成 → レビュー → 完了
→ 複雑な問題は見逃される

無限ループ

1
2
生成 → レビュー → 修正 → レビュー → 修正 → ...
→ 改善しない場合は要件を見直す

推奨パターン

1
2
生成 → テスト実行 → 結果フィードバック → レビュー+修正 → 再テスト
→ 外部検証を挟む

ツール活用例

実際に使えるツールの組み合わせ:

開発環境:

  • GitHub Copilot / Cursor / Claude Code:生成
  • pytest / jest:テスト
  • pylint / ESLint:静的解析
  • AI API:レビュー+修正

自動化:

  • GitHub Actions / GitLab CI:CI/CD
  • pre-commit hooks:コミット前チェック
  • Renovate:依存関係更新

まとめ

AIがコーディング中に間違うのにレビューで発見できる現象は、言語モデルの動作原理に由来する特性です。

技術的理解

  • 生成フェーズ: 自己回帰的、逐次的、局所的最適化
  • 評価フェーズ: 全体把握、双方向推論、パターンマッチング
  • Transformer: 因果的attentionによる制約

研究からの知見

  • 純粋な自己修正能力には限界がある(Kamoi et al., 2024)
  • 外部フィードバックが効果的(Pan et al., 2024; Chen et al., 2024)
  • 検証が簡単なタスクでのみ有効

実務での活用

効果的なワークフローの要点:

  1. 生成と評価を分離 - 別のプロンプトで実行
  2. 外部検証を必須化 - テスト、静的解析、実行確認
  3. フィードバックループ - 結果を基に改善
  4. 限界を理解 - 複雑な問題は人間がレビュー

これは人間が文章を書く時と校正する時の違いにも似ています。書いている最中は流れに乗って進みますが、後で読み返すと明らかな誤字に気づきます。しかし、複雑な論理エラーは見落とすことがあります。同様に、AIも外部の検証手段があってこそ、より確実に問題を発見できるのです。

実務で最も重要なポイント: 単純なレビュー依頼だけに頼らず、テスト実行やlintなどの客観的なフィードバックを組み合わせることで、AIの自己修正能力を最大限に引き出せます。

関連記事

参考資料

学術論文 (Academic Papers)


本記事の内容は2025年10月時点の研究に基づいています。AI技術と研究は急速に進展しているため、最新の論文も参照することをお勧めします。

This post is licensed under CC BY 4.0 by the author.