SuperAPI AI 工具集

diagnosing-bugs

编程与开发

查看项目

更新于 10-16 11:29

技能介绍

Diagnosing Bugs(诊断 Bug)

一套面向困难 Bug 的诊断纪律。只有在明确说明理由时才可跳过阶段。

在探索代码库时,如果存在 GLOSSARY.md,先阅读它以建立相关模块的清晰心智模型,并查看所触及领域的 ADR(架构决策记录)。

脱敏(Redact)

该技能要求展示命令、输出和捕获的产物。先对每个秘密脱敏:用 <REDACTED> 替代。围绕环境变量构建循环,让凭据留在环境中,而不是出现在展示内容里。捕获的产物可能带有认证头:只引用携带信号的那几行。

如果脱敏后的输出不足以诊断 Bug,应说明这一点并向用户询问。

阶段 1:构建反馈循环

这就是本技能的核⼼。 其余都是机械操作。如果拥有针对该 Bug 的紧致通过/失败信号(能对这个 Bug 变红),你就能找到原因;二分、假设检验、插桩都只是消费这个信号。如果没有,光盯着代码也救不了你。

在此投入不成比例的精力。要激进。要有创意。拒绝放弃。

构建循环的方式,大致按以下顺序

  1. 在能触及 Bug 的任何接缝处写失败测试:单元、集成、端到端。
  2. 对运行中的开发服务器使用 Curl / HTTP 脚本。
  3. 用固定输入做 CLI 调用,将 stdout 与已知正确的快照做 diff。
  4. 无头浏览器脚本(Playwright / Puppeteer)驱动 UI,并对 DOM/控制台/网络断言。
  5. 重放捕获的 trace。 把真实的网络请求/载荷/事件日志保存到磁盘;在隔离状态下经代码路径重放。
  6. 一次性测试台(harness)。 启动系统的最小���集(一个服务、mock 依赖),用单次函数调用走 Bug 代码路径。
  7. 属性/模糊循环。 如果 Bug 是“有时输出错误”,跑 1000 个随机输入寻找失败模式。
  8. 二分测试台。 如果 Bug 出现在两个已知状态(commit、数据集、版本)之间,自动化“在状态 X 启动、检查、重复”,以便用 git bisect run 运行。
  9. 差分循环。 把同一输入分别跑旧版本与新版本(或两套配置),diff 输出。
  10. 人机协同(HITL)bash 脚本。 最后手段。如果必须由人来点击,用 scripts/hitl-loop.template.sh 驱动他们,使循环仍保持结构化。捕获的输出回传给你。

构建正确的反馈循环,Bug 就解决了 90%。

收紧循环

把循环当作产品。一旦拥有一个循环,就收紧它:

  • 能否更快?(缓存准备,跳过无关初始化,收窄测试范围。)
  • 能否让信号更锐利?(对具体症状断言,而非“没有崩溃”。)
  • 能否更确定?(固定时间、给 RNG 播种、隔离文件系统、冻结网络。)

30 秒且不稳定的循环比没有循环好不了多少;2 秒且确定性的循环是紧致的,是调试的超能力。

非确定性 Bug

目标不是干净复现,而是更高的复现率。把触发循环 100 次、并行化、加压力、收窄时序窗口、注入 sleep。50% 概率的 flake Bug 可调试;1% 则不能,所以持续提高复现率直到可调试。

当确实无法构建循环时

停下来并明确说明。列出你尝试过的内容。向用户请求:(a) 访问能复现它的任何环境,(b) 脱敏后的捕获产物(HAR 文件、日志转储、core dump、带时间戳的录屏),或 (c) 允许添加临时的生产插桩。不要在没有循环的情况下进入假设阶段。

完成标准:一个能变红的紧致循环

阶段 1 在循环紧致且能变红时结束:你能说出一条命令(脚本路径、测试调用、curl),你已经至少运行过一次(展示调用及其输出,已脱敏),并且它满足:

  • [ ] 能变红:驱动真实的 Bug 代码路径,并断言用户的确切症状,从而能对这个 Bug 变红、修复后变绿。不是“运行无错误”;它必须能捕获这个特定 Bug。
  • [ ] 确定性:每次运行判词一致(flake Bug:按上述固定且高复现率)。
  • [ ] 快速:以秒计,而非分钟。
  • [ ] 智能体可运行:你能无人值守运行;人机协同仅通过 scripts/hitl-loop.template.sh。

