适用角色与上手难度
🎯 学习产出: 掌握规范化 Bug 诊断循环,能独立处理顽固 bug 和性能回退
🚀 AI 能力提升: 问题诊断、调试纪律
diagnosing-bugs
预计阅读时间: 9 分钟Bug 诊断循环。处理顽固 Bug 的纪律:拒绝在建立紧密反馈循环之前空想理论。这是本集合最强调"方法"的技能——"先有红色循环,Bug 就已 90% 修复"。
概述
"这个 bug 抗住了第一眼、间歇性闪断、在两个已知良好状态之间悄悄混进来的回归"——这就是为它设计的。六阶段流程,只在有明确理由时跳过阶段。探索代码库时先读 CONTEXT.md 建立模块心智模型,并检查改动区域的 ADR。
日常使用
实战:六阶段
阶段一:建立反馈循环(这是核心技能)
需要一条紧的通过/失败信号——一条命令能对这个 bug 变红。如果信号紧,你一定找得到原因;二分、假设检验、插桩都只是消费它。没有它,瞪多少代码都没用。在这里投入不成比例的精力。要激进、要创造、拒绝放弃。
构建方式(按大致优先级):
- 失败测试(能触达 bug 的接缝:单元/集成/e2e)
- curl/HTTP 脚本打开发服务器
- CLI 调用 + fixture 输入,与已知正确快照 diff stdout
- 无头浏览器脚本(Playwright/Puppeteer)驱动 UI,断言 DOM/console/network
- 回放捕获的流量(真实请求/载荷/事件日志存盘,隔离重放)
- 一次性 harness(系统最小子集 + mock 依赖,单函数调用触发 bug 路径)
- 属性/模糊循环("有时输出错"→ 跑 1000 个随机输入找失败模式)
- 二分 harness(bug 出现在两个已知状态之间→ 自动化"在状态 X 启动、检查、重复",
git bisect run) - 差分循环(同输入跑旧版 vs 新版、或两套配置,diff 输出)
- HITL bash 脚本(最后手段:人必须点按,用
scripts/hitl-loop.template.sh驱动)
收紧循环:把它当产品对待——更快?(缓存设置、跳过无关初始化、缩小测试范围)更尖?(断言具体症状,不是"没崩溃")更确定?(钉住时间、种子 RNG、隔离文件系统、冻结网络)。30 秒的 flaky 循环约等于没有;2 秒的确定性循环是调试超能力。
非确定性 bug:目标不是干净复现而是更高复现率——触发器跑 100 遍、并行、加压力、收窄时间窗、注入 sleep。50% 闪断可调试,1% 不可——持续提高直到可调试。
真的建不出循环:停下明说。列出试过的,向用户要:(a) 能复现环境的使用权,(b) 脱敏捕获物(HAR、日志转储、核心转储、带时间戳的录屏),(c) 临时生产插桩许可。没有循环就不进入假设。
阶段一完成标准:一条你已经实际运行过至少一次(展示调用和输出,脱敏)的命令,它:
- 能变红:驱动真实 bug 路径、断言用户的精确症状——不是"跑起来不报错"
- 确定性:每次运行同一裁决(闪断 bug:钉住的高复现率)
- 快速:秒级,不是分钟级
- agent 可无人值守运行(人在循环只经
scripts/hitl-loop.template.sh)
在红色命令存在之前禁止读代码构建理论——"跳过循环直接假设"正是这个技能要防的失败模式。没有红色命令就没有阶段二。
阶段二:复现 + 最小化
跑循环,看它变红。确认:(a) 循环产生的是用户描述的失败模式,不是旁边碰巧发生的另一个失败——错 bug = 错修复;(b) 多次运行可复现(或闪断 bug 复现率高到可对之调试);(c) 已捕获精确症状(错误信息、错误输出、慢的时序)供后续阶段验证。
最小化:变红后把复现缩到仍然变红的最小场景——一次砍一个输入/调用者/配置/数据/步骤,每次重跑循环,只留承载失败的部分。最小复现缩小阶段三的假设空间,并成为阶段五的干净回归测试。每个剩余元素都承重(去掉任何一个循环就变绿)才算完成。不复现+不最小化,不进下一步。
阶段三:假设
测试前先生成 3–5 个排名假设(单假设会锚定第一个想到的想法)。每个必须可证伪——说出它预测什么:
格式:"如果 X 是原因,那么改变 Y 会让 bug 消失 / 改变 Z 会让它更糟。"
说不出预测就是 vibe,丢弃或磨尖它。测试前把排名列表给用户看——他们常有领域知识瞬间重排("我们刚部署了 #3")或知道已排除的假设。便宜的检查点,巨大的省时。用户 AFK 就按你的排名继续,别阻塞。
阶段四:插桩
每个探针对应阶段三的特定预测,一次只改一个变量。工具偏好:调试器/REPL 检查(一个断点胜过十个日志)→ 区分假设的边界定向日志 → 绝不"全记下来再 grep"。每条调试日志带唯一前缀如 [DEBUG-a4f2],清理时一个 grep 全删——带标签的日志死,不带标签的活。
性能分支:性能回退用日志通常错。先立基线测量(时序 harness、performance.now()、profiler、查询计划),然后二分。先测量,后修复。
阶段五:修复 + 回归测试
先写回归测试再修复,但只在存在正确接缝时。正确接缝 = 测试能在调用点复现真实 bug 模式。只有浅接缝(单调用者测试但 bug 需要多调用者、无法复现触发链的单元测试)时,那里的回归测试给的是虚假信心。
没有正确接缝本身就是发现:记下它。架构阻止了这个 bug 被锁住——标记给下一步。
有接缝则:最小复现 → 变成该接缝的失败测试 → 看它失败 → 应用修复 → 看它通过 → 用原始未最小化场景重跑阶段一循环。
阶段六:清理
声明完成前必做:
- 原始复现不再出现(重跑阶段一循环)
- 回归测试通过(或无接缝已记录)
- 所有
[DEBUG-...]插桩移除(grep 前缀) - 一次性原型删除(或移到明确标记的调试位置)
- 最终正确的假设写进 commit/PR message——让下一个调试者学习
与其它技能的关系
- 脱敏纪律:展示命令/输出/捕获物前先脱敏,写
<REDACTED>,凭据留在环境变量里 - 交接:真实发现是"没有好接缝锁住 bug"时,事后交给
/improve-codebase-architecture - 修复:修复本身走
tdd

