AIコード修正が失敗したときの切り分け|再現・差分・切り戻し
更新日:2026年10月6日
AIが提案した変更で不具合が残ったら、まず入力・期待結果・実行環境を固定します。この記事では、空のCSVで失敗する小さなコードを使い、変更前の再現、1行の修正、切り戻しによる再確認まで実行します。
対象は、ターミナルでPythonのテストを実行し、変更差分を読める開発者です。教材の不具合は意図的に作成したもので、実際のAIが生んだバグや実サービスの障害報告ではありません。
再現条件を記録する
| 項目 | 今回の固定条件 |
|---|---|
| 入力 | 空文字列。ヘッダーもないCSV |
| 仕様 | 先頭行をヘッダーとして除き、残りのレコード数を返す。空入力は0件 |
| 期待 | 0を返す |
| 修正前の実際 | StopIteration |
| 原因の仮説 | 最初の行がないのにnext(rows)を呼んでいる |
| 確認 | 同じ3テストを変更前・修正後・切り戻しに実行 |
この例はCSV件数だけを扱います。内容の型検証や金額集計はCSV集計の検算記事へ分けています。
1. 再現コードとテストを保存する
検証環境はmacOS(Darwin arm64)、Python 3.14.2です。外部ライブラリ、APIキー、ネット接続は不要です。Pythonが入った端末で python3 --version を確認し、空の作業フォルダーを作ってください。Windowsでは以下の python3 を、利用環境に応じて py に読み替えます。掲載コード・入力はすべて架空の学習用です。
以下を debug_empty.py に保存します。beforeが変更前、afterが修正後です。引数を変えるだけで同じテストを両方に適用できます。空のCSVを読み込む処理と例外を再現する教材で、外部ファイルへ書き込みません。
import csv
import io
import sys
import unittest
MODE = sys.argv[1] if len(sys.argv) > 1 else "fixed"
# Before: valid empty input raises StopIteration.
def before(text):
rows = csv.reader(io.StringIO(text))
next(rows)
return sum(1 for _ in rows)
# After: exactly one change; an empty iterator has a default value.
def after(text):
rows = csv.reader(io.StringIO(text))
next(rows, None)
return sum(1 for _ in rows)
count_records = after if MODE == "fixed" else before
class Regression(unittest.TestCase):
def test_empty_file(self):
self.assertEqual(count_records(""), 0)
def test_header_only(self):
self.assertEqual(count_records("id,amount\n"), 0)
def test_two_records(self):
self.assertEqual(count_records("id,amount\n1,100\n2,200\n"), 2)
if __name__ == "__main__":
unittest.main(argv=[sys.argv[0]], verbosity=2)
2. 変更前で失敗を再現する
python3 debug_empty.py before
検証時の要点は以下でした。終了コードは1です。
test_empty_file ... ERROR
test_header_only ... ok
test_two_records ... ok
StopIteration
Ran 3 tests
FAILED (errors=1)
空入力だけが落ち、ヘッダーのみ・2件のデータは通ります。これは「件数計算全体が壊れた」という仮説より、「先頭行がないときの扱い」を先に確認する根拠になります。
3. 差分を1行に絞って確認する
- next(rows)
+ next(rows, None)
nextに既定値Noneを渡すと、イテレーターが空でも例外の代わりに既定値が返ります。その後に残りの行を数え、0になります。この挙動はPythonのnext公式リファレンスで確認できます。ファイルが存在しない、権限がないといった別の例外を握りつぶす変更はしていません。
python3 debug_empty.py fixed
実行結果は3テストすべてok、Ran 3 tests / OK、終了コード0でした。元の正常系も通るので、空入力だけを直して他のケースを壊していないことをこの範囲で確認できます。
4. 切り戻して同じ失敗が出るか確認する
python3 debug_empty.py rollback
rollbackはこの教材ではbefore関数へ切り替える指定です。Git履歴の操作はしません。実行すると再び test_empty_file がStopIteration、FAILED (errors=1)、終了コード1でした。
| 状態 | 空入力 | ヘッダーのみ | 2件入力 | 終了コード |
|---|---|---|---|---|
| 変更前 | 失敗 | 成功 | 成功 | 1 |
| 修正後 | 成功 | 成功 | 成功 | 0 |
| 切り戻し | 失敗 | 成功 | 成功 | 1 |
この往復で、テストが修正対象の不具合に反応することを確認しました。別環境で偶然通っただけ、テストの条件を弱めただけという可能性を減らせます。掲載例の3ケース以外の正しさは保証しません。
実際のAI修正で残す記録
上の表に加え、対象ファイル、実際の変更前コミット、実行時のPython・ライブラリ版、入力の匿名化方法、標準出力・エラー出力を残します。架空のコミットIDを作業記録へ混ぜないでください。
AIにはまず原因候補を頼み、原因を確認してから範囲を限定した修正を頼みます。
空のCSV入力でStopIteration、ヘッダーのみと2行入力は成功します。仕様は空入力0件です。再現条件とテストを変えず、原因候補、候補を区別する確認方法、最小の修正案を示してください。今回は先頭行を読む箇所だけを対象にし、他の関数の整理は別提案にしてください。
実プロジェクトでは、手元の未コミット変更を残したうえで、検証用の作業場所を分けます。変更前でも失敗するならAIの変更だけが原因とは言えません。変更前で成功・変更後で失敗するなら差分を小さくして確認します。どの変更から失敗したか不明な場合は、Git bisectで良い・悪いコミットを絞れます。
共有済みコミットを打ち消す場合は、履歴に打ち消しコミットを追加するGit revertを検討します。今回の学習コマンドはGitの切り戻し動作を検証したものではありません。実際の操作前には、戻すコミットの差分と共同作業中の変更を確認してください。
再現できない場合は何を残すか
ログや入力が不足して再現できないときは、修正済みとせず、時刻、エラー、環境差、次に採取する情報を記録します。個人情報やキーをAIへ貼り付ける前に削除し、同じ問題を再現できる最小の匿名入力を作ります。
この記事の完了条件は、同じ入力・同じテストで「失敗→成功→失敗」を再現し、差分がどの条件に効いたか説明できることです。実プロジェクトでは続けて、対象モジュールの既存テストと、変更が影響する利用箇所を確認します。
Git差分レビューの手順も変更範囲の確認に使えます。
出典と確認日
- Python csv、unittest(2026年10月5日確認)
- Git bisect、Git revert(2026年10月5日確認)
- ローカル検証:2026年10月5日、Python 3.14.2 / Darwin arm64。AI提案コードの検証、Git履歴の切り戻しは未実施。


コメント