デバッグと検証
デバッグの鉄の法則:根本原因の調査なしに修正してはならない。
4フェーズデバッグプロセス
バグに遭遇したとき、最初の本能は修正を試みることです。その本能に従ってはいけません。ランダムな変更を適用することは、デバッグではなく推測です。推測は時に成功しますが、信頼できるプロセスではありません。
Superpowersのデバッグプロセスは4つのフェーズに従います:
┌──────────────────────────────────────────────────────────────┐
│ DEBUGGING PROCESS │
│ │
│ フェーズ1 → フェーズ2 → フェーズ3 → フェーズ4 │
│ │
│ 根本原因 パターン 仮説と 実装 │
│ 調査 分析 テスト │
└──────────────────────────────────────────────────────────────┘
フェーズ1:根本原因調査
質問:「実際に何が起きているのか?」
修正を試みる前に、何が間違っているかを正確に理解する必要があります。
このフェーズで収集すること:
- バグを再現する正確な手順
- 期待される動作(何が起こるべきか)
- 実際の動作(何が実際に起きているか)
- エラーメッセージ(完全で、切り捨てなし)
- 環境の詳細(ブラウザ、OSバージョン、依存関係バージョン)
# エラーの完全な出力を収集する
npm test 2>&1 | tee debug-output.txt
# または完全なスタックトレースを表示する
npm test --verbose 2>&1
根本原因なしに修正を書かないでください。 もし「なぜこれが起きているのかわからないが、これを変えれば直るかもしれない」という状況にあるなら、まだフェーズ1にいます。
フェーズ2:パターン分析
質問:「これはどんな種類の問題か?」
エラーを理解したら、それを既知のパターンに分類します。
| パターン | シグナル | 一般的な原因 |
|---|---|---|
| 状態管理 | 断続的な失敗、タイミング依存の動作 | Race condition、staleな参照、変異 |
| 統合 | コンポーネントが個別にはパスするが、一緒にはフェイルする | インターフェースの不一致、インポートの問題 |
| 環境 | 一部のマシンでのみ失敗する | バージョンの不一致、欠落している設定 |
| 回帰 | 以前は動作していたが、最近変更後に壊れた | 依存関係の壊れた変更、副作用のある変更 |
| ロジック | 常に同じ間違った結果を出力する | アルゴリズムの欠陥、オフバイワンエラー |
| データ | 特定の入力でのみ失敗する | 検証の欠如、エッジケースの未処理 |
分類はどこを探すかを教えてくれます。状態管理の問題はロジックバグとは異なるアプローチが必要です。
フェーズ3:仮説とテスト
質問:「何が原因か?」
パターンに基づいて、1つ以上の具体的な仮説を立てます。
仮説は具体的でなければなりません:
- 「コードのどこかにバグがある」— 仮説ではありません
- 「
calculateTotalのオフバイワンエラーがitem indexが0から始まると考えるが1から始まっている」— 仮説です
各仮説のテスト:
- 仮説が正しい場合に何が真になるかを予測する
- その予測をテストする
- 予測が正しければ、仮説が確認される
- 予測が間違っていれば、仮説を棄却して次のものへ進む
// 仮説:calculateTotalがitem配列の最初のitemをスキップしている
// 予測:items.length === 1の場合、calculateTotalはゼロを返す
test('calculateTotal includes first item', () => {
const items = [{ price: 50 }];
// 現在の動作を確認するために、まず失敗するテストを書く
expect(calculateTotal(items)).toBe(50); // これが0を返したら仮説が確認される
});
フェーズ4:実装
質問:「根本原因を修正するにはどうすればよいか?」
根本原因が確認されたら、修正します。確認されていない根本原因については修正しないでください。
3つの失敗ルール(3 Failures Rule):
同じ問題を3回試みて失敗した場合、停止してエスカレーションしてください。3回の失敗はアプローチの問題を示しており、問題そのものではありません。
試み1 → 失敗 → 分析 → 試み2 → 失敗 → 分析 → 試み3 → 失敗
↓
STOP: エスカレーション
なぜ3回なのか?1回の失敗は想定内です。2回はより多くの情報があります。3回の失敗は、間違った問題を解決しようとしているか、間違ったアプローチを使っているか、またはあなたが持っていないコンテキストが必要なことを示しています。
1つの修正ルール(Single Fix Rule):
一度に1つの変更のみ行ってください。複数の変更を同時に適用すると、何が問題を修正したかがわからなくなります。
❌ 悪い:一度に5つのことを変更してテストを実行する
✓ 良い:1つのことを変更し、テストを実行し、確認してから次へ進む
5ステップ検証ゲート
修正を適用した後、機能が完了したと主張する前に、5ステップの検証ゲートを通過しなければなりません:
┌────────────────────────────────────────────────────────────────┐
│ VERIFICATION GATE │
│ │
│ ステップ1:IDENTIFY — この機能に関連するすべてのテストをリスト │
│ ステップ2:RUN — テストスイートを実行する(仮定しない) │
│ ステップ3:READ — 実際の出力を1行ずつ読む │
│ ステップ4:VERIFY — 各テストが出力で通過していることを確認 │
│ ステップ5:CLAIM — 今初めて機能が完了したと述べる │
└────────────────────────────────────────────────────────────────┘
ステップ1:IDENTIFY
この修正に関連するすべてのテストをリストしてください。今日書いたテストだけでなく—影響を受けるコードに触れる既存のテストも含めてください。
ステップ2:RUN
実際にテストスイートを実行してください。頭の中でではありません。実行してください。
npm test -- --coverage --testPathPattern=calculateTotal
ステップ3:READ
実際の出力を読んでください。すべての行を。以下を確認してください:
- 実行されたテスト
- 通過したテスト
- スキップされたテスト
- カバレッジの数値
ステップ4:VERIFY
IDENTIFYリストの各テストについて、それが出力に表示されて通過として表示されていることを確認してください。テストが出力にない場合、実行されていません。テストが失敗している場合、修正は完了していません。
ステップ5:CLAIM
ステップ1〜4を完了した後のみ、AI(または開発者)は「この修正は完了です」と述べることができます。
「動くはずだ」と言うことはステップ5を完了することではありません。 実際のテスト出力のみがステップ5を完了します。
停止を必要とするレッドフラグ
デバッグ中にこれらの状況が発生した場合、即座に作業を停止してエスカレーションしてください:
| レッドフラグ | 何を意味するか |
|---|---|
| 修正が別のものを壊す | 根本原因が特定されていない;より深い問題がある |
| 同じ修正を3回試みて失敗した | アプローチが間違っている;エスカレーション |
| テストは通過するが動作がまだ間違っている | テストが間違ったものをテストしている |
| AIがテスト出力を表示せずに修正が機能すると主張する | 許容不可能 |
| バグが断続的に消えたり現れたりする | Race conditionまたは環境の問題;より深い調査が必要 |
| 5つ以上のファイルに触れずに修正できない | 設計上の問題が根本原因;修正で追いかけない |
デバッグセッションの例
これが実際の4フェーズプロセスがどのように見えるかです:
報告された問題: 「チェックアウトフォームが金額の計算を正しく行わない」
フェーズ1 — 根本原因調査:
再現手順:
1. カートに2つのitemを追加($30と$45)
2. チェックアウトに進む
3. 合計フィールドに$75の代わりに$30と表示される
期待:$75
実際:$30
エラー:なし(サイレントな失敗)
フェーズ2 — パターン分析:
パターン:ロジック — 常に同じ間違った結果(最初のitemの価格)
これは配列の反復問題を示している
フェーズ3 — 仮説とテスト:
仮説:calculateTotalが配列全体を反復せず、最初のitemのみを返している
予測:items.length === 1の場合、正しく動作する
テスト:items.length === 1でフォームをテスト → ✓ 正しく動作する
仮説確認
フェーズ4 — 実装:
// 根本原因の発見:
function calculateTotal(items) {
return items[0].price; // バグ:reduceの代わりに最初のitemのみを返している
}
// 修正:
function calculateTotal(items: Item[]): number {
return items.reduce((sum, item) => sum + item.price, 0);
}
npm test -- --testPathPattern=checkout
# すべてのテストが通過 ✓
デバッグは調査です。 証拠なしに推測しないでください。根本原因が確認されるまで修正しないでください。修正が機能することを確認せずに完了と主張しないでください。