---
name: systematic-debugging
description: 在遇到缺陷、测试失败、构建失败、异常行为或排查卡住时使用。适用于不能靠猜修、需要先定位根因再改代码的场景。
---

# 系统化排查问题

## 目标

先找到根因，再做修复。  
避免“猜一个改一个”的随机试错。

## 何时使用

- 测试失败
- 构建失败
- 功能行为异常
- 线上或本地缺陷
- 已经试过几次修复但没解决

## 核心原则

- 先复现，再分析
- 先看证据，再下结论
- 先建立能区分对错的反馈环，再正式修复
- 一次只验证一个假设
- 排障早期可以并行收集互不依赖的证据，但结论必须按证据链收敛
- 修复症状不等于修复根因
- 在改代码前，必须能用一句话说出根因假设

## 基本流程

1. 读清楚报错和上下文
2. 稳定复现问题
3. 建立能稳定复现失败的反馈环：能稳定失败、能复跑、能在修复后变绿；可以是测试、最小复现命令、脚本、页面操作或日志判定
4. 并行收集互不依赖的证据：日志、配置差异、近期变更、网络/环境状态、相关源码路径
5. 建立假设矩阵，按证据强弱排序
6. 用一句话写出当前最高置信假设：`我认为根因是 X，因为证据 Y`
7. 用最小改动或最小观测手段验证该假设
8. 每轮验证后更新假设矩阵；被证伪的方向不要继续加码
9. 只有在根因明确后才正式修复

### 反馈环要求

- 没有反馈环时，只能做读取、复现、加观测或缩小范围；不要直接提交修复
- 反馈环必须足够小，能区分当前假设是否成立，不用一开始就跑全量测试
- 如果问题无法本地复现，要写清替代证据：日志模式、线上状态、CI 失败、用户提供的稳定步骤或最小可观察指标
- 修复后必须用同一个反馈环证明问题消失，再按风险补充更宽的验证

## 常见检查点

- 报错信息、堆栈、日志
- 最近提交或配置变更
- 输入、输出、状态流转
- 环境差异
- 网络路径、端口、DNS、代理、证书、服务发现和防火墙规则
- 是否有现成正常案例可对比
- 是否有最近已知正常版本版本、截图、旧行为或旧产物可当作对照

## 低层基线

系统、工具链、渲染、网络或生成物问题，先测更低一层的事实，再判断上层症状：

- 界面异常先区分 DOM/CSS/渲染层、数据层和状态层
- 网络异常先确认 DNS、代理、证书、端口和服务端响应，再改客户端逻辑
- 编译/工具链异常先确认版本、路径、环境变量和最小命令，再改业务代码
- 生成物异常先确认源文件、生成命令、输出路径和时间戳，再改消费方

## 假设矩阵

排障超过一个明显路径时，维护一个轻量矩阵：

```text
假设 | 证据 | 置信度 | 下一步检查 | 证伪条件
```

- `假设`：具体根因，不写“环境问题”这类空泛词。
- `证据`：已有事实，例如日志、配置差异、命令输出、源码条件。
- `置信度`：高 / 中 / 低。
- `下一步检查`：下一步最小验证动作。
- `证伪条件`：什么结果出现时应放弃该方向。

连续两个假设被证伪后，必须回到矩阵重新排序；不要沿着最熟悉的方向继续猜。

## 可用参考

遇到下面这些问题时，优先参考同目录下的补充文档：

- `root-cause-tracing.md`：适用于错误出现在很深的调用链里，需要一路往上追源头。
- `condition-based-waiting.md`：适用于测试或异步逻辑依赖时序，不能靠 sleep 猜等待时间。
- `defense-in-depth.md`：适用于一个问题可能需要多层保护，避免只修一处留下同类缺口。

## 强化模式

### 重复回归 / 截图对照模式

适用于：

- 用户说“还是不对”“修了但症状没消失”
- 用户给了旧截图、旧版本、旧产物作为参考
- 视觉结果以前是对的，现在坏了

处理方式：

1. 列出所有症状，不只盯住第一条。
2. 明确参考基准：旧截图、旧版本、旧提交、预期状态。
3. 先定义通过或失败检查，再决定怎么修。
4. 同样症状在一次修复后仍存在时，立即停止叠补丁，回到证据重新建模。

### 二分定位模式

适用于用户说“以前是好的”“之前是好的”“某个版本还能用”，或能提供最近已知正常版本。

1. 先保护当前工作区：`git status --short --branch -uall`；有改动时不要在当前检出目录直接二分定位。
2. 先读最近正常版本到当前的相关路径差异；如果范围不大，通常比完整二分定位更快。
3. 必须先定义非交互通过或失败命令；没有可复现检查时，二分定位只会制造噪音。
4. 二分定位得到嫌疑提交后，只读该提交差异，定位引入回归的具体条件。

### 范围扩散检查

适用于修复一类模式问题后，需要确认仓库里是否还有同型缺陷。

1. 提炼缺陷模式：函数、正则、选择器、锁、校验缺口、输入边界。
2. 在仓库中搜索同类形状。
3. 对每个命中项明确写出：同类问题 / 安全例外 / 还需确认。
4. 不要只修用户指出的那一处就宣布结束。

### 运行时证据阶梯

宣布修复前，尽量沿这条链路补齐证据：

1. 源码路径：具体到函数、条件、文件或状态转移
2. 稳定复现：最小复现命令、测试、页面路径或输入
3. 运行时证据：日志、状态、缓存、生成物、外部系统反馈
4. 针对性测试或构建
5. 真正的运行结果确认：尤其是界面、渲染、生成物问题

仅编译通过，不足以证明界面、渲染和产物类问题已修好。

### 定向日志

加日志前先写清楚：这条日志要回答什么问题。

- 第一条日志优先打在执行路径中点，不要直接堆在症状点
- 只记能区分假设的事实：分支、旧值/新值、输入键、错误码、顺序
- 临时日志在结束前移除
- 当假设涉及回调顺序、对象生命周期、异步状态、窗口/导航事件时，日志应作为形成假设的一部分，而不是连续修错后才补

### 配置修改安全线

排障中修改配置、版本、端口、权限或网络参数前：

1. 记录原值和文件路径。
2. 说明该修改要验证哪个假设。
3. 优先做最小临时变更，不混入无关优化。
4. 验证失败立即回滚，不把失败尝试留在最终状态。

## 常见错误

- “先随便改一下试试”
- 一次改很多地方
- 没复现稳定就开始修
- 只看表面症状
- 连续试了多次还不回头重新分析
- 同样症状没消失，还继续沿着旧假设补丁
- 修好一处就假设其他同类位置也没问题

## 输出要求

- 问题现象是什么
- 如何复现
- 当前证据指向什么
- 根因假设是什么
- 假设矩阵如何排序，哪些方向已被证伪
- 验证结果是什么
- 最终修复是否已经验证
- 若做了范围扩散检查，要写清还有无同类命中项

## 关键约束

- 根因没明确前，不要给大幅修复方案
- 连续几次假设都失败时，要停下来重新分析
- 不能把“猜中一次”当成排查方法
- 同样症状在一次修复后仍存在时，必须回到根因分析
- 连续 3 个假设被证据推翻后，应改为交接式汇报，而不是继续盲试
- 同一数值、阈值、间距或超时被调了 3 次还不对时，停止调参，回到结构约束或不变量
