---
name: debug
description: 排查 bug 时用。顺序是硬的：先复现、再让证据淘汰假设、修根因、用原始复现路径验证。不要看到第一个可疑点就动手改。
---

# 调试

顺序是硬的：**复现 → 定位 → 修 → 验证**。跳过复现直接改，修完也不知道
修没修好；「看起来像这个问题」的改动堆几层，代码比原来更难懂。

## 1. 复现

- 拿到最小复现路径：什么输入、什么操作、期望什么、实际什么。
- 按用户的描述复现不出来时，先对齐环境差异（版本、配置、数据），
  不要按「大概是这个意思」开修。
- 间歇性的 bug 先想并发、时序、未初始化、缓存。复现率不到 100% 也要
  拿到一个「多跑几次必现」的脚本——没有它，最后没法证明修好了。

## 2. 定位：让证据淘汰假设

- 看到症状先列 2-3 个候选假设，再找**能区分它们**的证据——不是找
  支持第一个假设的证据。
- 二分收敛：注释掉一半、在中间点打日志、`git bisect`。每一步的目标
  是砍掉一半可能性，而不是「再看一眼」。
- `[约束]` 打日志要打**变量的值**，不是「到这里了」。执行路径只占
  bug 的少数，多数 bug 错在数据不在路径。
- git 历史是证据：`git log -p -- 那个文件` 看这行什么时候变的、当时
  的提交想干什么。「以前是好的」这类 bug，bisect 比读代码快得多。

## 3. 修

- 修**根因**，不是在症状处打补丁。上游给了空值，在下游判空只是把
  炸点挪远了，还把上游的问题盖住了。
- 最小改动。定位过程中看到的其它可疑点，记下来告诉用户，不要一起
  改——一起改就分不清哪个改动起了作用。

## 4. 验证

- 用第 1 步的**原始复现路径**再跑一遍，不是用「我觉得等价」的路径。
- 跑一遍相关测试，确认没修出新的问题。
- 收尾回答两个问题：**同类的地方还有吗**（同一个错误模式往往不止
  一处，grep 一把）；**要不要加个测试守住这里**（这个 bug 能溜进来，
  说明现在没有测试拦它）。

## 调试自己刚写的代码时

嫌疑最大的不是代码，是自己对「输入长什么样」「这个 API 行为如何」的
假设。打印真实输入和中间值对着看，比反复重读代码快。
