ユースケース: チームで決済システムのプロダクションバグをデバッグする
⚠️ 注意: これは使い方を説明するための仮想ユースケースです。実際に AI が返す結果は以下の例と異なる場合があります。
決済のバグがプロダクションに漏れた時、自然な反応は最速で修正することです。Superpowers は、正しいタイミングで立ち止まり、正しい方法で修正する方法を教えてくれます -- 体系的に、証拠に基づき、レビュー付きで。
背景
2-3人の小さなチームで e-commerce システムを管理していると想像してください。ある日、チームは顧客から以下の報告を受けます:
「20%オフの商品を買って、10%のバウチャーを使ったけど、価格が正しくありません。期待していた金額より多く請求されました。」
調査の結果、以下が判明します:
- 商品: Tシャツ 定価 500,000ドン、20%オフ
- バウチャー: SAVE10、10%オフ
- 期待値: 500,000 x 0.8 = 400,000 -> 400,000 x 0.9 = 360,000ドン
- 実際: システムの計算結果は 350,000ドン
差額は10,000ドン。顧客は過少請求されていますが、高額商品や大きなバウチャーでは差額がさらに大きくなる可能性があります。
技術スタック: Node.js、TypeScript、関数 calculateFinalPrice()
学べること
このユースケースでは以下を実践します:
- Systematic Debugging 4 Phases -- 推測ではなく体系的なデバッグプロセス(詳しくは デバッグ & 検証)
- TDD Fix -- 修正前にバグを再現するテストを記述(詳しくは TDD)
- 双方向コードレビュー -- 送信者はコンテキストを準備し、レビュアーは応答前に丁寧に読む(詳しくは コードレビュー)
- Verification Gate -- 明確な証拠がある場合にのみ「修正済み」と宣言する(詳しくは デバッグ & 検証)
ステップバイステップ実践
ステップ 1: Phase 1 -- 根本原因の調査
コードを1行も修正する前に、問題を完全に理解する必要があります。推測は許されません。
1.1. 顧客からのバグ報告を読む
顧客からのフィードバック:
「Tシャツを500,000ドンで購入しました。商品ページには20%オフと表示されていました。SAVE10バウチャーでさらに10%オフにしました。360,000ドンを支払う予定でしたが、システムは350,000ドンと計算しました。」
🧑 ユーザー は AI に顧客からのバグ報告の内容を提供します。数値を正確に記録し -- 解釈し直さず、元データで作業します。プロンプトの例:
「顧客が決済バグを報告: Tシャツ500,000ドン、20%オフ、SAVE10バウチャーで10%オフ、期待値360,000ドンだがシステムは350,000ドンと計算。systematic なプロセスでデバッグを手伝ってください。」
🤖 AI は報告を受け取り、具体的な数値(定価、割引%、バウチャー、期待値 vs 実際値)を記録します。AI はすぐに原因を推測せず、体系的な調査プロセスを開始します。
1.2. バグの再現
🤖 AI はファイル src/utils/pricing.ts の関数 calculateFinalPrice() を読みます -- Product(discountPercent あり)と Voucher(オプション、discountPercent あり)を受け取り、割引後の価格を返します。一見すると、ロジックは正しく見えます: 商品割引を先に適用し、次にバウチャーを適用。しかしこれは AI が実行されていると思うコードです。AI はプロダクション上の実際のコードの確認を続けます。
🧑 ユーザー は AI がコードを読むのを観察し、正しいファイル/関数を調査していることを確認します。AI が間違ったファイルを読んでいる場合は、方向を修正してください。
1.3. 最近の変更を確認する
🤖 AI は git log を実行して最近のコミットを確認します:
git log --oneline -10
結果:
a1b2c3d feat: add loyalty points calculation
f4e5d6a refactor: simplify discount calculation <-- これだ
b7c8d9e fix: update shipping fee for remote areas
...
🤖 AI はコミット f4e5d6a(メッセージ「refactor: simplify discount calculation」)が非常に疑わしいことに気づき、diff を確認します:
git diff f4e5d6a~1..f4e5d6a -- src/utils/pricing.ts
🧑 ユーザー は AI と一緒に git log の結果を確認します。追加のコンテキストがあれば(例: 「そのコミットは新人開発者が先週マージしたもの」)、AI に提供して調査範囲を絞り込みます。
1.4. 実際のコード(バグのあるコード)の発見
🤖 AI はコミット f4e5d6a の diff を確認し、プロダクション上の実際のコードが変更されていることを発見します: 割引を順次適用する(価格 x 0.8 → x 0.9)代わりに、リファクタリングした人がパーセンテージを合算(20% + 10% = 30%)してから一度に適用(価格 x 0.7)していました。AI は発見をユーザーに報告します。
🧑 ユーザー は AI の分析を読み、確認します: 「その通り、正しいビジネスロジックは順次適用です。」ビジネスルールが異なる場合(例: 合算が正しい場合)は、AI が調査の方向を調整できるよう明確に伝えてください。
問題: これはロジックのバグです -- 「簡素化」が計算結果を変えてしまいました。
1.5. データフローの追跡
🤖 AI は両方の方法で計算フローをトレースし、ユーザーに分かりやすく提示します:
バグのあるコード(合算)の場合:
totalDiscount = 20 + 10 = 30
price = 500,000 * (1 - 30/100)
price = 500,000 * 0.7
price = 350,000ドン
正しいロジック(順次)の場合:
price = 500,000 * (1 - 20/100) = 500,000 * 0.8 = 400,000
price = 400,000 * (1 - 10/100) = 400,000 * 0.9 = 360,000ドン
差額: 360,000 - 350,000 = 10,000ドン。🤖 AI は警告します: 高額商品やより大きなバウチャーでは差額がはるかに大きくなります。
🧑 ユーザー は AI の計算が正しいことを確認し、結論に同意します。これは重要なチェックポイントです -- 次のステップに進む前に、双方が原因について合意していることを確認します。
注意: このステップでは調査のみを行い、まだ何も修正しません。理論より証拠が先です。
ステップ 2: Phase 2 -- パターン分析
バグを再現したら、パターンを探します。
2.1. 正常に動作するケースを見つける
🤖 AI はバグがいつ発生し、いつ発生しないかを特定するために、さまざまなケースを自動的にチェックします:
ケース 1: 商品割引のみ(バウチャーなし)
Input: product = { price: 500000, discountPercent: 20 }, no voucher
Code: totalDiscount = 20, price = 500000 * 0.8 = 400,000
Result: 400,000ドン -- 正しい
ケース 2: バウチャーのみ(商品割引なし)
Input: product = { price: 300000, discountPercent: 0 }, voucher = { discountPercent: 10 }
Code: totalDiscount = 0 + 10 = 10, price = 300000 * 0.9 = 270,000
Result: 270,000ドン -- 正しい
ケース 3: 両方の割引を同時に適用
Input: product = { price: 500000, discountPercent: 20 }, voucher = { discountPercent: 10 }
Code: totalDiscount = 20 + 10 = 30, price = 500000 * 0.7 = 350,000
Result: 350,000ドン -- 誤り(期待値 360,000ドン)
🧑 ユーザー は AI がリストアップしたケースを確認します。AI がまだ検討していない特別なケースがあれば(例: 「システムには VIP 顧客用の第3の割引タイプもある」)、追加分析のために補足してください。
2.2. 比較と結論のパターン
🤖 AI は結果を統合しパターンを導き出します: バグは両方の割引が同時に適用される場合にのみ発生します。 AI は数学的な理由を説明します:
- 合算:
price * (1 - (a + b) / 100) - 順次:
price * (1 - a/100) * (1 - b/100)
これら2つの式は数学的に同等ではありません(a または b が 0 でない限り)。
🧑 ユーザー は AI が導き出したパターンがシステムの実際の動作と一致することを確認します。他の顧客からの追加のバグ報告があれば、クロスチェックのために AI に提供してください。
ステップ 3: Phase 3 -- 仮説と検証
3.1. 仮説を明確に述べる
🤖 AI は構造化された形で仮説を明確に述べます:
仮説: バグは
calculateFinalPrice()関数が割引パーセンテージを順次適用する(価格 x 0.8 x 0.9)代わりに合算(20% + 10% = 30%)しているために発生しています。コミットf4e5d6a(「refactor: simplify discount calculation」)が順次適用のロジックを合算に変更しました。
🧑 ユーザー は仮説を読み、妥当かどうかを評価します。同意すれば確認して AI が検証に進みます。同意しない場合や別の仮説がある場合は提示してください。仮説を紙に書くかコメントに記録してください -- 頭の中だけに留めないでください。
3.2. 一度に1つの変数を検証する
🤖 AI は calculateFinalPrice() 関数に console.log を追加して各計算ステップをトレースし、顧客報告の入力で実行します:
[DEBUG] Initial price: 500000
[DEBUG] Product discount: 20
[DEBUG] Voucher discount: 10
[DEBUG] Total discount (合算): 30
[DEBUG] Final price: 350000
🤖 AI はデバッグ結果をユーザーに提示し、仮説を証明する行を指摘します。
🧑 ユーザー はデバッグ出力を確認し、顧客報告のデータと照合します。結果が報告されたバグの動作と一致することを確認します。
3.3. 具体的な数値で仮説を確認する
🤖 AI はバグの影響範囲を証明するために、さまざまな入力セットの比較表を作成します:
| ケース | 合算(バグ) | 順次(正しい) | 差額 |
|---|---|---|---|
| 500k, 20% + 10% バウチャー | 350,000 | 360,000 | 10,000 |
| 1,000k, 30% + 15% バウチャー | 550,000 | 595,000 | 45,000 |
| 2,000k, 50% + 20% バウチャー | 600,000 | 800,000 | 200,000 |
差額は商品価格と割引率に比例して増大します。🤖 AI は結論を出します: 仮説が確認されました。
🧑 ユーザー は比較表をレビューし、実際のデータに基づいてバグの深刻度を評価します(例: 「最も高額な商品は500万ドンなので、最大差額は...になりうる」)。修正の優先度を決定します。
ステップ 4: Phase 4 -- TDD Fix
原因が正確に特定できました。コードに飛びつく代わりに、TDD の正しいプロセスに従います: テストを先に書き、コードは後で修正します。
🤖 AI はアクションの前に確認します: 「仮説が確認されました: バグは順次適用の代わりにパーセンテージの合算が原因です。TDD プロセスに従って修正を進めてもよいですか -- バグを再現するテストを先に書き、コードは後で修正しますか?」
🧑 ユーザー が確認: 「同意します、進めてください。」
RED: バグを再現する失敗テストを記述
🤖 AI は5つのテストケースを含む src/utils/__tests__/pricing.test.ts ファイルを作成します:
- 商品割引とバウチャーの両方を順次適用(期待値 360,000)
- 商品割引のみ(期待値 400,000)
- 割引なし商品にバウチャーのみ(期待値 270,000)
- 割引なし(期待値 200,000)
- 端数のある価格の四捨五入(期待値 263,500)
🤖 AI が npm test -- --grep "calculateFinalPrice" を実行 → 2 FAIL、3 PASS。テスト 1 が予想通り失敗: 期待値 360,000 だが受信値 350,000。テスト 5 も合算ロジックが誤って計算するため失敗。
テスト 2、3、4 はパス。1種類の割引のみの場合、合算と順次で同じ結果になるためです。
🧑 ユーザー はテストケースのリストをレビューし -- システムの特別なケースが不足していれば補足します(例: 「VIP 顧客がさらに5%の割引を受けるケースもある」)。
GREEN: すべてのテストがパスするよう関数を修正
🤖 AI は src/utils/pricing.ts を修正 -- 順次割引適用ロジックを復元: 商品割引を先に適用し、バウチャー割引を後に適用(割引済み価格に対して)。テストを再実行 → 5/5 PASS。
🧑 ユーザー は修正の diff を読み、ロジックがシステムのビジネスルールに一致していることを確認します。
REFACTOR: ガード句を追加
🤖 AI は src/utils/pricing.ts にエッジケースのバリデーションを追加: price が負でないこと、discount が 0-100 の範囲内であること。テストファイルにガード句用の2つのテストケースを追加。テストスイート全体を再実行 → すべてパス、リグレッションなし。
🧑 ユーザー はガード句をレビュー -- バリデーションの制限がビジネスの規定に適合しているか確認します(例: discount は100%まで可能か、それとも最大80%か?)。
ステップ 5: 双方向コードレビュー
バグは修正され、テストもパスしました。しかしマージ前にコードレビューが必要です。
Dev A(修正者)がレビューリクエストを送信
🧑 ユーザー が AI に PR の作成を依頼します。🤖 AI は構造化された情報を持つプルリクエストを作成します:
## WHAT
商品割引とバウチャー割引の同時適用時に価格が誤って計算されるバグを修正。
## ROOT CAUSE
コミット f4e5d6a(「refactor: simplify discount calculation」)がロジックを
順次適用から割引パーセンテージの合算に変更していた。
合算: 500000 * (1 - (20+10)/100) = 350,000(誤り)
順次: 500000 * 0.8 * 0.9 = 360,000(正しい)
## PLAN
- 順次割引適用ロジックを復元
- 5つのテストケースを追加: 両割引、商品のみ、
バウチャーのみ、割引なし、四捨五入
- 入力バリデーション用のガード句を追加
## CHANGES
- src/utils/pricing.ts: calculateFinalPrice() 関数を修正
- src/utils/__tests__/pricing.test.ts: 7つのテストケースを追加
## BASE_SHA -> HEAD_SHA
f4e5d6a -> g7h8i9j
注意: Dev A はレビュアーが効率的にレビューできるよう、十分なコンテキストを提供します。送信者にはレビュアーが問題を理解できるよう支援する責任があります。
Dev B(レビュアー)がプロセスに従ってレビュー
Dev B(または Dev B の AI)は急いで応答しません。代わりに、5ステップのプロセスを実行します:
1. READ -- PR description と diff 全体を読む
2. UNDERSTAND -- 問題と修正方法を理解する
Dev B が自分で計算し直します:
合算: 500000 * (1 - 30/100) = 500000 * 0.7 = 350,000
順次: 500000 * 0.8 * 0.9 = 400000 * 0.9 = 360,000
確認: Dev A の計算は正しい。
3. VERIFY -- コードとテストを確認する
Dev B が修正コードを読み、各テストケースを読み、各テストケースが意味があり正しいことを確認します。
4. EVALUATE -- エッジケースを評価する
Dev B が質問します:
- 合計割引が100%を超えた場合はどうなるか?(ガード句で処理済み)
- price = 0 の場合は?(0 * 何でも = 0、OK)
- voucher.discountPercent = 0 の場合は?(price * 1 = price、OK)
- 他の関数に影響はあるか?(calculateFinalPrice の呼び出し元を確認)
5. RESPOND -- 具体的で建設的な応答
Dev B は以下のようには書きません:
"Looks good! LGTM"
Dev B は以下のように書きます:
## Review: Fix calculateFinalPrice
### 確認済み
- 順次適用ロジックは正しい。3つの異なる入力セットで自分で計算し直した。
- テストケースは基本的なケースを十分にカバーしている。
- 入力バリデーションのガード句は妥当。
### 提案
1. voucher.discountPercent = 0 のケースのテストを追加すべき
-- 価格に影響しないことを確認するため。
2. promotion.ts ファイルの `applyPromotion()` 関数を確認する必要がある --
この関数も calculateFinalPrice() を呼んでおり、
同様のロジックの修正が必要かもしれない。
3. なぜ順次適用であり合算ではないのかを説明するコメントを追加すべき
-- 将来誰かがまた「simplify」しないようにするため。
### 結論
上記3つの提案付きで Approve。提案2はマージ前に確認すべき。
Dev A の応答
Dev A はフィードバックを読み、対処します:
- voucher.discountPercent = 0 のテストケースを追加 -- 完了
applyPromotion()関数を確認 -- この関数はcalculateFinalPrice()を呼ぶだけで独自の割引ロジックはないことを確認 -- 修正不要- 説明コメントを追加 -- 完了
Dev A は calculateFinalPrice() 関数に説明コメントを追加 -- なぜ合算ではなく順次適用なのかを明記し、将来誰かがまた「simplify」するのを防ぎます。新しいコミットをプッシュし、3つの提案すべてを処理したことを確認の返信をします。
ステップ 6: Verification Gate
バグが修正されたと宣言する前に、5ステップの検証プロセスを適用します。
🧑 ユーザー が要求: 「マージ前にフル検証を実行してください。」
1. 特定 -- 実行すべきテスト
🤖 AI は実行すべきテストのリストを作成します:
- calculateFinalPrice のユニットテスト: 7テストケース
- チェックアウトフローのインテグレーションテスト: 3テストケース
- リグレッションテストスイート: 全体
🧑 ユーザー は必要に応じて追加します(例: 「チェックアウトページの e2e テストも実行して」)。
2. 実行 -- テストの実行
🤖 AI がすべてのテストを実行します:
# Unit tests
npm test -- --grep "calculateFinalPrice"
# Integration tests
npm test -- --grep "checkout"
# Full regression
npm test
3. 読解 -- 結果を1行ずつ読む
🤖 AI が結果を読み、提示します:
Test Suites: 24 passed, 24 total
Tests: 187 passed, 187 total
Snapshots: 0 total
Time: 12.34s
187テストすべてがパス。リグレッションなし。
4. 確認 -- 結果の照合
🤖 AI が確認チェックリストを作成します:
- calculateFinalPrice の7テストケース: PASS
- チェックアウトフローの3インテグレーションテスト: PASS
- 合計187テスト: PASS
- スキップまたは保留中のテストなし
🧑 ユーザー がステージング環境で、顧客報告と全く同じ入力で手動テストを行います:
- 商品 500,000ドン、20%オフ、10%バウチャー
- 結果: 360,000ドン -- 正しい
5. 宣言 -- 証拠に基づく結論
🤖 AI が証拠に基づく結論を提示します:
バグは修正済みです。
calculateFinalPrice()関数は割引の合算から順次適用に修正されました。7つのユニットテスト、3つのインテグレーションテスト、ステージングでの手動テストで検証済み。リグレッションなし。プロダクションへのデプロイ準備完了。
🧑 ユーザー が最終確認を行い、マージ/デプロイを承認します。
結果
Superpowers のプロセスを適用した結果、チームは以下を達成しました:
- バグが正しく修正された -- 一時的なホットフィックスではなく、体系的で証拠に基づく修正
- 7つのテストケースが追加された -- 将来このバグが再発することを防止
- チームがデバッグプロセスを学んだ -- リアクションモード(「最速で修正」)からシステマティックモード(「最も正しく修正」)への転換
- コードにコメントが追加された -- 次にコードを読む人が、なぜロジックが順次でなければならないかを理解し、再び「simplify」しようとしない
総所要時間: プロセス全体で約2-3時間。「素早い修正」より長く感じますが:
- バグが再発する可能性がない
- 急いだ修正から新たなバグが生まれる可能性がない
- チームが将来自信を持ってリファクタリングできるテストがある
重要なポイント
1. デバッグ時に推測しない
4つの体系的なフェーズに従います: 調査 -> パターン分析 -> 仮説 -> TDD Fix。各フェーズには次のフェーズに進む前の具体的なアウトプットがあります。
2. git log は味方
コミット「refactor: simplify discount calculation」が原因でした。原因を推測する前に git log --oneline -10 と git diff を実行する習慣は、間違った方向のデバッグに何時間も費やすことを防いでくれます。
3. 修正前にバグを再現するテストを書く
テストはバグが存在する証拠です。テストが失敗する時(RED)、証拠があります。修正後にテストがパスする時(GREEN)、バグが修正された証拠があります。テストがない = 証拠がありません。
4. 双方向コードレビュー
- 送信者: 十分なコンテキストを準備(WHAT、ROOT CAUSE、PLAN、CHANGES)。レビュアーが問題を素早く理解できるよう支援する。
- レビュアー: 応答前に丁寧に読む。実際に確認していないのに「LGTM」と書かない。具体的で建設的な提案を行う。
5. Evidence before claims(主張の前に証拠)
明確な verification の証拠がある場合にのみ「修正済み」と宣言する: テストパス、リグレッションなし、手動テスト成功。証拠のない宣言は保証のない約束です。