AIによる破壊的修正ループを防ぐ変更管理と回帰テスト
AIへ一つの不具合修正を依頼した結果、すでに正常だった別の機能が壊れることがあります。
壊れた箇所を再びAIへ直させると、今度は最初に修正した箇所が元へ戻る。修正回数は増えているのに、完成へ近づかず、同じ範囲を行き来します。
この記事では、この状態を破壊的修正ループと呼びます。
原因は、仕様書やプロンプトの不足だけではありません。仕様と設計を整えても、AIが影響範囲を読み違えたり、現在の依頼を優先して以前の正常動作を失わせたりする可能性は残ります。
必要なのは、AIに一度も間違わせないことではなく、壊れた変更を検出し、完成品へ通さない変更管理です。
破壊的修正ループとは
典型的には、次の順序で発生します。
不具合Aを修正する
↓
正常だった機能Bが壊れる
↓
機能Bを修正する
↓
不具合Aが復活する、または機能Cが壊れる
↓
同じ範囲の修正を続ける
これは、仕様変更を繰り返しながら価値を増やす反復開発とは異なります。
| 良い反復 | 破壊的修正ループ |
|---|---|
| 前回までの正常動作を維持する | 以前の正常動作が失われる |
| 新しい価値が積み上がる | 同じ箇所を行き来する |
| 成功条件が固定されている | 会話に合わせて成功条件が動く |
| 検証後の変更だけを採用する | 修正できたように見えた時点で採用する |
重要なのは、修正した回数ではありません。前回までの成果が残っているかです。
なぜ仕様書やプロンプトだけでは防げないのか
破壊的修正ループには、少なくとも次の原因があります。
仕様に不足がある
条件、例外、データの関係が書かれていなければ、AIは不足部分を推測します。この場合は、仕様そのものの修正が必要です。
仕様が現在の作業へ渡っていない
仕様書が存在しても、現在の修正で参照されていなければ変更を拘束できません。会話の長期化、チャットの移動、別のAIへの引き継ぎなどによって、以前の決定より目の前の依頼が強く扱われることがあります。
変更範囲が固定されていない
「この不具合を直す」だけでは、変更してよいファイルや処理が決まりません。AIが問題解決のために、想定外の共通処理や周辺機能まで変更する場合があります。
正常動作を守る判定がない
仕様書は正しい状態を説明しますが、変更後にもその状態が残っていることを保証しません。差分確認やテストがなければ、壊れたコードも採用できます。
AIの出力は完全には固定できない
仕様、設計、プロンプトを詳しくしても、影響範囲の読み違いや指示の見落としをゼロにはできません。
したがって、対策をプロンプトの改善だけに限定すると、最後に残る誤りを止められません。
修正前に固定する9項目
AIへコードを変更させる前に、次の情報を固定します。
- 今回変更する仕様
- 今回も維持する仕様
- 変更してよいファイルと範囲
- 参照する仕様書、コード、データ、ログ
- 変更してはいけない処理
- 修正対象に直接関係するテスト
- 影響が考えられる周辺機能のテスト
- 変更前後で確認する差分
- 判断材料が不足した場合の停止条件
これはAIへ渡す長文プロンプトではなく、作業を判定するための骨格です。
例えば、次のように記録します。
目的:
請求書一覧の検索条件を修正する
維持する動作:
確定済み請求書の表示条件
契約者ごとのアクセス制御
CSV出力の検索条件
変更可能範囲:
invoices.php
invoices_search.php
参照資料:
請求書検索の仕様書 v2
invoices_search.php の既存ロジック
変更禁止:
請求確定処理
権限判定の共通関数
直接テスト:
請求書一覧の検索テスト
周辺テスト:
CSV出力テスト
権限別表示テスト
確認する差分:
検索条件のクエリ生成部分
権限判定の呼び出し箇所
停止条件:
共通関数の変更が必要になった場合
仕様書と既存テストの期待値が矛盾した場合
「何を直すか」と同時に、「何を残すか」「どこで止まるか」を明記することが重要です。
実際の変更は8段階で進める
前節の骨格を、実際の修正作業では次の順序で使います。
1. 維持する動作を確認する
修正前に、現在の正常動作を画面、テスト、ログなどで確認します。修正後だけを見ても、以前の正解が失われたかどうかは判断できません。
2. 対象テストを修正前に実行する
変更対象に関係するテストを実行し、修正前の基準を残します。既知の不具合で失敗するテストと、維持すべき成功テストを区別します。
3. 変更範囲を限定する
変更可能なファイルと処理をAIへ明示します。範囲外の変更が必要になった場合は、自動的に広げず、一度停止して理由を確認します。
4. 限定した範囲だけを修正する
一度に複数の問題を直さず、一つの目的に対する最小の変更を行います。変更が小さいほど、原因と影響を追跡しやすくなります。
5. 差分を確認する
テストを実行する前に、変更されたファイルと内容を確認します。
git status --short
git diff --stat
git diff -- path/to/changed_file.php
確認するのは、コードが読みやすいかだけではありません。
- 指定していないファイルが変わっていないか
- 条件式や権限判定が削除されていないか
- 例外処理やログが失われていないか
- 修正目的と無関係な整理が混ざっていないか
目的外の変更があれば、テストに合格してもそのまま採用しません。
6. 直接テストと周辺テストを実行する
テストは段階的に実行します。
- 変更した機能に直接関係するテスト
- 影響が考えられる周辺機能のテスト
- 区切りや公開前に行う全体の回帰テスト
毎回すべてのテストを実行すると、確認時間が長くなります。一方、直接テストだけでは周辺への破壊を見逃します。
変更内容に応じて確認範囲を広げることが必要です。
7. 同じ修正を繰り返したら停止する
同じ箇所を二度、三度と修正している場合、追加の修正より先に原因を見直します。
- 仕様とテストの期待値が矛盾していないか
- 共通処理の責務が広すぎないか
- 修正対象を誤っていないか
- 必要なログや再現条件が不足していないか
- 一つの変更へ複数の目的を混ぜていないか
停止条件がなければ、AIは新しい修正案を出し続けられます。しかし、案が増えることと正解へ近づくことは同じではありません。
8. 区切りで全体の回帰テストを実行する
直接テストと周辺テストを通過した変更をまとめ、公開前や機能単位の区切りで全体テストを実行します。
私が業務管理アプリを開発していた時点では、Playwrightの自動テストは106件ありました。件数そのものより、変更に応じて直接、周辺、全体の三段階に分けることが、確認時間と安全性の両立に有効でした。
AIへの依頼文にも判定基準を含める
AIへは、修正の実行だけでなく、変更範囲と検証結果の報告も求めます。
次の不具合を、指定した変更可能範囲だけで修正してください。
修正前に、維持する仕様と対象テストを確認してください。
範囲外の変更が必要な場合は変更せず、必要な理由を報告してください。
修正後は次を報告してください。
1. 原因
2. 変更したファイルと処理
3. 維持した仕様
4. 実行したテストと結果
5. 確認できていない範囲
仕様、コード、テストが矛盾して判断できない場合は、推測で変更せず停止してください。
この指示だけで誤りがなくなるわけではありません。目的は、AIの回答を正解とみなすことではなく、後から人間が判定できる材料を揃えることです。
ループエンジニアリングとの関係
ループエンジニアリングは、単にAIへ何度も修正させることではありません。
IBMの解説では、AIエージェントが実行結果を観察し、判断と修正を繰り返して目標へ進むワークフローを設計する手法として説明されています。
有効なループには、少なくとも次の要素が必要です。
- 目標
- 成功条件
- 観察する結果
- 次へ進む判定
- 停止条件
- 人間が判断する位置
破壊的修正ループは、ループエンジニアリングそのものではありません。検証条件や停止条件が不足した、制御されていないループです。
まとめ
AIによる破壊的修正ループを防ぐために必要なのは、完璧なプロンプトだけではありません。
- 変更する仕様と維持する仕様を分ける
- 変更可能範囲を固定する
- 修正前の正常動作を基準として残す
- 差分で目的外の変更を確認する
- 直接、周辺、全体の順にテストする
- 判断材料が不足した場合の停止条件を決める
- 壊れた変更を完成品として採用しない
AIが壊れたコードを一度も生成しない状態を作るのは困難です。
しかし、壊れた変更を検出し、通さない工程は設計できます。
AIと開発するときに守るべきものは、コードの形ではありません。そのコードによって、すでに実現できていた正しい動作です。