跳转到正文

实战案例:团队调试支付系统生产环境 Bug

⚠️ 注意: 这是一个假设性案例,旨在说明使用方法。AI 在实际操作中的回答可能与下面的示例不同。

当一个支付 bug 泄漏到生产环境时,自然反应是尽快修复。Superpowers 教你在正确的时机放慢脚步,用正确的方式修复——有系统、有证据、有审查。


背景

想象你在一个 2-3 人的小团队中工作,管理一个电商系统。某天,团队收到客户的投诉报告:

"我购买了打 8 折的产品,使用了 10% 折扣的优惠券,但价格不对。我被收取了比预期更高的金额。"

经过检查,你发现:

  • 产品: T恤,原价 500,000đ,打 8 折(减 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()


你将学到什么

通过本案例,你将实践:

  1. 系统化调试 4 阶段 -- 有系统的调试流程而非猜测(详见 调试与验证
  2. TDD Fix -- 在修复之前先编写重现 bug 的测试(详见 TDD
  3. 双向代码审查 -- 提交者准备上下文,审查者仔细阅读后再反馈(详见 代码审查
  4. Verification Gate -- 只有在有明确证据时才声称"已修复"(详见 调试与验证

分步实践

步骤 1:Phase 1 -- 调查根本原因

在修复任何代码之前,你需要充分理解问题。不允许猜测。

1.1. 阅读客户的 bug 报告

客户反馈:

"我购买了一件 T恤,原价 500,000đ,产品页面标注打 8 折。我使用了 SAVE10 优惠券再减 10%。我期望支付 360,000đ,但系统收了 350,000đ。"

🧑 用户 向 AI 提供客户的 bug 报告内容。精确记录数字——不要重新解读,直接使用原始数据。示例 prompt:

"客户报告了支付 bug:购买 T恤 500,000đ 打 8 折,使用 SAVE10 优惠券减 10%,期望 360,000đ 但系统收了 350,000đ。请帮我按系统化流程进行调试。"

🤖 AI 接收报告,记录具体数字(原价、折扣百分比、优惠券、期望值 vs 实际值)。AI 不会 立即猜测原因,而是启动系统化调查流程。

1.2. 重现错误

🤖 AI 读取文件 src/utils/pricing.ts -- 函数 calculateFinalPrice() 接收 Product(有 discountPercent)和 Voucher(可选,有 discountPercent),返回折后价格。乍看之下,逻辑似乎正确:先应用产品折扣,然后应用优惠券。但这是 AI 认为 在运行的代码。AI 继续检查 production 上的实际代码。

🧑 用户 观察 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 的 message "refactor: simplify discount calculation" 非常可疑,并检查 diff:

git diff f4e5d6a~1..f4e5d6a -- src/utils/pricing.ts

🧑 用户 与 AI 一起查看 git log 结果。如果你知道更多上下文(例如:"那个提交是新来的开发者上周 merge 的"),请提供给 AI 以缩小调查范围。

1.4. 发现实际代码(有 bug 的代码)

🤖 AI 检查提交 f4e5d6a 的 diff,发现 production 上的实际代码已被修改:原本是顺序应用折扣(价格 x 0.8 再 x 0.9),重构者累加了百分比(20% + 10% = 30%)然后一次性应用(价格 x 0.7)。AI 向用户报告发现。

🧑 用户 阅读 AI 的分析并确认:"对,正确的业务逻辑应该是顺序应用。"如果业务规则不同(例如累加才是正确的),请明确说明以便 AI 调整调查方向。

问题: 这是逻辑错误——"简化"改变了计算结果。

1.5. 跟踪数据流

🤖 AI 用两种方式追踪计算流程,清晰地展示给用户:

有 bug 的代码(累加):

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 -- 模式分析

重现了 bug 之后,寻找模式。

2.1. 寻找正常工作的示例

🤖 AI 自动检查多种不同情况,确定 bug 何时出现、何时不出现:

情况 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 未考虑到的特殊情况(例如:"系统还有第 3 种 VIP 客户折扣"),请补充以便 AI 进一步分析。

2.2. 比较并总结模式

🤖 AI 汇总结果并得出模式:bug 仅在同时应用两种折扣时出现。 AI 解释数学原因:

  • 累加:price * (1 - (a + b) / 100)
  • 顺序:price * (1 - a/100) * (1 - b/100)

这两个表达式在数学上不等价(除非 a 或 b 为 0)。

🧑 用户 确认 AI 得出的模式是否与系统的实际行为一致。如果有其他客户的 bug 报告,提供给 AI 进行交叉验证。


步骤 3:Phase 3 -- 假设与验证

3.1. 清晰陈述假设

🤖 AI 以清晰、结构化的方式陈述假设:

假设: Bug 发生是因为函数 calculateFinalPrice() 累加折扣百分比(20% + 10% = 30%)而不是顺序应用(价格 x 0.8 x 0.9)。提交 f4e5d6a("refactor: simplify discount calculation")将逻辑从顺序应用重构为累加。

🧑 用户 阅读假设并评估是否合理。如果同意,确认以便 AI 进行验证。如果不同意或有其他假设,请提出。将假设写下来或记录在评论中——不要让假设只停留在脑中。

3.2. 一次检验一个变量

🤖 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 创建多组不同输入的对比表来证明 bug 的影响范围:

情况累加(bug)顺序(正确)差额
500k, 20% + 10% 优惠券350,000360,00010,000
1,000k, 30% + 15% 优惠券550,000595,00045,000
2,000k, 50% + 20% 优惠券600,000800,000200,000

差额随产品价格和折扣幅度增大而增大。🤖 AI 总结:假设已确认。

🧑 用户 审查对比表,根据实际数据评估 bug 的严重程度(例如:"我们最贵的产品是 500 万,那最大差额可能达到...")。决定修复优先级。


步骤 4:Phase 4 -- TDD Fix

现在你已经确切知道了原因。与其急于修改代码,不如按照正确的 TDD 流程来:先写测试,后修改代码。

🤖 AI 在行动前先询问:"假设已确认:bug 由累加百分比而非顺序应用引起。你同意我按 TDD 流程进行修复——先写重现 bug 的测试,再修改代码吗?"

🧑 用户 确认:"同意,开始吧。"

RED:编写重现 Bug 的失败测试

🤖 AI 创建文件 src/utils/__tests__/pricing.test.ts,包含 5 个测试用例:

  • 同时应用产品折扣和优惠券的顺序计算(期望 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 通过因为它们只有一种折扣,累加和顺序的结果相同。

🧑 用户 审查测试用例列表——如果系统有缺少的特殊情况(例如:"还有 VIP 客户额外减 5% 的情况"),请补充。

GREEN:修复函数使所有测试通过

🤖 AI 修复 src/utils/pricing.ts -- 恢复顺序应用折扣的逻辑:先应用产品折扣,再应用优惠券折扣(在已减价的基础上)。重新运行测试 → 5/5 PASS

🧑 用户 阅读修复的 diff,确认逻辑符合系统的业务规则。

REFACTOR:添加 Guard Clause

🤖 AIsrc/utils/pricing.ts 中为边界情况添加验证:price 不为负,discount 在 0-100 范围内。在测试文件中添加 2 个 guard clause 的测试用例。重新运行完整测试套件 → 全部通过,没有回归。

🧑 用户 审查 guard clause -- 确认验证限制是否符合业务规定(例如:折扣可以到 100% 吗,还是最多 80%?)。


步骤 5:双向代码审查

Bug 已修复,测试已通过。但在 merge 之前,需要代码审查。

Dev A(修复者)发送审查请求

🧑 用户 要求 AI 创建 PR。🤖 AI 创建包含完整结构化信息的 pull request:

## WHAT
修复同时应用产品折扣和优惠券折扣时价格计算错误的 bug。

## ROOT CAUSE
提交 f4e5d6a ("refactor: simplify discount calculation") 将逻辑
从顺序应用改为累加折扣百分比。

累加: 500000 * (1 - (20+10)/100) = 350,000 (错误)
顺序: 500000 * 0.8 * 0.9 = 360,000 (正确)

## PLAN
- 恢复顺序应用折扣的逻辑
- 添加 5 个测试用例覆盖各种情况:双折扣、仅产品折扣、
  仅优惠券、无折扣、以及四舍五入
- 添加 guard clause 进行输入验证

## 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(或 Dev B 的 AI)不急于回复。而是执行 5 步流程:

1. READ -- 阅读完整的 PR 描述和 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% 怎么办?(已通过 guard clause 处理)
  • 如果 price = 0 怎么办?(0 * 任何数 = 0,没问题)
  • 如果 voucher.discountPercent = 0 怎么办?(price * 1 = price,没问题)
  • 是否影响其他函数?(检查 calculateFinalPrice 的调用者)

5. RESPOND -- 具体、有建设性地回复

Dev B 不会写:

"Looks good! LGTM"

Dev B 会写:

## Review: Fix calculateFinalPrice

### 已确认
- 顺序应用逻辑是正确的。我用 3 组不同的输入自行验证了计算。
- 测试用例充分覆盖了基本情况。
- guard clause 的输入验证是合理的。

### 建议
1. 应该为 voucher.discountPercent = 0 的情况添加测试用例,
   确保它不影响价格。

2. 需要检查 promotion.ts 文件中的 `applyPromotion()` 函数——
   该函数也调用了 calculateFinalPrice(),可能有类似
   的逻辑需要修复。

3. 应该添加注释解释为什么要顺序应用而不是累加,
   以防止将来有人再次"简化"。

### 结论
批准,附带以上 3 条建议。建议 2 应在 merge 前检查。

Dev A 回复

Dev A 阅读建议并处理:

  1. 添加 voucher.discountPercent = 0 的测试用例 -- 完成
  2. 检查函数 applyPromotion() -- 确认该函数仅调用 calculateFinalPrice() 且没有自己的折扣逻辑 -- 无需修改
  3. 添加解释注释 -- 完成

Dev A 在函数 calculateFinalPrice() 中添加注释——清楚说明为什么要顺序应用而不是累加,以防止将来有人再次"简化"。推送新提交并回复确认已处理所有 3 条建议。


步骤 6:Verification Gate

在声称 bug 已修复之前,应用 5 步验证流程。

🧑 用户 要求:"在 merge 之前运行完整验证。"

1. 确定 -- 需要运行哪些测试

🤖 AI 列出需要运行的测试清单:

- calculateFinalPrice 的单元测试: 7 个测试用例
- checkout 流程的集成测试: 3 个测试用例
- 回归测试套件: 全部

🧑 用户 根据需要补充(例如:"再运行 checkout 页面的 e2e 测试")。

2. 运行 -- 执行测试

🤖 AI 运行所有测试:

# 单元测试
npm test -- --grep "calculateFinalPrice"

# 集成测试
npm test -- --grep "checkout"

# 完整回归
npm test

3. 阅读 -- 逐行阅读结果

🤖 AI 阅读并展示结果:

Test Suites: 24 passed, 24 total
Tests:       187 passed, 187 total
Snapshots:   0 total
Time:        12.34s

所有 187 个测试通过。没有回归。

4. 确认 -- 对照结果

🤖 AI 汇总确认检查清单:

  • calculateFinalPrice 的 7 个测试用例:PASS
  • checkout 流程的 3 个集成测试:PASS
  • 共 187 个测试:PASS
  • 没有被 skip 或 pending 的测试

🧑 用户 在 staging 环境中使用客户报告中完全相同的输入进行手动检查:

  • 产品 500,000đ,打 8 折,10% 优惠券
  • 结果:360,000đ -- 正确

5. 声明 -- 有证据的结论

🤖 AI 基于证据给出结论:

Bug 已修复。 函数 calculateFinalPrice() 已从累加折扣修改为顺序应用。通过 7 个单元测试、3 个集成测试和 staging 环境手动检查验证。没有回归。准备部署到生产环境。

🧑 用户 做最终确认并批准 merge/deploy。


结果

应用 Superpowers 工作流程后,团队取得了以下成果:

  1. Bug 被正确修复 -- 不是临时热修复,而是有系统、有证据的修复
  2. 添加了 7 个测试用例 -- 防止此 bug 在未来重现
  3. 团队学会了调试流程 -- 从反应模式("尽快修复")转变为系统模式("尽可能正确地修复")
  4. 代码中添加了注释 -- 后续阅读者会理解为什么逻辑必须是顺序的,不会有人再"简化"

总时间:大约 2-3 小时完成整个流程。看似比"快速修复"慢,但是:

  • 不可能再次出现同样的 bug
  • 不可能因为仓促修复而引入新的 bug
  • 团队有了测试来支撑未来的放心重构

关键要点

1. 调试时永远不要猜测

遵循 4 个阶段的系统化流程:调查 -> 模式分析 -> 假设 -> TDD Fix。每个阶段都有具体的输出,然后才进入下一个阶段。

2. git log 是你的朋友

提交 "refactor: simplify discount calculation" 就是原因。养成在猜测原因之前运行 git log --oneline -10git diff 的习惯,将帮你节省数小时的错误方向调试。

3. 修复之前先编写重现 bug 的测试

测试是 bug 存在的证据。当测试失败时(RED),你有了证据。当修复后测试通过时(GREEN),你有了 bug 已被修复的证据。没有测试 = 没有证据。

4. 双向代码审查

  • 提交者: 准备完整的上下文(WHAT, ROOT CAUSE, PLAN, CHANGES)。帮助审查者快速理解问题。
  • 审查者: 回复前仔细阅读。如果没有真正检查过就不要写 "LGTM"。给出具体的、有建设性的建议。

5. 证据先于声明

只有在有明确的验证证据时才声称"已修复":测试通过、没有回归、手动检查成功。没有证据的声明是没有保障的承诺。


参考