| name | verify-workflow-debug |
| description | 系统化根因调试——先建反馈循环再假设。当遇到 bug、测试失败、意外行为,或提到"调试""debug""为什么不工作""crash" |
Debug — 系统化调试
入口/出口
- 入口: Bug 报告、测试失败、意外行为、性能问题、构建失败
- 出口: 复现测试通过 +
docs/bugs/<name>/01-root-cause.md(根因记录)
- 指向: 回到原流程(重新 build 或 review)
- 前置加载: CANON.md +
build-quality-tdd/SKILL.md
- 输出路径:
docs/bugs/<name>/01-root-cause.md → build-workflow-execute(重新 build)或 verify-workflow-review(重新 review)
何时不使用
- 已知行为不需要修复(已文档化的限制、预期行为)
- 环境问题简单重试可解决(网络闪断、服务重启)
Iron Law
```
根因调查在前,修复在后。
```
没有完成 Phase 1,不能提出修复方案。
流程:4 阶段必须按序
Phase 1:根因调查
在尝试任何修复之前:
-
读错误信息仔细 — 不跳过错误。读完整堆栈。记下行号、文件路径、错误码。
-
稳定复现 — 能可靠触发吗?准确步骤是?每次必现吗?不可复现 → 收集更多数据,不要猜。
-
构建最小复现 — 去掉无关代码/配置直到只剩 bug 本身。简化输入到最小触发用例。最小复现让根因变得明显,防止修复症状而不是原因。
-
查最近变更 — git diff、最近提交、新依赖、配置变更、环境差异。
-
多组件系统加诊断埋点 — 当系统跨多个组件(CI → build → signing,API → service → DB)时:
- 对每个组件边界:日志记录什么进入组件、什么离开组件
- 验证环境/配置传播
- 一次运行收集证据,显示在哪里断裂
- 然后分析证据→定位失败组件→具体调查该组件
-
向上追溯数据流 — 错误在调用栈深处时:从最终错误点向上追溯。错误值从哪来?谁带着错误值调用了这里?不断追溯直到找到源头。在源头修复,不在症状处修。
Phase 2:模式分析
在确定模式后再修复:
- 找工作示例 — 同代码库中相似的正常工作代码在哪里?
- 和参考实现对比 — 按模式实现时,完整阅读参考实现。不跳读。
- 识别差异 — 工作和不工作之间有什么不同?列出每一个差异,"这不重要"?这是最常见的陷阱。
- 理解依赖 — 需要哪些组件?什么设置/配置/环境?它做出了什么假设?
Phase 3:假设与验证
- 形成单一假设 — 写下来:"我认为 X 是根因,因为 Y"。具体不模糊。
- 最小化测试 — 做最小的变更来测试假设。一次只变一个变量。
- 验证通过再继续 — 确认了?→ Phase 4。没确认?→ 新假设。不要叠更多修复。
- 不知道时说不知道 — "我不理解 X"。不要假装知道。寻求帮助。研究更多。
Phase 4:修复
- 创建复现测试(RED) — 调用
build-quality-tdd/SKILL.md 写失败测试。先有测试再修复。没有复现测试的修复 = 没有修复。
- 实现单一修复 — 处理识别出的根因。一次一个变更。没有"顺便改一下"。
- 验证修复(GREEN) — 测试通过了吗?其他测试没受影响?问题确实解决了?
- 如果修复不工作 — STOP。数一下试了几次。
- < 3 次:回到 Phase 1 用新信息重新分析
- ≥ 3 次:冻结。进入 Phase 4.5
Phase 4.5:架构质疑门
连续 3 次修复失败 = 架构问题:
迹象:
- 每次修复都暴露新的共享状态/耦合/不同位置的问题
- 修复需要"大规模重构"才能实施
- 每次修复在其他地方引发新症状
STOP 并质疑基础:
- 这个模式从根本上成立吗?
- 我们是在"因为惯性而坚持它"吗?
- 重构架构 vs. 继续修复症状?
与人类讨论后再尝试更多修复。这不是假设失败——这是错误的架构。
错误类型专门诊断
测试失败
测试在代码变更后失败:
├── 代码被测试覆盖了?
│ └── YES → 测试还是代码错了?
│ ├── 测试过时 → 更新测试
│ └── 代码有 bug → 修复代码
├── 改了不相关的代码?
│ └── YES → 副作用 → 检查共享状态、import、全局变量
└── 测试本来就 flaky?
└── 检查时序问题、顺序依赖、外部依赖
构建失败
构建失败:
├── 类型错误 → 读错误信息,检查对应位置类型
├── Import 错误 → 模块存在?exports 匹配?路径正确?
├── 配置错误 → 检查构建配置文件的语法/schema
├── 依赖错误 → 检查 package.json,重装依赖
└── 环境错误 → Node 版本、OS 兼容性
运行时错误
运行时错误:
├── TypeError: Cannot read property 'x' of undefined
│ └── 某个值不该 null/undefined → 向上追溯数据流
├── 网络错误 / CORS
│ └── 检查 URL、headers、服务端 CORS 配置
├── 渲染错误 / 白屏
│ └── 检查 error boundary、console、组件树
└── 意外行为(无错误)
└── 关键路径加日志,验证每一步数据
Safe Fallback 模式
时间压力下使用安全降级,不崩溃:
function getConfig(key: string): string {
const value = process.env[key];
if (!value) {
console.warn(`Missing config: ${key}, using default`);
return DEFAULTS[key] ?? '';
}
return value;
}
function renderChart(data: ChartData[]) {
if (data.length === 0) {
return <EmptyState message="暂无数据" />;
}
try {
return <Chart data={data} />;
} catch (error) {
console.error('Chart render failed:', error);
return <ErrorBoundaryFallback />;
}
}
好坏示例
Good — 系统化根因 + 修复验证
Phase 1 读错误信息 + 稳定复现 + 最小复现 → Phase 2 找工作示例对比差异 → Phase 3 假设"N+1 查询是根因" → Phase 4 写复现测试(RED)→ 修复 → 测试通过(GREEN)。根因可追溯,修复可验证。
Bad — 随机试错法
"试试改这个"、"再改那个"、"改三个地方一起跑"。没有读错误信息、没有复现步骤、没有单一假设、没有复现测试。修了症状不知道根因,同类 bug 在其他位置复发。
输出模板
- Phase 1-3 使用
templates/bug/01-root-cause.md,落盘到 docs/bugs/<name>/01-root-cause.md
- Phase 4 使用
templates/bug/02-fix-plan.md,落盘到 docs/bugs/<name>/02-fix-plan.md
根因记录必须包含:Status Summary、症状、影响范围、时间线、复现步骤、复现证据、调查过程、根因、非根因排除、修复方向、Done When。
修复计划必须包含:Status Summary、修复目标、最小改动范围、复现测试、修复步骤、验证计划、回归风险、Follow-up Actions、Done When。
相关技能
- 写复现测试 →
build-quality-tdd/SKILL.md
- 验证修复 → CANON 第 5 条(Verify Don't Assume)
验证证据
输出或记录必须包含:
- 输入/来源: 读取的 spec、plan、代码、反馈或发布上下文。
- 执行动作: 实际完成的检查、生成、修复、导出或发布步骤。
- 验证结果: 命令、审查结论、产物路径、截图或人工确认。
- 阻塞/回退: 未通过项、回退路径或需要 human partner 决策的问题。
常见说辞
| 说辞 | 现实 | 后果 |
|---|
| "快速修复,之后调查" | 没有之后。先在根因,再修复。 | 修症状不修根因,同类 bug 在 3 个不同位置反复出现,累计修复时间 10x 于单次根因调查。 |
| "先改改看行不行" | 猜。先确定根因。 | 猜测式调试平均浪费 45 分钟(行业数据),系统化调试平均 15 分钟。每次猜错都在掩盖真实线索。 |
| "改多个地方一次跑" | 无法隔离有效变更。 | 两个变更互相干扰,通过纯属巧合。下一次只改其中一个时故障复现,且无法判断是哪个变更"真正"修复了问题。 |
| "跳过测试,手动验证" | 手动测试不能证明边界情况。 | 手动验证遗漏的边界条件(并发、空值、超时)以生产偶发故障形式出现,排查需 2-8 小时。 |
| "紧急情况没时间走流程" | 系统化调试比猜更快。 | 紧急中猜测式修复引入新 bug 的概率 ~40%,二次事故的停机损失 > 系统化调试多花的 10 分钟。 |
| "再试一次就好"(第 3+ 次) | 3 次失败 = 架构问题。质疑,不继续猜。 | 第 4、5、6 次尝试不会比前 3 次更好。每次失败都引入更多不确定性,最终不得不全部回退,浪费时间且代码更乱。 |
违反字面规则就是违反精神。 没有灰色地带。
验证失败处理
| 失败场景 | 处理方式 |
|---|
| 无法复现 bug | 收集更多数据(日志、监控、用户上下文),不要猜测。不可复现 = 不能确定修复 |
| 修复后测试仍失败 | STOP。数一下尝试次数。< 3 次 → 回 Phase 1 重新分析;≥ 3 次 → 进入 Phase 4.5 架构质疑门 |
| 修复引入回归 | 回退修复,重新分析根本原因和副作用 |
| 找不到工作参考示例 | 扩展搜索范围到同技术栈的其他项目,或寻求人类指导 |
| 复现测试本身有缺陷 | 修复测试,确保 RED→GREEN 循环有效。测试没错之前不要修代码 |
红旗 — STOP 走流程
如果发现自己想:
- "快速修复,之后调查"
- "先试试改 X"
- "改多个地方一次跑测试"
- "跳过测试,手动验证"
- "大概是 X,让我修"
- 提出修复方案前还没追溯数据流
- "最后一次尝试"(已经试过 2+ 次)
- 每次修复暴露不同位置的新问题
全部意味着:STOP。回到 Phase 1。
注意来自人类伙伴的信号:
- "是不是没发生?" — 你假设了但没验证
- "能不能...看看?" — 你加诊断证据
- "别猜了" — 你在没理解根因的情况下提修复方案
- "我们又卡住了?"(沮丧)— 你的方法不对
全部意味着:STOP。回到 Phase 1。
验证清单