こんにちは、松山です。
これは「AI駆動開発実践」シリーズの第6篇です。
シリーズの目次
第1篇 AIプログラミングの新時代:Claude Codeとは?
第2篇 10分で始める:Claude Codeのインストールと設定完全ガイド
第3篇 基本操作:Claude Codeとの正しい対話の仕方
第4篇 AIにあなたのプロジェクトを理解させる:コンテキスト管理のテクニック
第5篇 コードリファクタリングの神器:AIに古いコードを最適化させる
深夜3時、本番環境でアラート発生。
ユーザーから機能異常の苦情が来て、ログはエラーだらけ。でも、見ても見てもさっぱりわからない…
こんな状況、すべての開発者が経験したことがあるはずです。
従来のデバッグ:
- ログを眺めてボーッとする
- あちこちにprint/console.log
- Stack Overflowで長時間検索
- 数時間かけてようやく問題を特定
Claude Codeを使うと:
- エラー情報を貼り付け
- AIが素早く原因を特定
- 修正案を提示
- 10分で問題解決
今日は、AIをあなたのデバッグアシスタントにする方法を教えます。
AIデバッグの強み
なぜAIはデバッグが得意?
1. 膨大な経験
- 数百万種類のバグパターンを見てきた
- 各種フレームワークのよくある落とし穴を熟知
- 言語特性の罠を理解している
2. 体系的な思考
- 可能性を見落とさない
- 多角的に分析
- レアなエッジケースも考慮
3. 高速学習
- あなたのコードコンテキストを理解
- 異なるファイルのロジックを関連付け
- データフローを追跡
エラーメッセージの解読
テクニック1:AIにエラーを説明させる
シーン: Pythonでエラーが出たが、意味がわからない
あなた:このエラーに遭遇しました。分析してください:
TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'
File "app.py", line 45, in calculate_total
total = subtotal + shipping_cost
File "services/pricing.py", line 120, in get_shipping_cost
return base_rate * weight
Claude:エラー情報を分析します:
【エラータイプ】
TypeError:intとNoneを加算しようとした
【発生場所】
app.py 45行目:total = subtotal + shipping_cost
【根本原因】
shipping_costがNoneで、subtotalがint
Noneに対して数学演算はできません
【トレース元】
スタックから見ると、shipping_costはget_shipping_cost関数から来ています
この関数はpricing.py 120行目で base_rate * weight を返しています
【考えられる原因】
1. base_rateがNone
2. weightがNone
3. または、get_shipping_costがある分岐でreturnしていない
【確認をお勧め】
pricing.pyのget_shipping_cost関数を確認:
- すべての分岐でreturnしていますか?
- base_rateはどこから来ますか?Noneの可能性は?
- weightパラメータは検証されていますか?
あなた:pricing.pyを確認したら、確かにある分岐でreturnしていませんでした
Claude:問題が見つかりました!修正案:
def get_shipping_cost(weight, destination):
if destination == 'local':
return 5.0
elif destination == 'domestic':
base_rate = 10.0
return base_rate * weight
elif destination == 'international':
base_rate = 25.0
return base_rate * weight
# ❌ 問題:destinationがどの分岐にも一致しない場合、returnがない
修正:
else:
# デフォルトはローカル配送料
return 5.0
# または例外をスロー:
# raise ValueError(f"Unknown destination: {destination}")
テクニック2:複雑なスタックトレースを解読
シーン: 深い再帰エラー
あなた:プログラムがクラッシュしました。スタック情報が超長いです:
[100行のスタックトレースを貼り付け]
RecursionError: maximum recursion depth exceeded
[...100行のスタック...]
Claude:スタックトレースを分析中…
【問題の特定】
再帰深度制限超過(通常は1000層)
【主要な呼び出しチェーン】(重複を除く)
process_node() → find_children() → process_node() → ...
【ループパターン】
process_nodeとfind_childrenが相互に呼び出し、無限ループを形成
【問題の特定】
ファイルtree.py:
- 45行目:process_nodeがfind_childrenを呼び出し
- 60行目:find_childrenがprocess_nodeを呼び出し
【考えられる原因】
データ構造に循環参照:
ノードAの子ノードにノードBが含まれる
ノードBの子ノードにまたノードAが含まれる
【修正案】
1. 訪問記録を追加し、重複訪問を回避:
visited = set()
if node.id in visited:
return
visited.add(node.id)
2. または再帰深度を制限:
def process_node(node, depth=0, max_depth=10):
if depth > max_depth:
raise ValueError("Tree too deep")
テクニック3:わかりにくいエラーメッセージを分析
シーン: サードパーティライブラリの謎のエラー
あなた:Celeryタスクが失敗、エラーメッセージの意味がわかりません:
kombu.exceptions.OperationalError: [Errno 111] Connection refused
at /usr/lib/python3.9/site-packages/kombu/connection.py line 458
Claude:これはCelery/RabbitMQ関連のエラーです。
【エラー解読】
OperationalError:操作エラー
Errno 111:Connection refused - 接続拒否
【意味】
Celery workerがメッセージキュー(RabbitMQ/Redis)に接続できない
【よくある原因】
1. メッセージキューサービスが起動していない
2. 設定されているアドレス/ポートが間違っている
3. ファイアウォールが接続をブロック
4. ネットワーク問題
【調査手順】
1. RabbitMQ/Redisが動作しているか確認:
sudo systemctl status rabbitmq-server
# または
redis-cli ping
2. Celery設定を確認:
# settings.py
CELERY_BROKER_URL = 'redis://localhost:6379/0'
# アドレス、ポートが正しいか確認
3. 接続をテスト:
telnet localhost 6379
# または
nc -zv localhost 6379
4. ファイアウォールルールを確認
【クイック修正】
開発環境の場合、最も可能性が高いのはメッセージキューの起動を忘れたこと:
sudo systemctl start rabbitmq-server
# または
redis-server
ログ分析テクニック
テクニック4:大量のログから問題を見つける
シーン: 5000行のログから問題を探す
あなた:ユーザーが機能異常を報告、ログファイルが大きい(5000行)。
logs/app.logを分析して、
関連するエラー情報を見つけてください
Claude:ログファイルを分析中…
【主要エラー】(時系列順)
1. 14:32:15 ERROR - Database connection timeout
→ データベース接続タイムアウト
2. 14:32:20 WARNING - Retry attempt 1/3
→ リトライ開始
3. 14:32:25 WARNING - Retry attempt 2/3
→ 2回目のリトライ
4. 14:32:30 ERROR - All retry attempts failed
→ リトライすべて失敗
5. 14:32:31 ERROR - User operation failed: create_order
→ ユーザー操作失敗:注文作成
【タイムライン分析】
14:32:15 - データベース問題が発生開始
14:32:30 - リトライ失敗
14:32:31 - ユーザーに見えるエラー発生
【根本原因】
データベース接続が不安定で、注文作成が失敗
【関連ログ】
1分前(14:31:00-14:32:00):
- データベースクエリ時間が50msから徐々に5000msに上昇
- 大量のスロークエリ警告
【提案】
1. データベース状態を確認:スロークエリ、接続数、ロック待機
2. データベースサーバーの負荷を確認
3. 問題のあるクエリの最適化または接続プールサイズの増加を検討
テクニック5:複数のログソースを関連付け
シーン: 分散システム、複数サービスのログ
あなた:ユーザーリクエストが失敗、3つのサービスが関係:
- APIサービスログ:api.log
- 注文サービスログ:order.log
- 決済サービスログ:payment.log
リクエストID:req-12345
この3つのログを関連付けて分析し、
失敗原因を見つけてください
Claude:リクエストreq-12345を関連付けて分析中…
【完全なリクエストチェーン】
1. APIサービス(api.log):
14:30:00 INFO [req-12345] 注文作成リクエスト受信
14:30:01 INFO [req-12345] 注文サービス呼び出し → order-service
2. 注文サービス(order.log):
14:30:02 INFO [req-12345] 注文作成、注文番号:ORD-9876
14:30:03 INFO [req-12345] 決済サービス呼び出し → payment-service
3. 決済サービス(payment.log):
14:30:04 ERROR [req-12345] 決済ゲートウェイタイムアウト
14:30:09 ERROR [req-12345] 決済失敗:GATEWAY_TIMEOUT
14:30:09 INFO [req-12345] 注文サービスに失敗を返す ←
4. 注文サービス(order.log):
14:30:10 ERROR [req-12345] 決済失敗、注文をロールバック
14:30:10 INFO [req-12345] 注文キャンセル:ORD-9876
14:30:10 INFO [req-12345] APIサービスに失敗を返す ←
5. APIサービス(api.log):
14:30:11 ERROR [req-12345] 注文作成失敗
14:30:11 INFO [req-12345] クライアントにエラーを返す
【問題特定】
決済サービスのサードパーティゲートウェイタイムアウト(14:30:04)
【影響チェーン】
決済タイムアウト → 注文ロールバック → リクエスト失敗
【提案】
1. 決済ゲートウェイの状態を確認
2. 決済タイムアウトのリトライメカニズムを検討
3. ユーザー体験を最適化:タイムアウト時は「失敗」ではなく「処理中」と表示
段階的な問題調査
テクニック6:二分探索法で特定
シーン: どこでエラーが発生したか不明
あなた:機能が昨日は正常で、今日は動きません。
コードに20個のコミットがあり、どの変更が原因かわかりません
Claude:二分探索法で特定します:
1. まず中間のコミット(10番目)をテスト:
git checkout commit-10
テストを実行…
あなた:10番目のコミットは正常です
Claude:問題はcommit-11からcommit-20の間です
2. 15番目のコミットをテスト:
git checkout commit-15
テストを実行…
あなた:15番目のコミットですでに問題があります
Claude:問題はcommit-11からcommit-15の間です
3. 13番目のコミットをテスト:
git checkout commit-13
あなた:13番目は正常です
Claude:問題はcommit-14またはcommit-15です
4. commit-14を確認:
git show commit-14
問題発見:commit-14で価格計算ロジックを修正し、
ゼロ除算エラーを導入
【問題のあるコミットを特定】
commit-14:"Update pricing logic"
具体的なファイル:services/pricing.py line 67
問題コード:price = base / quantity
quantityが0の時クラッシュ
テクニック7:診断コードを追加
シーン: 中間状態を知る必要がある
あなた:関数が間違った結果を返していますが、
どのステップの計算が間違っているかわかりません
Claude:診断ログを追加します:
def calculate_final_price(items, discount, tax_rate):
+ print(f"[DEBUG] Input: items={items}, discount={discount}, tax_rate={tax_rate}")
subtotal = sum(item.price for item in items)
+ print(f"[DEBUG] Subtotal: {subtotal}")
discounted = subtotal * (1 - discount)
+ print(f"[DEBUG] After discount: {discounted}")
final = discounted * (1 + tax_rate)
+ print(f"[DEBUG] Final price: {final}")
return final
一度実行して、出力を確認:
[DEBUG] Input: items=[...], discount=0.1, tax_rate=0.08
[DEBUG] Subtotal: 100.0
[DEBUG] After discount: 90.0
[DEBUG] Final price: 97.2 ← 正しい
でも、結果が間違っていると言いましたか?実際の出力を貼り付けてください
あなた:[出力を貼り付け]
[DEBUG] Final price: -97.2
Claude:問題発見!priceが負の数です。
subtotal計算を確認:
あぁ、見つけました:
subtotal = sum(item.price for item in items)
あるitem.priceが負の数です!
理由:返品商品のpriceが負の数に設定されている
修正:正の価格の商品のみを集計
subtotal = sum(item.price for item in items if item.price > 0)
実例:複雑な本番環境バグの解決
問題の説明
現象:
- たまにユーザーの注文が失敗
- 安定して再現できない
- 本番環境でのみ発生
エラーメッセージ:
IntegrityError: duplicate key value violates unique constraint
デバッグプロセス
Step 1:エラー分析
あなた:本番環境でたまにこのエラーが出ます:
IntegrityError: duplicate key value violates unique constraint "orders_order_number_key"
DETAIL: Key (order_number)=(ORD-12345) already exists
問題:
1. 本番環境でのみ発生
2. ローカルでは再現できない
3. 発生確率は約5%
Claude:エラー情報を分析します:
【エラータイプ】
データベースユニーク制約違反
【具体的な問題】
注文番号(order_number)の重複
【考えられる原因】
1. 注文番号生成アルゴリズムに問題(十分にユニークでない)
2. 並行リクエストによる(競合状態)
3. リトライメカニズムによる重複作成
【なぜ本番環境だけ?】
本番環境の特徴:
- 高並行性(ローカルにはない)
- 複数インスタンス(ローカルは単一インスタンス)
→ ヒント:並行性問題の可能性が高い
【調査推奨】
注文番号生成コードを確認、
特に並行安全性の問題に注意
Step 2:コード確認
あなた:注文番号生成コード:
def generate_order_number():
today = datetime.now().strftime('%Y%m%d')
count = Order.objects.filter(
created_at__date=datetime.now().date()
).count()
return f"ORD-{today}-{count+1:04d}"
Claude:問題が見つかりました!これは典型的な競合状態です:
【問題シナリオ】
2つの並行リクエストが同時に実行:
時刻T1:
- リクエストA:count = 10
- リクエストB:count = 10(同じく10!)
時刻T2:
- リクエストA:ORD-20250109-0011を生成
- リクエストB:ORD-20250109-0011を生成(重複!)
時刻T3:
- リクエストA:データベースに書き込み ✓
- リクエストB:データベースに書き込み ❌ ユニーク制約違反
【なぜ本番環境だけ?】
本番環境は並行性が高く、2つのリクエストが同時に到着する確率が高い
【解決策】
方法1:データベースシーケンスを使用
CREATE SEQUENCE order_number_seq;
def generate_order_number():
today = datetime.now().strftime('%Y%m%d')
seq = connection.execute("SELECT nextval('order_number_seq')").scalar()
return f"ORD-{today}-{seq:04d}"
方法2:UUIDを使用
import uuid
def generate_order_number():
return f"ORD-{uuid.uuid4().hex[:12].upper()}"
方法3:データベースレベルの自動増分+プレフィックス
# モデル
order_number = models.CharField(unique=True, blank=True)
def save(self, *args, **kwargs):
if not self.order_number:
super().save(*args, **kwargs) # まず保存してIDを取得
today = self.created_at.strftime('%Y%m%d')
self.order_number = f"ORD-{today}-{self.id:06d}"
super().save(update_fields=['order_number'])
推奨:方法2(UUID)、最もシンプルで完全に衝突なし
Step 3:修正の検証
あなた:UUID方式を採用、デプロイ後もたまにエラーが出ますが、
エラーメッセージが変わりました:
IntegrityError: duplicate key value violates unique constraint "orders_pkey"
Claude:これは新しい問題:主キー(ID)の衝突
原因:複数のアプリケーションインスタンスが異なるID生成器を使用
【問題】
自動増分IDを使用している場合、複数インスタンスが同じIDを生成する可能性
【確認】
データベースのID列の定義は?
- 自動増分(SERIAL):衝突しないはず
- 手動指定:衝突の可能性
モデル定義を確認:
あなた:モデルはカスタムID生成を使用:
id = models.IntegerField(primary_key=True)
def save(self):
if not self.id:
max_id = Order.objects.aggregate(Max('id'))['id__max'] or 0
self.id = max_id + 1
super().save()
Claude:また競合状態です!
2つのインスタンスが同時に:
- max_idをクエリ:100
- 新しいIDを計算:101
- 書き込み:衝突!
【修正】
データベース自動増分を使用:
id = models.AutoField(primary_key=True)
カスタムsaveロジックを削除
こうすればデータベースがID一意性を保証
Step 4:完全解決
修正後:
- UUID注文番号を使用
- データベース自動増分IDを使用
- 冪等性チェックを追加(リトライの重複を防止)
デプロイして3日間観察:
✓ 0エラー
✓ 問題解決!
デバッグテクニックまとめ
クイック診断チェックリスト
- [ ] エラーメッセージは完全?(スタックトレース、エラータイプ)
- [ ] 安定して再現できる?再現手順は?
- [ ] 環境の違い?(開発vs本番)
- [ ] 最近の変更?(git log)
- [ ] 関連ログ?(複数サービス)
- [ ] データ状態?(データベース、キャッシュ)
AIデバッグ対話テンプレート
【問題の説明】
現象:XXX機能が動作しない
再現:YYYの手順
【エラー情報】
[完全なエラースタック]
【環境情報】
- システム:XXX
- 言語/フレームワークバージョン:YYY
- データベース:ZZZ
【試したこと】
- 試行1:無効
- 試行2:無効
【関連コード】
[主要コードを貼り付け]
考えられる原因を分析してください
次回予告
次の記事『ゼロから始める:Claude Codeで完全なアプリケーションを構築』では、以下を教えます:
- 要件から本番公開までの完全フロー
- AIに段階的に機能を実装させる方法
- フロントエンドとバックエンドの連携テクニック
- 実戦:RESTful APIの構築
デバッグでバグを解決し、開発で価値を創造。次回はゼロから完全なアプリケーションを作ります!
💬 インタラクションタイム
あなたが遭遇した最も難しいバグは何でしたか?
- A. 並行性/競合状態の問題
- B. メモリリーク
- C. 本番環境でのみ発生するバグ
- D. サードパーティライブラリの謎の問題
小さな課題: 未解決のバグを1つ見つけて、Claude Codeで分析してみてください!
このブログをフォローして、デバッグマスターになりましょう! 🚀
このシリーズについて
これは「AI駆動開発実践」シリーズの第6篇です。今日はAIデバッグを学びました。次回はゼロから完全なアプリケーションを構築します!
新しいプロジェクトを作る準備はできましたか?次回もお楽しみに! 👋

