📊 页面导航

适用角色与上手难度

角色推荐度上手难度
🛠️ 开发★★★★★★★★☆☆
🧪 测试★★★★☆★★★☆☆
📦 产品★☆☆☆☆★★★★★

🎯 学习产出: 掌握规范化 Bug 诊断循环,能独立处理顽固 bug 和性能回退

🚀 AI 能力提升: 问题诊断、调试纪律

diagnosing-bugs

预计阅读时间: 9 分钟

Bug 诊断循环。处理顽固 Bug 的纪律:拒绝在建立紧密反馈循环之前空想理论。这是本集合最强调"方法"的技能——"先有红色循环,Bug 就已 90% 修复"。

概述

"这个 bug 抗住了第一眼、间歇性闪断、在两个已知良好状态之间悄悄混进来的回归"——这就是为它设计的。六阶段流程,只在有明确理由时跳过阶段。探索代码库时先读 CONTEXT.md 建立模块心智模型,并检查改动区域的 ADR。

日常使用

> 诊断一下这个 bug
> 用户反馈结算页偶发白屏,帮我查
> 最近性能回退了,帮我定位

实战:六阶段

阶段一:建立反馈循环(这是核心技能

需要一条的通过/失败信号——一条命令能对这个 bug 变红。如果信号紧,你一定找得到原因;二分、假设检验、插桩都只是消费它。没有它,瞪多少代码都没用。在这里投入不成比例的精力。要激进、要创造、拒绝放弃。

构建方式(按大致优先级):

  1. 失败测试(能触达 bug 的接缝:单元/集成/e2e)
  2. curl/HTTP 脚本打开发服务器
  3. CLI 调用 + fixture 输入,与已知正确快照 diff stdout
  4. 无头浏览器脚本(Playwright/Puppeteer)驱动 UI,断言 DOM/console/network
  5. 回放捕获的流量(真实请求/载荷/事件日志存盘,隔离重放)
  6. 一次性 harness(系统最小子集 + mock 依赖,单函数调用触发 bug 路径)
  7. 属性/模糊循环("有时输出错"→ 跑 1000 个随机输入找失败模式)
  8. 二分 harness(bug 出现在两个已知状态之间→ 自动化"在状态 X 启动、检查、重复",git bisect run
  9. 差分循环(同输入跑旧版 vs 新版、或两套配置,diff 输出)
  10. 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