关于 debug
要求先建立可复现的失败信号并调查根因,再着手修复的调试技能。它引导开发者缩小复现范围、验证排序后的假设、添加回归测试并检查结果。
主要功能
- 01建立可触发失败的复现命令
- 02缩小故障复现范围
- 03排序并验证可证伪假设
- 04修复前添加回归测试
- 05验证修复并清理调试代码
适用场景
- 01调查可复现的软件故障
- 02追踪跨系统边界的错误
- 03根因修复后防止问题回归
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jayden-dang/ skills --skill debug安装后在 Claude Code 中输入/debug确认已加载。
详细介绍
调试
铁律
NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST
靠猜测和打补丁会浪费数小时,还会引入新的 Bug。这套流程适用于所有技术问题,尤其是在最容易让人想跳过时:紧急情况、“显而易见”的单行修复,以及上一次修复无效后的那一刻。
阶段 1 — 建立反馈循环(关卡)
在提出任何理论之前,先构建并运行一个能显示失败的信号:一条命令——测试调用、脚本或 curl——它现在会因为这个确切的 Bug 而失败,并会在修复后通过。建立正确的反馈循环,Bug 就解决了 90%;之后的每个阶段都只是利用这个循环。
发现自己在这条命令尚未准备好之前就开始读代码、形成理论?停下。没有能显示失败的命令,就不能进入阶段 2。
构建反馈循环的策略,按大致优先顺序排列:
- 在能够触及 Bug 的任意接缝处编写失败测试(单元、集成、端到端)
- 使用 curl / HTTP 脚本请求正在运行的开发服务器
- 使用固定样例输入调用 CLI,并与已知的正确输出进行比较
- 编写无头浏览器脚本,断言 DOM/控制台/网络行为
- 将捕获的轨迹(保存的请求、载荷、事件日志)重放到代码路径中
- 编写临时测试工具:截取系统的最小部分来触发 Bug 路径
- 对随机输入运行属性测试/模糊测试循环,适用于输出“有时出错”的情况
- 如果 Bug 出现在两个已知状态之间,使用二分测试工具(
git bisect run) - 差分循环:将相同输入分别传给旧版和新版,并比较输出
- 人工参与脚本——最后手段,仅适用于必须由人实际操作的情况;将操作步骤写成脚本,让循环保持结构化
然后优化它:更快(缓存设置、缩小范围——以秒计,而不是分钟)、更精准(断言用户的确切症状,而不是“没有崩溃”)、更确定(固定时间、随机种子,隔离文件系统)。遇到非确定性 Bug?不要执着于干净地复现;提高复现率(循环触发 100 次、增加压力、缩短时间窗口),直到足以据此调试。
确实无法构建这样的循环?请明确说明,列出你尝试过的方法,并向用户索取可复现的环境、捕获的材料,或添加临时检测代码的许可。不要凭感觉继续。
**关卡检查:**说出命令,并贴出一次运行时的失败输出。
阶段 2 — 复现并简化
运行循环;确认它因用户描述的问题而失败(附近但不同的失败 = 找错了 Bug = 修复错了问题)。阅读完整错误信息——完整消息、完整堆栈跟踪和行号;答案往往就在错误里。检查近期改动:git diff、新依赖、配置、环境。
一次删减一项来缩小复现范围(输入、配置、调用方、步骤),每次删减后都重新运行,直到剩下的每个元素都不可或缺——移除其中任何一项都会让测试通过。最小复现能缩小假设范围,并成为回归测试。
对于多组件系统(CI → 构建 → 部署、API → 服务 → DB):在每个边界添加检测——记录每层的输入和输出——并先运行一次,确认问题出在哪里,然后再动任何修复。当错误出现在调用链深处时,沿调用方反向追踪错误值,直到找到最初的触发点。绝不要只修复错误出现的位置;那是在处理症状。
阶段 3 — 提出假设
在测试任何假设之前,先列出 3–5 个按优先级排序的假设(单个假设会让你锚定在第一个看似合理的想法上)。每个假设都必须可证伪,并给出明确的预测:“如果 X 是原因,那么更改 Y 就会让 Bug 消失”。如果你无法说出预测,那就只是凭感觉——请完善或舍弃该假设。把排序后的列表展示给用户(他们通常会立刻重新排序);即使用户不在,也不要因此停下。
先测试最小的假设。一次只改变一个变量——绝不要叠加更改。条件允许时,使用调试器/REPL 进行插桩(一个断点胜过十条日志);否则使用带有唯一前缀的定向日志进行探查(例如,[DBG-x7q2]),这样清理时只需 grep 一次——绝不要记录所有内容再 grep。性能 Bug:先测量基线(性能分析器、计时测试框架),再二分排查;日志在这种情况下会误导你。不理解某事?就说“我不理解 X”并进行调查——绝不要假装理解并猜测。
假设被证伪?将其划掉,转向下一个。不要在失败的修复上再堆一个新修复。
阶段 4 — 修复
- 先编写失败的回归测试。 必需子技能:使用
tdd。测试必须放在正确的接缝处——能够复现实际发生的 Bug 模式。如果不存在正确的接缝,这本身就是一项发现:记录下来,并在事后复盘中指出;在此处编写浅层测试会造成虚假的信心。 - 只做一个修复,解决根本原因。不要做“顺手改进”,也不要打包进行重构。
- 观察回归测试通过,重新运行完整测试套件,再用最初未缩减的场景重新运行阶段 1 的循环。
修复尝试失败 3 次 = 停止。 问题在于架构,而不是你最新的假设——尤其是每次修复都会暴露别处的新耦合时。第 4 次尝试前,先与用户讨论。
退出 — 迷你规格、清理、事后复盘
- Tier-1 迷你规格:在所属功能的
requirements.md(如果没有功能归属,则在docs/specs/fixes.md)中添加修复需求和SHALL CONTINUE TO防护需求。用新的 ID 标记回归测试。 - 移除所有插桩:grep 查找你的
[DBG-...]前缀;删除临时测试框架。 - 在提交说明中写明已确认的根本原因。
- 提问:“怎样才能避免这个 Bug?”如果答案涉及架构(缺少良好接缝、存在隐藏耦合、调用方关系错综复杂),请详细记录并告诉用户运行
/improve-architecture——等修复完成后再运行,此时你掌握的信息最多。 - 必需子技能:在声称 Bug 已修复之前,使用
verify。
合理化说辞
| 想法 | 现实 |
|---|---|
| “情况紧急,没时间走流程” | 查明根本原因比反复猜测和试错更快 |
| “显然就是 X,我直接修好了” | 看到症状不等于理解原因 |
| “先快速打个补丁,之后再调查” | 补丁会变成永久修复,而问题原因会再次引发故障 |
| “同时试几种改动,节省时间” | 你无法判断哪种改动奏效了,而且其中一种可能会破坏其他东西 |
| “太简单了,不需要复现” | 简单的 Bug 也有根本原因;整个循环只需几分钟 |
| “修复后再补回归测试” | 未经测试的修复会导致回归;红色测试能证明修复确实有效 |
| “再试一次”(已经失败 2 次以上) | 不先讨论架构就进行第 4 次尝试,只是在徒劳折腾 |
用户信号 — 返回阶段 1
| 用户说 | 意味着 |
|---|---|
| “别再猜了” | 你还没找到根本原因就提出了修复方案——返回阶段 1 |
| “这真的在发生吗?” | 你未经验证就作出了假设——收集证据 |
| “这样能让我们发现什么吗?” | 你的探查没有对应任何预测——重新陈述假设 |
| “我们在原地打转” | 统计失败的修复次数;你很可能已经到了架构关卡 |