AIが作ったコードをAIにデバッグさせる実践的方法論

AIが生成した小規模ソフトウェアは「大まかには動く」状態でも、そのまま公開するのは危険である可能性があります。エラーが出ていない段階でも潜むリスクと、それをAI自身に検証させる方法をまとめます。

AIが作るコードのバグに関する主要な研究とその結果

1. 最も包括的な査読論文(2026年3月・arXiv)

「Debt Behind the AI Boom」― シンガポール経営大学ほか

GitHub上の6,275リポジトリから30万4,362件のAI作成コミットを収集し、静的解析で各コミットの前後を比較した大規模実証研究。 arXiv

主な結果:

  • すべてのAIツールにおいて、15%以上のコミットが少なくとも1件の問題を導入している arXiv
  • 追跡した問題の24.2%が最新リビジョン時点でもまだ残存している――つまり4件に1件は修正されないまま蓄積する

AIが生成したコードに固有のバグパターン

人間が書いたコードとAIが書いたコードでは、バグの出方が違います。AIに固有のパターンを知っておくことが、効果的なデバッグの前提になります。

「動いているように見える」系

  • テストケースへの過剰適合――よくある入力には正しく動くが、その条件から外れると崩れる。人間なら「なぜそう動くか」を理解して書くが、AIはパターンマッチで書くため
  • ハッピーパス偏重――正常系は丁寧に書くが、失敗・例外・中断の処理が「とりあえず形だけある」状態になりやすい
  • 条件分岐の穴――if/elseが一見網羅されているように見えて、特定の組み合わせが抜けている

一貫性のなさ系

  • 同じ処理を別々の方法で実装――コードの前半と後半で同じ目的の処理が違う書き方になっており、片方だけバグを持つ
  • 変数名・関数名の意味のズレ――名前と実際の動作が微妙に違う。getUser() が実はDBに書き込んでいたりする
  • 設定値や定数のばらつき――タイムアウト値やリトライ回数などが、ファイルをまたいで微妙に違う数字になっている

「それっぽいが間違い」系

  • 古いAPIの使用――学習データに古いコードが多いため、非推奨・廃止済みのメソッドを自信満々に使う
  • off-by-oneエラーの多さ――ループの境界値(< vs <=、0始まりか1始まりか)のミスが人間より多い傾向
  • コピペ的な文脈ズレ――似た処理を参考にして生成するため、前の文脈の変数名・前提条件が残り込む

特に見つけにくいもの

  • 非決定的な動作――たまにだけ失敗する処理(タイミング依存・乱数依存)のケアが薄い
  • データが大きくなったときの劣化――少量データでは動くが、件数が増えると極端に遅くなる・メモリを食い尽くすアルゴリズムを平気で使う
  • 副作用の見落とし――関数が「返り値を返す」だけでなく外部状態を変えているのに、呼び出し側がそれを知らない設計

一番厄介なのは「動いているが間違っている」パターンで、テストで引っかからず、しばらく使って初めて発覚します。


エラーが出ていない段階でAIに検証させるべき項目

「エラーなし・本格テスト前」は最もAIが活きる場面です。優先度の高い検証項目と、具体的な質問の仕方をまとめます。

1. ロジックの自己矛盾チェック(最優先)

「動くが間違っている」系バグの温床。

  • 「このコードが意図通り動く前提条件を全部列挙して」
  • 「このアルゴリズムを日本語で説明して」→ 説明が怪しければロジックも怪しい
  • 「この関数の入力と出力の関係を表で整理して」

2. 境界値・エッジケースの網羅

エラーは出ないが、特定の値で壊れる系。

  • 「この関数が失敗しそうな入力値を20個挙げて」(数を多めに指定するのがコツ)
  • 「0・空・null・最大値・負の数を渡したらどうなるか、コードを読んで答えて」
  • 「ユーザーが意地悪な操作をするとしたらどんな操作か」

3. 暗黙の前提・依存関係の可視化

