systematic-debugging について
Systematic Debugging は、バグやテスト失敗、性能問題などを調べる開発者向けのスキルです。根本原因の調査、パターン分析、仮説検証、実装の4段階で進め、修正前の証拠収集を重視します。
主な機能
- 014段階の調査プロセス
- 02再現と証拠の収集
- 03変更確認とデータフロー追跡
- 04仮説を一つずつ検証
- 05根本原因に対する修正の確認
活用シーン
- 01本番環境のバグ調査
- 02テストやビルドの失敗診断
- 03複数コンポーネントにまたがる問題の追跡
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ ckorhonen/ claude-skills --skill systematic-debuggingインストール後、Claude Code で/systematic-debuggingを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
condition-based-waiting-example.tscondition-based-waiting.mdCREATION-LOG.mddefense-in-depth.mdfind-polluter.shroot-cause-tracing.mdtest-academic.mdtest-pressure-1.mdtest-pressure-2.mdtest-pressure-3.md詳細
体系的なデバッグ
概要
場当たり的な修正は時間を浪費し、新たなバグを生みます。応急処置的なパッチは根本的な問題を覆い隠してしまいます。
中核原則: 修正を試みる前に、必ず根本原因を特定してください。症状への対処は失敗です。
このプロセスの文言に違反することは、デバッグの精神に違反することです。
鉄則
NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST
フェーズ 1 を完了していなければ、修正を提案することはできません。
使用するタイミング
あらゆる技術的な問題に使用してください:
- テストの失敗
- 本番環境でのバグ
- 予期しない動作
- パフォーマンスの問題
- ビルドの失敗
- 統合の問題
特に次のような場合に使用してください:
- 時間的な圧力がかかっているとき(緊急時は推測に走りたくなります)
- 「ちょっとした修正を一つ」が明らかに正解に思えるとき
- すでに複数の修正を試したとき
- 前回の修正がうまくいかなかったとき
- 問題を完全には理解していないとき
次の場合でも省略しないでください:
- 問題が単純に見えるとき(単純なバグにも根本原因はあります)
- 急いでいるとき(急ぐと必ず手戻りが発生します)
- マネージャーが「今すぐ直せ」と求めるとき(体系的に進める方が、やみくもに試すより速いです)
4 つのフェーズ
次のフェーズに進む前に、各フェーズを必ず完了しなければなりません。
フェーズ 1: 根本原因の調査
いかなる修正を試みる前にも:
-
エラーメッセージを注意深く読む
- エラーや警告を読み飛ばさないでください
- 正解そのものが書かれていることが多いです
- スタックトレースを最後まで読んでください
- 行番号、ファイルパス、エラーコードをメモしてください
-
一貫して再現させる
- 確実に再現できますか?
- 正確な手順は何ですか?
- 毎回発生しますか?
- 再現しない場合は、推測せずにさらにデータを収集してください
-
最近の変更を確認する
- この問題を引き起こしうる変更は何ですか?
- Git の差分、最近のコミット
- 新しい依存関係、設定の変更
- 環境の違い
-
複数コンポーネントから成るシステムで証拠を収集する
システムが複数のコンポーネントを持つ場合(CI → ビルド → 署名、API → サービス → データベース):
修正を提案する前に、診断用の計測を追加してください:
For EACH component boundary: - Log what data enters component - Log what data exits component - Verify environment/config propagation - Check state at each layer Run once to gather evidence showing WHERE it breaks THEN analyze evidence to identify failing component THEN investigate that specific component例(多層システム):
# Layer 1: Workflow echo "=== Secrets available in workflow: ===" echo "IDENTITY: ${IDENTITY:+SET}${IDENTITY:-UNSET}" # Layer 2: Build script echo "=== Env vars in build script: ===" env | grep IDENTITY || echo "IDENTITY not in environment" # Layer 3: Signing script echo "=== Keychain state: ===" security list-keychains security find-identity -v # Layer 4: Actual signing codesign --sign "$IDENTITY" --verbose=4 "$APP"これによって判明すること: どの層で失敗しているか(シークレット → ワークフロー ✓、ワークフロー → ビルド ✗)
-
データフローを追跡する
エラーがコールスタックの深い位置で発生している場合:
後方追跡の完全な手法については、このディレクトリにある
root-cause-tracing.mdを参照してください。簡易版:
- 不正な値はどこから発生しているのか?
- 不正な値を渡したのは何か?
- 発生源が見つかるまで追跡を続けてください
- 症状ではなく発生源で修正してください
フェーズ 2: パターン分析
修正する前にパターンを見つけてください:
-
動作する例を見つける
- 同じコードベース内で、類似した動作するコードを探してください
- 壊れている部分に似ていて、動作しているものは何ですか?
-
参照実装と比較する
- パターンを実装する場合は、参照実装を最後まで完全に読んでください
- 流し読みせず、すべての行を読んでください
- パターンを完全に理解してから適用してください
-
相違点を特定する
- 動作しているものと壊れているものの違いは何ですか?
- どんなに小さくても、すべての相違点を書き出してください
- 「それは影響しないはずだ」と決めつけないでください
-
依存関係を理解する
- このコンポーネントには、他にどのコンポーネントが必要ですか?
- どのような設定、構成、環境が必要ですか?
- どのような前提を置いていますか?
フェーズ 3: 仮説とテスト
科学的方法:
-
単一の仮説を立てる
- 「Y という理由で X が根本原因だと考える」と明確に述べてください
- 書き出してください
- 曖昧にせず、具体的にしてください
-
最小限のテストを行う
- 仮説を検証するために、可能な限り最小の変更を加えてください
- 一度に変更する変数は 1 つだけにしてください
- 複数のことを同時に修正しないでください
-
続行する前に検証する
- うまくいきましたか? はい → フェーズ 4 へ
- うまくいきませんでしたか? 新しい仮説を立ててください
- 既存の修正の上に、さらに修正を重ねないでください
-
わからないときは
- 「X がわからない」と述べてください
- 知っているふりをしないでください
- 助けを求めてください
- さらに調査してください
フェーズ 4:実装
症状ではなく、根本原因を修正します:
-
失敗するテストケースを作成する
- 可能な限りシンプルに再現する
- 可能であれば自動テストにする
- テストフレームワークがない場合は、一度限りのテストスクリプトを作成する
- 修正前に必ず作成する
- 適切な失敗テストを書くには、
superpowers:test-driven-developmentスキルを使用する
-
修正を 1 つだけ実装する
- 特定した根本原因に対処する
- 一度に変更するのは 1 つだけ
- 「ついでの」改善はしない
- リファクタリングをまとめて行わない
-
修正を検証する
- テストは通るようになったか?
- ほかのテストは壊れていないか?
- 問題は実際に解決したか?
-
修正がうまくいかない場合
- 停止する
- これまでに何回修正を試したか数える
- 3 回未満の場合:フェーズ 1 に戻り、新しい情報をもとに再分析する
- 3 回以上の場合:停止して、アーキテクチャを再検討する(下記のステップ 5)
- アーキテクチャについて話し合う前に、修正を 4 回目も試してはいけない
-
修正に 3 回以上失敗した場合:アーキテクチャを再検討する
アーキテクチャの問題を示すパターン:
- 修正するたびに、別の箇所で新たな共有状態/結合/問題が明らかになる
- 実装に「大規模なリファクタリング」が必要になる
- 修正するたびに、別の場所で新たな症状が発生する
停止して、根本的な前提を問い直す:
- このパターンは根本的に妥当か?
- 「ただ惰性で続けている」だけではないか?
- 症状の修正を続けるのではなく、アーキテクチャをリファクタリングすべきではないか?
これ以上修正を試みる前に、人間のパートナーと話し合ってください
これは仮説の失敗ではありません。アーキテクチャが間違っています。
危険信号 — 停止して手順に従う
次のように考えていることに気づいたら:
- 「ひとまず手早く直して、調査は後で」
- 「X を変えて、うまくいくか試してみよう」
- 「変更を複数加えてから、テストを実行しよう」
- 「テストは省略して、手動で確認しよう」
- 「おそらく X だろう。修正してみよう」
- 「完全には理解していないけれど、これでうまくいくかもしれない」
- 「パターンでは X とあるけれど、別の方法に変えてみよう」
- 「主な問題はこれです:[調査せずに修正を列挙]」
- データフローを追跡する前に解決策を提案する
- 「修正をもう 1 回試そう」(すでに 2 回以上試している場合)
- 修正するたびに、別の箇所で新たな問題が明らかになる
これらはすべて、停止してフェーズ 1 に戻るべきサインです。
修正に 3 回以上失敗した場合: アーキテクチャを再検討してください(フェーズ 4.5 を参照)
人間のパートナーが示す、進め方が間違っているサイン
次のような指摘があったら注意してください:
- 「それは実際に起きていないのでは?」 — 確認せずに決めつけています
- 「それで分かるのは……?」 — 証拠を集めるべきでした
- 「当てずっぽうはやめて」 — 理解せずに修正を提案しています
- 「徹底的に考えて」 — 症状だけでなく、根本的な前提を問い直してください
- 「行き詰まっているのでは?」(苛立った様子)— その進め方はうまくいっていません
このような反応があったら: 停止して、フェーズ 1 に戻ってください。
よくある正当化
| 言い訳 | 実情 |
|---|---|
| 「問題は単純だから、手順は不要」 | 単純な問題にも根本原因があります。単純なバグなら、手順に従ったほうが早く解決できます。 |
| 「緊急事態だから、手順に従う時間はない」 | 体系的なデバッグは、当てずっぽうの試行錯誤を繰り返すより速く進みます。 |
| 「まずこれを試してから調査しよう」 | 最初の修正が、その後の進め方を決めます。最初から正しく進めてください。 |
| 「修正がうまくいくと確認してからテストを書こう」 | テストされていない修正は定着しません。先にテストを書けば、その効果を確認できます。 |
| 「一度に複数修正すれば時間を節約できる」 | 何が効いたのか特定できず、新たなバグの原因になります。 |
| 「参照資料が長すぎるから、パターンを応用しよう」 | 部分的な理解は、必ずバグにつながります。最後まで読んでください。 |
| 「問題が分かった。修正しよう」 | 症状が見えていることと、根本原因を理解していることは別です。 |
| 「修正をもう 1 回試そう」(2 回以上失敗した後) | 3 回以上失敗したら、アーキテクチャに問題があります。再度修正するのではなく、パターンを問い直してください。 |
よくある落とし穴
デバッグの実践で確認されているアンチパターンです。認識したら、すぐに進め方を正してください。関連する指針については、危険信号 と よくある正当化 も参照してください。
1. 再現する前に解決策に飛びつく(早まった修正)
アンチパターン:
Error log shows "timeout" → assume network issue → add retry logic
(But you never actually reproduced the timeout)
問題:
- 検証していない症状に対処する修正になっています
- 実際の問題は修正されず、「修正」によって隠れたままです
- 症状が再発すると、自分のパッチのデバッグに時間を費やすことになります
回避方法:
- **フェーズ 1 の要件:**既知の手順でバグを確実に発生させられますか?
- 正確な再現手順を記録します:「X を Y に設定して
npm testを実行する」→ バグが発生する - 再現できなければ、修正が機能するか検証できません
- 再現性は必須です。発生させられるまで、さらにデータを集めます
対処方法:
- 作業を止めて、フェーズ 1 に戻ります
- 正確な再現手順を見つけます
- その後、フェーズ 2 に進みます
2. 一度に複数の変更を行う(根本原因を切り分けられない)
アンチパターン:
Change 3 things:
- Update dependency version
- Refactor retry logic
- Add fallback handler
Run tests → passes
問題:
- どの変更で修正されたのか分かりません
- 変更の 1 つが微妙なバグを引き起こしても、数か月間気づかない可能性があります
- 今後、このコードパスでバグが発生したときに、原因の特定がさらに難しくなります
回避方法:
- フェーズ 3 の要件:「最小限のテストを行い、可能な限り最小の変更を加えます」
- 一度に変更するのは 1 つの変数だけです
- 変更するたびにテストします
- 何が修正したのか切り分けられない場合、その修正についての理解が不十分です
対処方法:
- すべての変更を元に戻し、やり直します
- 1 つだけ変更して、テストします
- 「この変更だけで修正できた理由は[理由]です」と記録します
- 次の仮説についても同様に繰り返します
3. バグの原因は最近の変更だと思い込む(環境やデータを無視する)
アンチパターン:
Feature shipped 3 days ago → bug happened today → bug must be in that feature
(But you didn't verify the feature code against current data/environment)
問題:
- 最近のコードは怪しく見えても、環境の変更には気づきにくいものです
- 実際には問題のないコードのデバッグに、何日も費やすことになります
- 実際の原因は、データの破損、環境設定の誤り、依存関係の更新かもしれません
回避方法:
- フェーズ 1 の要件:「最近の変更の確認」には次の項目が含まれます
- コードに Git の差分がありますか?はい。
- 依存関係の更新はありますか?はい。
- 環境の変更はありますか?はい。
- データのマイグレーションはありますか?はい。
- 設定の変更はありますか?はい。
- 仮説を検証します。最近のコード変更を元に戻し、バグが続くか確認します
- 最近の変更を元に戻してもバグが発生する場合、原因は環境にあります
- 複数のコンポーネントで構成されるシステムでは、診断用の計測機能を使います(フェーズ 1 のステップ 4 を参照)
対処方法:
- 過去 1 週間の変更をすべてリストアップします:コード、依存関係、設定、環境、データ
- 環境やデータに起因する原因を体系的に除外します
- その後で初めて、コードに注目します
4. 再現手順を記録しない(修正を検証できない)
アンチパターン:
"Fixed the bug" (but didn't write down how to trigger it)
Weeks later: "Wait, does this still happen? I don't remember how to trigger it"
問題:
- 修正が本当に機能するか検証できません
- 同僚にテスト方法を伝えられません
- リグレッションを検出できません
回避方法:
- フェーズ 1 の要件:「一貫して再現する」
- 正確な手順を書き留めます
- 形式:「再現手順:[手順 1]、[手順 2]、[手順 3]」
- フェーズ 2 に進む前に、これらの手順を PR/Issue のコメントに追加します
- 修正を実装した後、同じ手順を再実行して検証します
- 再現手順をテストケースにします(フェーズ 4、ステップ 1)
対処方法:
- 今すぐ再現手順を記録します
- 正確な手順を再実行するまでは、修正が完了したと主張しないでください
- 再現手順を記録しており、修正後にバグを再現できなくなった場合、修正が機能していることの確認になります
5. ログや可観測性なしでデバッグする(手探りで進める)
アンチパターン:
Error happens in production
You: "Probably a race condition?"
You don't have logs showing what was happening at that moment
You guess at fixes for 3 hours
問題:
- 根拠なしに判断しています
- 修正を推測すると、新たなバグが発生します
- 原因を理解していないため、同じエラーが再発します
回避方法:
- フェーズ 1 の要件:「複数のコンポーネントで構成されるシステムの証拠を集める」
- デバッグを始める前に、「どのようなログがありますか?」と確認します
- ログが不足している場合は、一時的なログを追加して、ローカルで再現します
- 何が起きているかを示す証拠なしに、修正を提案しないでください
- 本番環境の問題では、障害発生時のログやメトリクスを取得します
具体的な方法:
# Reproduce with logging
DEBUG=* npm test # Enable all logs
strace node app.js # System call tracing
tcpdump -i any -w dump # Network tracing
対処方法:
- いったん中断し、重要な判断ポイントにログを追加します
- ログを強化した状態で問題を再現します
- これで仮説を立てるための証拠が得られます
- その後、修正を提案します
6. 失敗するテストケースを用意しない(検証のふり)
アンチパターン:
Implement fix
Run manual test: "Looks good"
Commit and push
(No automated test proving bug existed)
問題点:
- 何かを「修正」したものの、それが壊れていたことを一度も証明していません
- 後で回帰テストを行うと、バグがまだ発生することが分かります
- 「修正できた」のか「運よく直った」のかを区別できません
回避方法:
- フェーズ 4 の要件:「失敗するテストケースを作成する」
- 修正前にテストが失敗することが必須です
- 修正後にテストが成功することが必須です
- テストによってバグが存在したことと、修正によってバグが解消されたことを証明します
- 失敗するテストなしに修正を実装しないでください
- 自動テスト(ユニットテスト、統合テスト、一時的なスクリプト)を手動検証より優先します
復旧方法:
- 修正を元に戻します
- バグを再現するテストを書きます
- テストが失敗することを確認します
- 修正を実装します
- テストが成功することを確認します
7. 失敗しているコンポーネントを切り分けない(すべてを責める)
アンチパターン:
Database slow? Maybe it's the query. Maybe it's the network. Maybe it's CPU.
Let me add caching, fix indexes, reduce batch size, and upgrade servers.
問題点:
- 壊れていないものの修正にリソースを浪費します
- 実際のボトルネックを把握できません
- 何を最適化すべきか分からないため、今後の最適化が失敗します
回避方法:
- フェーズ 1 の要件:「複数コンポーネントのシステムで証拠を集める」
- 各コンポーネントの境界で、入ってくるデータと出ていくデータを記録します
- どのコンポーネントが遅い、または失敗しているのかを特定します
- そのコンポーネントに調査を集中します
- 5 つのことを最適化するのではなく、ボトルネックを最適化します
例:
# API slow. Which part?
time curl /api/endpoint # Total time
# Add logging to API entry point
# Add logging before DB query
# Add logging after DB returns
# Add logging before response
# Now you know: API fast (10ms), DB query slow (2s) → focus on DB
復旧方法:
- 診断用の計測機能を使って、実際にどのコンポーネントが失敗しているのかを特定します
- 特定のコンポーネントを切り分けるまでは、修正を提案しないでください
8. 修正が完了したと思い込む(エッジケースの見落とし)
アンチパターン:
Test case passes for happy path
Deploy without testing error cases
(Bug appears 2 weeks later under edge condition you didn't think to test)
問題点:
- ある状況に対する修正が、ほかの状況を壊します
- バグの 80% はエッジケースに潜んでいます
回避方法:
- フェーズ 4 の要件:「修正を検証する」
- 成功するケースをテストします
- エラーケースをテストします
- 境界条件をテストします
- 無効なデータ、空のデータ、極端なデータでテストします
- ほかのコンポーネントとの相互作用をテストします
- デプロイ前に包括的なテストカバレッジを確保します
- 修正した各バグについて回帰テストを追加します
復旧方法:
- エッジケースのテストを追加します
- 包括的なテストスイートを再実行します
- すべてのケースに合格するまで、修正が完了したと宣言しないでください
これらの落とし穴は、一度きりのミスではなく、繰り返し現れるパターンです。自分がその一つに陥っていると気づいたら、作業を止めて手順に立ち戻ってください。
クイックリファレンス
| フェーズ | 主な作業 | 成功基準 |
|---|---|---|
| 1. 根本原因 | エラーを読み、再現し、変更を確認し、証拠を集める | 何が、なぜ起きたかを理解する |
| 2. パターン | 動作する例を探し、比較する | 相違点を特定する |
| 3. 仮説 | 理論を立て、最小限のテストを行う | 仮説を確認するか、新たな仮説を立てる |
| 4. 実装 | テストを作成し、修正し、検証する | バグが解消され、テストに合格する |
プロセスの結果「根本原因なし」と判明した場合
体系的な調査によって、問題が本当に環境依存、タイミング依存、または外部要因によるものだと判明した場合:
- プロセスを完了しています
- 調査した内容を記録します
- 適切な対処(再試行、タイムアウト、エラーメッセージ)を実装します
- 今後の調査に備えて、監視やログ記録を追加します
ただし: 「根本原因がない」ケースの 95% は、調査が不十分です。
補助的な手法
これらの手法は体系的なデバッグの一部であり、このディレクトリから利用できます:
root-cause-tracing.md- コールスタックをさかのぼってバグを追跡し、最初の引き金を特定しますdefense-in-depth.md- 根本原因を見つけた後、複数の層に検証を追加しますcondition-based-waiting.md- 任意のタイムアウトを条件ポーリングに置き換えます
関連するスキル:
- superpowers:test-driven-development - 失敗するテストケースを作成する場合(フェーズ 4、ステップ 1)
- superpowers:verification-before-completion - 成功を宣言する前に修正が機能したことを検証します
実際の効果
デバッグセッションでの比較:
- 体系的なアプローチ:修正まで 15~30 分
- 手当たり次第に修正するアプローチ:2~3 時間の試行錯誤
- 初回で修正できる率:95% 対 40%
- 新たに発生するバグ:ほぼゼロ 対 頻繁に発生