用途
系统化调试技能用于处理任何技术问题,包括测试失败、生产环境 bug、异常行为、性能问题、构建失败和集成问题。它的核心目标是:在尝试任何修复之前,先找到根本原因。技能明确指出,随机修复会浪费时间并引入新 bug,快速补丁只会掩盖底层问题。
核心原则
技能遵循一条“铁律”:没有完成根因调查,就不能提出修复方案。如果尚未完成第一阶段,就不允许提出修复。症状层面的修复被视为失败。
四阶段框架
第一阶段:根因调查
在尝试任何修复之前,需要:
- 仔细阅读错误信息,不要跳过错误或警告,完整阅读堆栈跟踪,记录行号、文件路径和错误代码。
- 稳定复现问题,确认能否可靠触发、具体步骤是什么、是否每次都发生;如果无法复现,应收集更多数据而不是猜测。
- 检查最近变更,包括 git diff、近期提交、新依赖、配置变更和环境差异。
- 在多组件系统中收集证据。当系统包含多个组件(如 CI → 构建 → 签名,或 API → 服务 → 数据库)时,在提出修复前,应在每个组件边界添加诊断插桩:记录进入组件的数据、离开组件的数据、验证环境/配置传播、检查每一层的状态。运行一次以收集证据,显示问题出在哪里,然后分析证据定位失败组件,再调查该特定组件。
- 追踪数据流。当错误出现在调用栈深处时,使用内置向后追踪:坏值从哪里来?谁用坏值调用了这里?一直向上追踪直到找到源头,在源头修复而不是在症状处修复。
技能提供了内置深栈追踪方法,适用于 bug 出现在远离真实触发点的情况。典型信号包括:错误发生在执行深处而非入口点、堆栈跟踪很长、能看到失败位置但看不到坏值来源、诱人的修复点在症状处。追踪过程为:观察症状、找到直接原因、追问谁调用了它、持续向上追踪直到找到第一个坏输入、无效假设或错误触发点,在那里修复,然后在较低层添加纵深防御。技能还给出了在危险操作前添加插桩的代码示例,并强调追踪规则:绝不要停在“这一行崩溃了”,要继续追问是哪个调用者、输入或状态转换产生了坏值。
第二阶段:模式分析
在修复前找到模式:
- 寻找可工作的示例,在同一代码库中定位类似的可工作代码。
- 对照参考实现,如果是在实现某个模式,要完整阅读参考实现,不要略读,要读每一行,在应用前完全理解该模式。
- 识别差异,列出可工作与损坏之间的每一个差异,无论多小,不要假设“那个不可能有影响”。
- 理解依赖,包括需要哪些其他组件、什么设置/配置/环境、它做了哪些假设。
第三阶段:假设与测试
采用科学方法:
- 形成单一假设,清晰陈述“我认为 X 是根因,因为 Y”,写下来,要具体而非模糊。
- 最小化测试,做尽可能小的改动来测试假设,一次只改变一个变量,不要同时修复多个东西。
- 继续前先验证。成功则进入第四阶段;失败则形成新假设,不要在原有基础上叠加更多修复。
- 当不知道时,直接说“我不理解 X”,不要假装知道,可以寻求帮助或进一步研究。
第四阶段:实施
修复根因而非症状:
- 决定测试策略。根据复杂度自动决定:对于复杂算法、业务逻辑、数据转换等容易出 bug 的地方编写测试;对于 UI 组件、React hooks、简单 CRUD、直接映射以及 100% 确定正确的内容跳过测试。测试类型仅限确定性单元测试,不做集成测试、复杂 mock 或异步复杂性。
- 如果编写测试:使用最简单的复现,自动化测试在修复前失败,验证逻辑而非实现细节。
- 如果跳过测试:通过类型检查/lint 验证修复,对 UI 变更进行手动验证,通过代码审查确认修复正确。
- 实施单一修复,针对已识别的根因,一次只做一个改动,不做“顺便”改进,不捆绑重构。
- 验证修复。如果写了测试:测试现在通过吗?其他测试没被破坏吗?如果没写测试:类型检查通过吗?lint 干净吗?手动验证确认修复吗?始终检查:问题真的解决了吗?相关功能没有回归吗?
如果修复无效:停止。统计已尝试的修复次数。如果少于 3 次,返回第一阶段,用新信息重新分析。如果达到或超过 3 次,停止并质疑架构。不要在未进行架构讨论的情况下尝试第 4 次修复。
如果 3 次以上修复失败:质疑架构
表明架构问题的模式包括:每次修复都在不同地方揭示新的共享状态/耦合/问题;修复需要“大规模重构”才能实现;每次修复都在别处产生新症状。此时应停止并质疑根本问题:这个模式从根本上合理吗?我们是不是“纯粹出于惯性在坚持”?应该重构架构还是继续修复症状?在尝试更多修复前与人类伙伴讨论。这不是失败的假设,而是错误的架构。
危险信号
如果发现自己有以下想法,应立即停止并回到第一阶段:
- “先快速修复,稍后再调查”
- “先试试改 X 看看行不行”
- “添加多个改动,运行测试”
- “大概是 X,让我修一下”
- “我不完全理解,但这可能有用”
- “模式说 X,但我会用不同方式调整”
- “主要问题有这些:[列出修复方案而没有调查]”
- 在追踪数据流之前提出解决方案
- “再试一次修复”(已经试过 2 次以上时)
- 每次修复都在不同地方揭示新问题
- 在确定修复正确时仍为 UI 组件编写测试
人类伙伴的错误信号
注意这些重定向:
- “那没有发生吗?”——你没有验证就假设了
- “它会向我们展示……吗?”——你应该添加证据收集
- “停止猜测”——你在不理解的情况下提出修复
- “深入思考这个”——质疑根本问题,而不仅仅是症状
- “我们卡住了?”(沮丧)——你的方法不奏效
看到这些时:停止,回到第一阶段。
常见合理化借口
技能列出了常见借口及其现实:
- “问题很简单,不需要流程”——简单问题也有根因,流程对简单 bug 很快。
- “紧急情况,没时间走流程”——系统化调试比猜测-检查的反复折腾更快。
- “先试试这个,然后再调查”——第一次修复就设定了模式,从一开始就做对。
- “同时多个修复节省时间”——无法隔离哪个起作用,会导致新 bug。
- “参考太长,我会调整模式”——部分理解必然导致 bug,要完整阅读。
- “我看到问题了,让我修复它”——看到症状不等于理解根因。
- “再试一次修复”(2 次以上失败后)——3 次以上失败等于架构问题,质疑模式,不要再修复。
- “UI 修复不需要测试”——正确!UI 组件通过类型检查/手动测试验证,而不是单元测试。
快速参考
- 第一阶段:根因——阅读错误、复现、检查变更、收集证据;成功标准是理解 WHAT 和 WHY。
- 第二阶段:模式——寻找可工作示例、比较;成功标准是识别差异。
- 第三阶段:假设——形成理论、最小化测试;成功标准是确认或形成新假设。
- 第四阶段:实施——创建测试、修复、验证;成功标准是 bug 解决、测试通过。
当流程揭示“没有根因”
如果系统化调查发现问题确实是环境性的、时间相关的或外部的:
- 你已经完成了流程
- 记录你调查了什么
- 实施适当的处理(重试、超时、错误消息)
- 为未来调查添加监控/日志
但要注意:95% 的“没有根因”情况是调查不完整。
与其他技能的集成
测试技能(需要时):tdd(如果可用)——在修复需要测试覆盖的复杂业务逻辑时使用;对于 UI 组件、简单 CRUD 或任何可通过类型检查/手动测试验证的内容跳过。
实际影响
根据调试会话:系统化方法 15-30 分钟修复;随机修复方法 2-3 小时反复折腾;首次修复率 95% 对 40%;引入新 bug 接近零对常见。