「呼び出し順序の前提」「グローバル状態への依存」など、コードに書かれていない制約を炙り出す。

  • 「この関数を単体で呼び出したら何が必要か、前提を全部出して」
  • 「このコード全体で、順番を間違えると壊れる処理はどこか」
  • 「外部の状態(DB・ファイル・グローバル変数)に依存している箇所を全部リストアップして」

4. 異常系・失敗時の挙動確認

ハッピーパス偏重の弱点を突く。

  • 「各関数が失敗したとき、呼び出し元はどう処理するか確認して」
  • 「処理が途中で止まったとき、データはどういう状態になるか」
  • 「エラーが握りつぶされている箇所はあるか」(try/catchで何もしていない箇所)

5. データが増えたときの挙動予測

  • 「データが1万件になったとき、計算量的に問題になる処理はあるか」
  • 「ループのネスト構造を確認して、O(n²)以上の処理がないか指摘して」

質問の仕方の共通コツ

  • ラバーダック形式――「このコードを、コードを知らない人に説明するように解説して」。AIが理解せず書いた箇所は説明が曖昧になる
  • レビュアー役を明示――「シニアエンジニアとして、このコードの問題点だけを指摘して。褒めなくていい」
  • 検証する順番――「暗黙の前提を出す → ロジックを説明させる → エッジケースを列挙 → 異常系を確認」の順が効果的

「不変条件スキャン法」――芋づる式にバグを引き出す方法論

特定の視点でコード全体を走査させると、その視点に引っかかる関連箇所が芋づる式に浮上してきます。これを意図的に使う方法論です。

ステップ1:不変条件を言語化する

「このコードが正しく動くために、常に成立していなければならない条件」を1文で定義します。

例:

  • イベントリスナーはリセット後に1つだけ存在する
  • 同一ユーザーIDのセッションは同時に1つだけ
  • この配列は常にソート済みである

コツ:「このコードの不変条件を列挙して」とAIに聞くと出発点になります。

ステップ2:その条件が破れる経路を全列挙させる

「この条件が破れるとしたら、どの処理・どのタイミングか全部出して」

ここで関連コードが自然に引き込まれ、副次的なバグが浮上することがあります。


関数単位で検証させる汎用プロンプトテンプレート

走査範囲が広がりすぎて終わらないようにするため、関数を1つずつ貼り替えて順次実行するのがコツです。

基本テンプレート

以下の関数を検査してください。

【対象関数】
[関数名・コードをここに貼る]

【検査項目】
1. この関数が正しく動くための前提条件(呼び出し前に成立すべきこと)
2. この関数が保証すべき事後条件(呼び出し後に成立すべきこと)
3. 上記の条件が破れるケースを具体的に列挙
4. 関数内で外部状態を読み書きしている箇所
5. 問題があれば指摘(なければ「問題なし」と明記)

上記の結果を見て最終的に人間が判断します。

使い方

関数を1つずつ貼り替えて順次実行するだけです。

  1. 1回目:関数Aを貼って実行
  2. 2回目:関数Bを貼って実行
  3. 全部終わったら:「上記の検査で出た問題点を一覧にして優先順位をつけて」

最後にまとめさせるプロンプト

ここまでの検査結果をまとめてください。

【出力形式】
- 問題あり:関数名・問題の概要・深刻度
- 問題なし:関数名のみ
- 修正推奨順:深刻度順に並べる

推測や補完は不要。検査済みの内容だけをまとめること。

【制約】の「外に出ない」「不明と書く」が最重要です。これを入れないとAIが勝手に呼び出し元を推測して範囲が広がります。


まとめ

AIが生成したコードのデバッグで一番大切なのは、「AIを賢い検索エンジンとして使うのではなく、情報を正確に渡した上で論理を言語化させる道具として使う」ことです。

特に効果的な流れは以下の通りです。

  1. 暗黙の前提を出す――前提が間違っていたらロジック検証が意味をなさない
  2. ロジックを説明させる――理解せず書いた箇所は説明が曖昧になる
  3. エッジケースを列挙させる――数を多めに指定する(20個など)
  4. 異常系を確認させる――失敗時にデータがどんな状態になるかを確認
  5. 関数単位で繰り返す――範囲を絞って「外に出ない」制約をつける