debug
概览
debug 是处理 bug 的调查与决策入口。默认产物是诊断结论和经用户批准的修复契约;代码实施、验证和 Git 收尾统一交给 implement。
非 bug 的功能、设计、需求澄清走 scope。
铁律
先复现。追根因。给方案。等确认。再修复。
没有验证信号、根因证据、用户批准的修复计划,不写生产修复代码。
用户最初说“修一下”“帮我修”只表示目标,不等于批准修复方案。批准必须发生在你提交第 6 阶段内容之后。
禁止:
- 看症状直接改代码。
- 没有复现就猜原因。
- 用临时绕过冒充根因修复。
- 用户确认前写生产修复代码。
- 一次改多个变量。
- 在失败修复上继续叠补丁。
修复闸门
写任何生产修复代码前,先自检:
- 已用验证信号复现或锁定现象。
- 已找到有证据支撑的根因。
- 已向用户提交根因、证据、修复方案、验证计划和风险。
- 用户已在看到计划后明确同意继续。
任一项不成立,就停在调查或计划阶段,不要修改生产代码。
诊断阶段 Git 边界
- 只读调查、运行现有测试和不落盘实验不调用 Git skill。
- 如果复现需要把测试、脚本、日志配置或临时埋点写入仓库,首次写入前调用
git-workflow的prepare阶段。 - 诊断阶段执行过
prepare后,等待批准和继续调查沿用同一 Git 生命周期,不因内部阶段切换重复prepare或finalize。 - 任务取消、阻塞、验证失败或外部 handoff 时,按真实结果调用
finalize;不得把诊断产物当成已完成修复提交。 - 用户批准后把诊断产物列入修复契约;执行过
prepare时向 implement 传递git_state: prepared,纯只读诊断传递git_state: unprepared。
工作流
1. 明确问题与验证信号
先定义问题,再选择能判断 bug 存在/消失的信号。
确认:
- 实际行为和预期行为。
- 触发条件:输入、环境、配置、版本、账号、时间窗口、操作步骤。
- 影响范围:路径、调用方、平台、数据集。
- 最近变化:代码、依赖、配置、数据、部署。
选择最便宜可靠的验证信号:失败测试、命令/脚本、真实材料、临时复现脚手架、A/B 对照、人工复现步骤。
没有验证信号就停下,说明已尝试什么,并向用户要复现环境、材料,或临时埋点许可。
2. 复现并锁定现象
用验证信号看着 bug 出现。确认:
- 失败模式就是用户描述的 bug。
- 多次运行可复现;非确定性 bug 至少有足够复现率。
- 捕获了精确症状:错误消息、输出、状态、时序、截图或 trace。
3. 追根因
沿数据流和控制流找到源头:
- 读完整错误、警告、调用栈、错误码和路径。
- 查最近 diff、commit、依赖、配置、环境变化。
- 追踪错误值在哪里产生、传递、变坏。
- 多组件系统要检查边界输入、输出、配置传播和状态。
- 底层报错要沿调用链向上追到最初触发点。
不要只修症状位置,除非已经证明那里就是源头。
4. 对照正常模式
找相邻的正常实现,完整阅读后比较差异:
- 调用顺序、初始化、配置、依赖、环境假设。
- 数据结构、状态检查、错误处理。
- 好路径和坏路径在边界上的输入输出。
不要预设小差异没有影响。
5. 验证假设
生成 3-5 个排序后的可证伪假设。每个假设都写预测:
如果 X 是原因,那么改变 Y 会让 bug 消失,或改变 Z 会让 bug 更明显。
把假设列表给用户看;如果用户不在,按排序继续。每轮只测 top 1 假设,一次只改一个变量。
临时埋点必须服务于某个预测。优先调试器、REPL、性能分析器、查询计划、计时脚手架、二分定位;日志要打在能区分假设的边界,并加唯一标记,例如 [DEBUG-a4f2]。
6. 提交诊断结论与修复计划
写任何生产修复代码前,先向用户提交结论和计划:
- 根因:哪一层、哪个条件、哪条数据流或调用关系导致问题。
- 证据:哪个验证信号、日志、断点、diff、trace 或实验支持结论。
- 修复方案:准备改哪里、为什么这样改、排除了哪些替代方案。
- 验证计划:如何证明修好了;哪些测试、脚本、人工步骤或观察信号会重跑。
- Git 计划:确认后由
implement继承已有 Git 生命周期或执行prepare,并负责后续checkpoint、finalize及 commit/push/merge/cleanup。 - 风险:可能影响哪些路径,修复后要检查什么。
没有用户明确确认,不进入修复阶段。如果根因仍是猜测,明确说“还不能修”,回到假设验证。
7. 等待批准并形成 handoff
用户看到第 6 阶段的完整结论后明确同意,才形成以下实施契约:
root_cause: 已证实的根因
evidence: 复现、日志、trace、diff 或实验
repro: 可重复验证信号
fix_scope: 准备修改的文件、行为和排除项
acceptance: 修复完成必须满足的可观察结果
verification: 原始 repro、回归测试和相关测试
risks: 影响路径和回归风险
diagnostic_artifacts: 已写入仓库的诊断文件或临时埋点
契约缺少根因证据、repro、fix scope、验收或验证计划时不得交接。用户只说“继续看看”不等于批准修复。
8. 交给 implement 实施
用户批准后调用 implement,传入 source_kind: approved-fix、source: <完整修复契约>(含 diagnostic_artifacts)、wiki_target: <已确认目标或 none>,以及与诊断阶段一致的 git_state: prepared | unprepared。不要在 debug 内重复实现、checkpoint 或最终汇报。
从此由 implement 独占:
- Git 生命周期所有权;仅
unprepared时执行prepare,并负责checkpoint、finalize - 对话 Todo 和文件所有权
- 测试先行的 RED、最小 GREEN、重构和回归验证;优先复用 debug 交接的失败测试或 repro
- 原始 repro、回归测试和相关测试
- 临时诊断产物清理
- commit、push、merge、cleanup 和最终完成报告
实施发现根因错误或关键假设被推翻时,implement 停止并把证据退回 debug;不要在执行阶段重新猜根因。
方法准则
调用栈深处的错误
不要只修底层报错点。沿调用链问:谁传入了坏值,坏值在哪里创建,为什么上游允许它进入系统。
flaky / race / timeout
不要猜 sleep 时间。等待真实条件:事件出现、状态 ready、文件存在、计数达到、队列为空、请求完成。
纵深防御
如果根因是非法数据、危险状态或错误环境,沿数据流补防御:入口校验、业务校验、环境守卫、必要取证。
人工复现
必须由用户点击或观察时,写清步骤、要捕获的输出和判断标准,再把反馈转成可验证事实。
看到就停下
| 念头 | 现实 |
|---|---|
| "先临时修一下" | 这是绕过,不是根因修复。 |
| "八成是 X,我直接改" | 没有预测和证据就不是假设。 |
| "一次改多处省时间" | 你会不知道哪一处有效。 |
| "问题简单,不需要流程" | 简单 bug 也有根因。 |
| "测试后面再补" | 没有失败测试,无法证明修复针对原 bug。 |
| "再试一个 fix" | 失败后先重新调查。 |
| "日志全打上再说" | 临时埋点必须服务于具体预测。 |
| "这里太难测" | 可能是 seam 不对,也可能是设计阻止验证。 |
| "用户会同意这个修法" | 先列根因和方案,等用户确认。 |
用户说 "Stop guessing"、"Will it show us...?"、"Is that not happening?" 或类似话时,立即回到根因调查。