Mingyu's Library主页
2026年8月28日

第 21 课:调试工作流——把「猜 bug」变成「证 bug」

2026-08-28 · 主题:debugging workflow(复现 → 定位 → 修复 → 验证的闭环) 往期相关:第 2 课 Plan Mode、第 8 课 上下文管理、第 15 课 成本控制。本课讲的是遇到 bug 时的具体动作序列,与它们不重复。

Claude Code 调试最大的失败模式是:你贴一段报错,它给出一个听起来很合理的解释和一个补丁,你 merge 了,三天后同样的 bug 又回来了。原因是整个过程没有任何一步是可证伪的。今天这四条,就是把调试从「解释」拉回「证明」。


技巧 1(入门):一次性把「可复现三件套」喂够

报错文本、期望行为、复现步骤——三样一起给,Claude 才能从深上下文开始调查,而不是先反问你五轮。

这个 bug 请按「先复现再修」的方式处理,不要直接改代码。

【报错】
TypeError: Cannot read properties of undefined (reading 'id')
  at resolveOwner (src/services/ticket.ts:142)

【期望行为】
ticket 没有 assignee 时,resolveOwner 应返回默认的 team 账号,而不是抛错。

【复现步骤】
1. POST /tickets  body: {"title":"x"}   (不传 assigneeId)
2. GET /tickets/:id/owner
3. 500

【环境】
node 22.6 / 本地 dev,pnpm test 可跑

要点:把「期望行为」单独写出来。很多 bug 报告只有现象,Claude 只能猜你想要什么,于是修出一个「不报错但语义错」的版本。

官方参考:Common workflows · Claude Code Docs


技巧 2(进阶):Prove-It 模式——先写失败测试,再允许改实现

Claude 的默认倾向是先写实现再补测试。要 TDD 就必须显式禁止它写实现:

第一步,只做这一件事:为上面这个 bug 写一个失败的测试,放在
tests/services/ticket.spec.ts。

硬性约束:
- 现在不要碰 src/ 下任何文件
- 测试必须真的失败,跑给我看失败输出
- 断言的是「返回默认 team 账号」,不是「不抛错」

我确认测试正确后,再让你去修实现。

确认失败输出符合预期后,第二步才是:

现在修 src/services/ticket.ts,目标是让刚才那个测试通过。
不允许修改测试文件。修完跑 pnpm test 全量,把结果贴出来。

「不允许修改测试文件」这句必须写死——否则模型在卡住时会去改断言让测试变绿,这是最隐蔽的假修复。

参考:TDD with Claude Code(社区指南)


技巧 3(进阶):把调试闭环写进项目记忆,不用每次重述

在 CLAUDE.md 里固化调试纪律,之后每个会话自动生效(呼应第 1 课):

## 调试纪律(bug 修复时必须遵守)
1. 先复现:能跑的失败测试或最小复现脚本,没有就先造一个
2. 定位阶段只读不写:先给出「疑似根因 + 证据(文件:行号)」,等我确认
3. 修复只允许改根因文件,不得为了让测试通过而修改测试断言
4. 收尾必须跑 `pnpm test` 全量并贴输出;新增的回归测试保留在仓库里
5. 禁止用 try/catch 吞异常或加 `?.` 掩盖 undefined 来「让报错消失」

第 5 条尤其值得写:?. 和空 catch 是模型消除报错最省力的路径,也是把 bug 从崩溃变成静默数据错误的最快方式。


技巧 4(实战):卡住时用 Plan Mode + 子会话做「假设分叉」,而不是让主会话越改越乱

调了 40 分钟还没定位时,不要继续在同一个会话里累加尝试(上下文里全是失败痕迹,模型会被自己的错误假设带偏)。改成:

# 1) 先切 Plan Mode(Shift+Tab),强制它只调查不动手
让它输出:3 个互斥的根因假设,每个假设写明「用什么最小实验可以证伪」

# 2) 对最可能的两个假设,各分叉一个会话并行验证
/fork
# 在分叉会话里:「只验证假设 B:在 resolveOwner 入口打日志跑复现脚本,
#  确认 assignee 是 undefined 还是 null,不要改任何业务逻辑」

# 3) 哪个假设被证实,就在那条线上继续修;主会话用 /clear 丢掉失败尝试的上下文

配套两个观测手段: - /context 看上下文占用,历史里失败尝试占比高就该 /clear 重开(第 8 课) - v2.1.243 起,循环内的 token 消耗明细可以看到「哪个任务在空转」,调试打转时是个很直接的信号(changelog)

判断标准:如果一个 bug 你已经让 Claude 改了三轮还没绿,问题几乎一定不在代码,而在你们对根因的共识还没建立。停下来回到技巧 2。


第 21 课 · 2026-08-28 · 上一课:第 20 课「Output Styles」(2026-08-27)