debugging について
debuggingは、AIエージェント向けの体系的なデバッグスキルです。再現、原因特定、修正、検証の手順で、多様な言語や実行環境の並行処理・メモリ・性能の問題にも対応します。
主な機能
- 01障害の再現と範囲の絞り込み
- 02根本原因の追跡と修正
- 03変更の検証と回帰テストの追加
- 04並行処理・メモリ・性能問題の調査
活用シーン
- 01スタックトレースから例外箇所を特定
- 02並行コードの競合状態を調査
- 03増え続けるメモリ使用量を診断
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ seb1n/ awesome-ai-agent-skills --skill debuggingインストール後、Claude Code で/debuggingを入力して読み込まれたか確認します。
詳細
デバッグ
このスキルは、AI エージェントがソフトウェアのバグを診断して解決するための体系的な方法を提供します。修正を推測するのではなく、再現、切り分け、診断、修正、検証という構造化されたプロセスに従って、根本原因を特定し、信頼性の高い修正を行います。複数の言語やランタイム環境において、ロジックエラー、実行時例外、競合状態、メモリーリーク、パフォーマンスの低下など、幅広い種類のバグに対応します。
ワークフロー
-
問題を再現します。 バグが実際に観測でき、繰り返し発生することを確認します。正確なエラーメッセージ、スタックトレース、ログ出力、または予期しない動作の説明を収集します。問題を引き起こす最小限の入力または手順を特定します。バグが断続的に発生する場合は、発生頻度と、発生に関係する環境条件(負荷、タイミング、特定のデータ)を記録します。
-
障害箇所を切り分けます。 スタックトレース、エラーメッセージ、コード構造を使って、原因となっているコードの範囲を絞り込みます。障害が発生した箇所からデータフローをさかのぼり、値がどこで想定と異なったのかを特定します。コンポーネントをスタブ化またはバイパスした状態でもバグが続くかを確認し、無関係なコードパスを除外します。大規模なコードベースでは、二分探索の手法を使います。システムの半分を無効にしてバグが引き続き発生するかを確認し、これを繰り返します。
-
根本原因を診断します。 問題のある範囲を特定したら、コードが誤動作する正確な理由を明らかにします。よくある根本原因には、入力に関する誤った前提(null、空、範囲外)、並行スレッドによる状態の変更、古くなったキャッシュやメモ化された値、演算子の優先順位の誤り、非同期呼び出しでの await の欠落、依存関係のバージョン不整合などがあります。根本原因とその症状を区別します。NullPointerException は症状であり、根本原因は数回前の関数呼び出しでの検証漏れである可能性があります。
-
修正を作成して適用します。 副作用を起こさずに根本原因に対処できる、最小限の変更を加えます。共有インターフェースを変更する場合は、すべての呼び出し元を確認して互換性を確保します。観測された単一の障害だけに対処する狭い修正よりも、エラーの種類全般に対応する防御的な修正(例:入力検証の追加)を優先します。
-
修正を検証し、再発を防止します。 再現手順をもう一度実行し、バグが解決したことを確認します。以前失敗した状況を再現するテストケースを作成または更新し、バグが気付かれないまま再発しないようにします。既存のテストも引き続き通ることを確認します。重要な処理経路で発生したバグの場合は、今後同様の問題を検出するため、ログ記録やモニタリングの追加を検討します。
対応技術
| カテゴリー | ツールと手法 |
|---|---|
| スタックトレース | Python のトレースバック、Java/JS のスタックトレース、Go のパニック出力、Rust のバックトレース |
| ロギング | Python の logging、JavaScript の console、構造化 JSON ログ |
| デバッガー | pdb / ipdb、Chrome DevTools、gdb / lldb、dlv (Go) |
| プロファイリング | cProfile、py-spy、Chrome の Performance タブ、pprof |
| メモリー分析 | tracemalloc、heapq、Valgrind、Chrome の Heap Snapshots |
| 並行処理 | スレッドダンプ、asyncio のデバッグモード、Go の競合検出器 (-race) |
使い方
次の入力のうち、1 つ以上を提供してください。
- 問題のあるコードを含むコードスニペットまたはファイルパス。
- エラーメッセージまたはスタックトレース — 要約ではなく、出力全体を貼り付けてください。
- 予期しない動作の説明 — 何を予期していたか、実際に何が起きたか。
- 再現手順 — バグを引き起こす正確なコマンド、入力、またはユーザー操作。
- ログ — 障害の発生前と発生中の関連するログ出力。
提供するコンテキストが多いほど、エージェントはより迅速かつ正確に問題を診断できます。
例
例 1 — Python の非同期コードにおける競合状態のデバッグ
バグ報告:「応答から user_profile フィールドが抜けることがありますが、高負荷時に限られます。」
src/api/handler.py のバグのあるコード:
import asyncio
import aiohttp
user_cache = {}
async def fetch_user(session, user_id):
if user_id in user_cache:
return user_cache[user_id]
async with session.get(f"https://api.example.com/users/{user_id}") as resp:
data = await resp.json()
user_cache[user_id] = data
return data
async def build_response(user_id):
async with aiohttp.ClientSession() as session:
profile, orders = await asyncio.gather(
fetch_user(session, user_id),
fetch_orders(session, user_id),
)
return {"user_profile": profile, "orders": orders}
エージェントによる診断:
user_cache 辞書は、同期処理を行わずに複数のコルーチンからアクセスされる共有の可変状態です。同じ user_id に対するリクエストが同時に 2 つ到着すると、両方ともキャッシュミスとなり、両方が HTTP リクエストを送信します。その結果、2 回目の書き込みで、一部が異なるオブジェクトによって 1 回目の内容が上書きされる可能性があります。さらに重要なのは、並行処理中の辞書の変更により、Python 3 では RuntimeError が発生する可能性があることです。
根本原因: 並行処理のコンテキストにおける、同期されていない共有の可変状態です。
修正:
import asyncio
import aiohttp
user_cache = {}
cache_locks = {}
global_lock = asyncio.Lock()
async def fetch_user(session, user_id):
async with global_lock:
if user_id not in cache_locks:
cache_locks[user_id] = asyncio.Lock()
lock = cache_locks[user_id]
async with lock:
if user_id in user_cache:
return user_cache[user_id]
async with session.get(f"https://api.example.com/users/{user_id}") as resp:
data = await resp.json()
user_cache[user_id] = data
return data
キーごとのロックにより、特定のユーザーを取得するコルーチンは一度に 1 つだけになります。それ以外のコルーチンは待機し、その後キャッシュから読み取ります。
例 2 — Node.js サーバーにおけるメモリリークのデバッグ
バグ報告:「サーバーのメモリ使用量が 24 時間で 150 MB から 2 GB に増え、その後 OOM でクラッシュします。」
src/server.js のバグのあるコード:
const express = require("express");
const app = express();
const requestLog = [];
app.use((req, res, next) => {
requestLog.push({
method: req.method,
url: req.url,
headers: { ...req.headers },
timestamp: new Date(),
});
next();
});
app.get("/health", (req, res) => {
res.json({ status: "ok", requestCount: requestLog.length });
});
app.listen(3000);
エージェントによる診断:
requestLog 配列はモジュールレベルの変数で、上限なく増え続けます。受信するすべてのリクエストについて、リクエストヘッダーの完全なコピーを含むオブジェクトが追加されます。1 分あたり 1000 件のリクエストがあり、ヘッダーの平均サイズが 2 KB の場合、1 日あたり約 2.8 GB が蓄積されます。これは報告された OOM 発生までの時間と一致します。
根本原因: 削除やローテーションが行われない、上限のないメモリ内配列です。
修正:
const express = require("express");
const app = express();
const MAX_LOG_SIZE = 10000;
const requestLog = [];
app.use((req, res, next) => {
if (requestLog.length >= MAX_LOG_SIZE) {
requestLog.shift();
}
requestLog.push({
method: req.method,
url: req.url,
timestamp: new Date(),
});
next();
});
主な変更点:(1)配列のサイズを固定の上限に制限し、最も古いエントリーを削除します。(2)ヘッダー全体を保存するのをやめ、必要な情報だけを記録します。(3)本番環境では、メモリ内配列を適切なロギングパイプライン(例:ログファイルへの書き込みや外部サービスへの送信)に置き換えます。
検証: autocannon -d 60 http://localhost:3000/health で負荷テストを実行し、process.memoryUsage() でメモリを監視します。メモリは直線的に増加せず、上限サイズで横ばいになるはずです。
ベストプラクティス
- スタックトレース全体を下から上に読みます。 根本原因は、多くの場合、最上位の例外ではなく、最も深いアプリケーションフレームにあります。フレームワークのフレームは読み飛ばしてもかまいませんが、自分のコードのフレームは順番に読む必要があります。
- 一度に変更するのは 1 つだけにします。 仮説を検証するときは、変更を 1 つだけ加えて再実行します。複数の変更を同時に行うと、どの変更が影響したのか特定できません。
- ログを効果的に活用します。 疑わしい関数の開始時と終了時にログ文を挿入し、重要な変数の値を出力します。バグの修正後は、ログの詳細度を下げるか、ログ文を削除します。
- まず最近の変更を確認します。 特定のデプロイやコミットの後にバグが発生した場合、
git bisectを使うか、最近の差分を確認するのが、根本原因にたどり着く最速の方法であることがよくあります。 - 修正する前に再現します。 再現できないバグに対して修正を適用してはいけません。再現できなければ修正が機能するか検証できず、原因に対処せずに症状を隠す変更を加えてしまうおそれがあります。
- リグレッションテストを作成します。 修正したバグごとに、修正前は失敗し、修正後は成功する新しいテストケースを追加します。これは、同じバグの再発を防ぐ最も信頼できる方法です。
エッジケース
- ハイゼンバグ: デバッグツールを接続すると消えてしまうバグがあります(例:ブレークポイントによるタイミングの変化で競合状態が見えなくなる)。このような場合は、対話型デバッガーの代わりにログ記録やトレースを使用し、利用可能であれば言語の競合検出ツールを使って実行することを検討してください。
- 環境固有のバグ: 本番環境でのみ発生するバグは、開発環境と異なる OS のバージョン、メモリ制限、ネットワーク遅延、設定などに依存している可能性があります。エージェントは環境の詳細を確認し、同じ制約(例:メモリ制限を設定した Docker)で再現する方法を提案します。
- サードパーティライブラリのバグ: 根本原因がアプリケーションコードではなく依存関係にある場合、ライブラリのアップグレード、回避策の適用、または既知の正常なバージョンへの固定が修正方法となることがあります。エージェントは対応方針を提案する前に、変更履歴と課題トラッカーを確認します。
- コンパイラーまたはランタイムのバグ: まれに、バグの原因が言語のランタイム自体にあることがあります。エージェントはまずアプリケーションレベルの原因を徹底的に調べ、アプリケーションレベルの原因が見つからない場合に、別のランタイムバージョンでのテストを提案します。
- 破損した状態: バグが破損したデータ(例:書き込みが途中で止まったデータベースの行)に関係している場合、診断にはコードと併せてデータを調べる必要があります。エージェントはコードパスの分析と照合するため、サンプルデータまたはデータベースの状態を尋ねます。