Systematic Debugging 是一套严格的调试工作流,用于处理 bug、测试失败或意外行为。
【用途】 当测试失败、生产或本地出现 bug、响应/状态/渲染/查询结果与预期不符、调查性能退化、超时、竞态条件或间歇性失败,以及问题在修复后至少一次再次出现时,应使用本技能。资料明确不接受以下借口:「看起来很简单,我直接修」「没时间,先打个补丁继续」「大概就是这里,改一下就好」。
【三项核心目的】 修复原因而非症状;防止基于猜测的修复;在修复前用测试锁定失败。
【硬性门槛】 以下规则没有例外:在获得可复现或可观察状态之前不得修复;在陈述根因假设之前不得修复;在拥有失败测试或等价复现机制之前不得修复;每次只验证一个假设;修复期间不做「顺便」重构;如果三次修复尝试失败,在应用下一个补丁前应怀疑结构性问题。违反该流程被视为一次调试失败。
【必填输出契约】 使用本技能时必须在内部锁定以下七项:问题陈述(用一句话定义哪里出错)、复现路径(如何复现或观察失败)、证据(实际观察到的结果)、根因假设(为何认为该问题发生)、失败守卫(失败测试、复现脚本或日志验证之一)、修复(针对根因的单一修复)、验证(修复后的复现路径与相关测试结果)。若缺少其中任何一项,工作即未完成。
【工作流程】 阶段一,定义问题:先压缩问题,明确预期行为、实际观察行为、影响范围和是否总是可复现;输出格式为 Problem: <expected> but got <actual> under <condition>;不得把症状与猜测混在一起。 阶段二,复现或插桩:修复前必须能再次看到失败,优先级依次为用现有测试复现、最小集成测试复现、单元测试复现、通过复现脚本或命令观察、添加日志/插桩后观察;规则包括把复现路径做到最小、即使只在 UI 可见也尽量在更低层复现、对间歇性失败通过日志与捕获输入/时间戳/并发条件提升可观测性;若复现失败,不要进入修复,而应提升可观测性。无法复现时的处理包括:记录输入值、检查环境差异、检查近期变更、在边界点添加日志、寻找产生相同症状的更小条件。 阶段三,收集证据:只收集可观察事实,始终检查完整错误信息与堆栈跟踪、失败输入值、最近变更的文件或提交、环境/配置差异、调用路径与数据流;对多组件问题在每个边界检查输入、输出、被转换的值以及何种条件下会出错;在定位问题位置之前不要修复。 阶段四,隔离根因:只提出一个候选原因,格式为 Hypothesis: <root cause> because <evidence>;好的假设应指向单一原因、与观察证据相关联、可用小实验反驳;坏假设如「似乎某处有异步问题」「整个序列化层似乎不稳定」;应把原因追溯到源头,若错误出现在堆栈深处,应追踪输入来源而非症状。 阶段五,锁定失败:修复前锁定失败,优先级依次为自动化失败测试、在现有测试中添加回归用例、最小复现脚本、通过日志/断言临时验证;规则包括尽可能创建自动化测试、它在修复前必须失败、修复后必须在同一路径上通过、测试名称必须揭示出哪里坏了;若自动化测试可行,应配合 test-driven-development 技能使用。 阶段六,实施单一修复:修复只针对一个假设,允许直接处理原因的最小代码改动及验证所需的最小配套改动;禁止捆绑多个看似相关的修复、把重构与修复合并、偷偷加入格式化/清理/重命名、无证据地添加空值守卫、吞掉异常;若修复失败,立即回到阶段一或阶段三,说明之前的假设是错的。 阶段七,验证并收尾:关闭前必须全部满足:原始复现路径不再失败、新的失败守卫通过、相关测试未被破坏、你能解释该修复阻断的是原因而非症状;对间歇性 bug,一次通过不够,需要在重复运行或变化条件下验证。
【停止条件】 以下情况应停止并重新框定问题。一、复现失败:多次尝试仍无法复现时,检查可观测性是否不足、是否存在环境差异、问题定义是否错误;禁止在没有复现的情况下改代码。二、三次修复失败:若连续三次修复未命中,应得出结论:当前理解有误,或问题很可能是结构性的(共享状态、边界设计、职责分离);此时「第四个补丁」不是答案,需要结构性讨论。三、没有失败守卫:若无法创建失败测试或等价复现机制,不要宣布完成,至少留下复现命令与观察结果。
【危险信号】 若出现以下想法,立即停止并回到更早阶段:「我就改这一行应该就能跑」「日志稍后再看,先修」「测试稍后再补」「这个和那个一起一次修掉」「错误消失了,所以我不需要知道原因」。
【最小检查清单】 用一句话定义了问题;已复现或使失败可观察;已收集证据;已创建单一根因假设;修复前已创建失败守卫;只应用了单一修复;修复后通过同一路径验证。
【完成标准】 本技能的完成标准不是「代码改了」。完成标准是:问题定义清晰、失败在修复前被锁定、修复与根因相连、验证结果保留。缺少这四项,调试就不算结束。