如果发现自己在这条命令存在之前就去读代码构建理论,停下:直接跳到假设正是本技能要防止的失败。 没有能变红的命令,就没有阶段 2。

阶段 2:复现 + 最小化

运行循环。观察它随 Bug 出现而变红。

确认:

  • [ ] 循环产生的是用户描述的失败模式,而非附近碰巧出现的其他失败。错的 Bug = 错的修复。
  • [ ] 该失败在多次运行中可复现(或对非确定性 Bug,可复现率达到足以调试)。
  • [ ] 你已捕获确切症状(错误消息、错误输出、慢的耗时),以便后续阶段验证修复确实针对它。

最小化

一旦变红,把复现缩小到仍会变红的最小场景。一次只削减一个输入、调用方、配置、数据和步骤,每次削减后重新运行循环,只保留对失败有支撑作用的元素。

为何要做:最小复现会在阶段 3 缩小假设空间(剩余可疑的活动部件更少),并在阶段 5 成为干净的回归测试。

当每个剩余元素都有支撑作用时完成:移除其中任何一个都会让循环变绿。

在完成复现且最小化之前,不要继续。

阶段 3:假设

在测试任何假设之前,先产生 3–5 个排序假设。单一假设会让思路锚定在第一个看似合理的想法上。

每个假设必须可证伪:说明它所做的预测。

格式:“如果 <X> 是原因,那么 <改变 Y> 会让 Bug 消失/<改变 Z> 会让它更严重。”

如果无法陈述预测,该假设只是一种感觉:丢弃或使其更锐利。

在测试前把排序列表展示给用户。 他们常拥有能立即重排的领域知识(“我们刚部署了 #3 的改动”),或知道你已排除的假设。廉价的检查点,省下大量时间。不要阻塞等待;如果用户不在,就按你的排序继续。

阶段 4:插桩

每个探针必须映射到阶段 3 的特定预测。一次只改变一个变量。

工具偏好:

  1. 如果环境支持,使用调试器/REPL 检查。一个断点胜过十条日志。
  2. 在能区分假设的边界处做定向日志。
  3. 绝不“记录全部再 grep”。

给每条调试日志打标签,使用唯一前缀,例如 [DEBUG-a4f2]。最后清理就变成一次 grep。未打标签的日志会存活;打了标签的日志会消亡。

性能分支。 对性能回归,日志通常是错的。改为:先建立基线测量(计时测试台、performance.now()、profiler、查询计划),然后二分。先测量,后修复。

阶段 5:修复 + 回归测试

在修复之前写回归测试,但前提是存在正确的接缝。

正确的接缝是指测试能复现调用点上发生的真实 Bug 模式。如果唯一可用的接缝太浅(Bug 需要多个调用方时只测单个调用方、单元测试无法复现触发 Bug 的调用链),在此写回归测试会带来虚假的信心。

如果不存在正确的接缝,这本身就是发现。 记下来。代码库架构阻止了该 Bug 被锁定。为下一阶段标记这一点。

如果存在正确接缝:

  1. 把最小复现转成该接缝处的失败测试。
  2. 观察它失败。如果你通过改动代码或夹具强行变红,与原始副本做 diff,证明改动确实生效后再信任它。
  3. 应用修复。
  4. 观察它通过。
  5. 针对原始(未最小化)场景重新运行阶段 1 的反馈循环。

阶段 6:清理

在宣布完成前必须做到:

  • [ ] 原始复现不再复现(重新运行阶段 1 循环)
  • [ ] 回归测试通过(或记录接缝缺失)
  • [ ] 所有 [DEBUG-...] 插桩被移除(grep 该前缀)
  • [ ] 一次性原型已删除(或移到明确标记的调试位置)
  • [ ] 在 commit/PR 信息中说明最终正确的假设,让下一个调试者从中学习