diagnosing-bugs
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDiagnosing Bugs
诊断 Bug
Debugging is not guessing with extra steps; it's a search problem, and a search problem has a systematic solution: understand the space you're searching, reproduce the failure on demand, shrink the search space by half at a time, and change exactly one thing per experiment so cause and effect stay unambiguous. Every rule below traces back to Agans' Debugging: The 9 Indispensable Rules; treat them as a sequence, not a menu to pick from.
Redact secrets before showing any command, log, or captured artifact: same discipline as 's Phase 3: replace credentials with , quote only the lines carrying real signal from anything with auth headers or full payloads.
secure-coding<REDACTED>调试不是加上额外步骤后的猜测;它是一个搜索问题,而搜索问题有系统化的解法:理解你要搜索的空间,按需复现故障,每次将搜索空间缩小一半,并且每次实验只改变一件事,从而让因果关系始终明确。下面的每条规则都源自 Agans 的 Debugging: The 9 Indispensable Rules;应将它们视为一个执行顺序,而不是可以随意挑选的菜单。
在展示任何命令、日志或捕获内容之前,先对其中的 secrets 进行脱敏:遵循与 Phase 3 相同的纪律,使用 替换凭据;对于包含 auth headers 或完整 payloads 的内容,只引用真正携带有效信息的行。
secure-coding<REDACTED>Rule 1: Understand the system
规则 1:理解系统
Before touching anything, know the normal path the failing code should take: read or equivalent domain docs if they exist, trace the flow from entry point to the point of failure, and know what "correct" looks like well enough to recognize it. A theory formed without this is a guess with the system's real shape substituted by an assumed one.
CONTEXT.md在动手之前,先了解失败代码本应遵循的正常路径:如果存在 或同类领域文档,就阅读它们;从入口追踪到故障发生点;并充分了解“正确”状态是什么样,以便能够识别它。脱离这些认识而形成的理论,不过是用假设的形态替代了系统真实形态的猜测。
CONTEXT.mdRule 2: Make it fail, then look before you theorize
规则 2:让它失败,然后先观察,再提出理论
Reproduce on demand first. A bug that only happens "sometimes, somewhere in prod" isn't diagnosable yet; build the smallest repeatable trigger: a failing test, a script, a request replayed against a dev instance. For a flaky/non-deterministic failure, the goal isn't a clean 100% repro but a high enough reproduction rate to observe: loop it, add stress, narrow timing windows, until it fails often enough to study.
Then look, don't theorize. With a reproducible failure in hand, inspect the actual evidence (logs, state, a debugger breakpoint) before forming a hypothesis. A theory built by staring at code without first looking at what the system actually did is the single most common way to burn time chasing the wrong cause.
首先按需复现。 一个只是“有时在生产环境某处”发生的 bug,目前还无法诊断;应构建最小可重复触发方式:失败的测试、脚本,或针对开发实例重放的请求。对于 flaky/non-deterministic 故障,目标不是实现干净的 100% 复现,而是获得足以观察的较高复现率:循环执行、施加压力、收窄时间窗口,直到它足够频繁地失败以供研究。
然后先观察,不要先推演理论。 掌握可复现的故障后,先检查实际证据(日志、状态或 debugger breakpoint),再形成假设。只盯着代码、却没有先查看系统实际做了什么就构建理论,是浪费时间追查错误原因最常见的方式。
Rule 3: Divide and conquer
规则 3:分治法
Shrink the suspect region by bisecting it, not by scanning it linearly:
- Across code: binary-search the call stack or commit history (): is the bug present at the midpoint between known-good and known-bad?
git bisect - Across input: shrink a large failing input to the smallest one that still fails, cutting roughly half at a time.
- Across the system: if the failure could be in any of several components, test the middle of the chain first (does data arrive there correctly?) rather than starting at one end and walking through every component in order.
Each bisection step is itself an experiment: apply Rule 4 to it.
通过二分查找缩小可疑范围,而不是线性扫描:
- 跨代码: 对调用栈或提交历史进行二分查找():已知正常与已知异常之间的中点是否仍存在该 bug?
git bisect - 跨输入: 将大型失败输入缩减为仍会失败的最小输入,每次大约削减一半。
- 跨系统: 如果故障可能位于多个组件中的任意一个,先测试链路的中间位置,例如数据是否正确到达那里,而不是从一端开始依次检查每个组件。
每次二分步骤本身也是一次实验:对其应用规则 4。
Rule 4: Change one thing at a time
规则 4:每次只改变一个变量
Every experiment changes exactly one variable and keeps everything else pinned. Two simultaneous changes (a code edit and an environment change; a config flip and a data change) make it impossible to attribute the resulting behavior to either one; a "fix" applied this way might be doing nothing, or might be masking a second, still-live bug.
每项实验恰好只改变一个变量,并让其他所有条件保持固定。同时进行两项更改,例如代码修改和环境变更,或者配置开关和数据变更,就无法判断结果行为由哪一项导致;以这种方式实施的“修复”可能毫无作用,也可能只是掩盖了第二个仍然存在的 bug。
Rule 5: Keep an audit trail
规则 5:保留审计记录
Write down every experiment and its result as you go, including the negative ones; "tried X, no change" is information, and without a record it gets re-tried by mistake an hour later. Tag any temporary debug instrumentation with a unique, greppable prefix so cleanup at the end is a single search, not a memory exercise. Write the audit trail and final explanation without em dashes, using a comma, colon, period, or a reworded sentence instead.
随着进展,记录每次实验及其结果,包括否定性结果;“尝试了 X,没有变化”本身就是信息。如果没有记录,一小时后可能会误以为它从未尝试过,并再次尝试。为所有临时调试插桩添加唯一且可 grep 的前缀,使最终清理只需执行一次搜索,而不必依赖记忆。撰写审计记录和最终说明时不要使用破折号,而应使用逗号、冒号、句号,或换一种说法。
Rule 6: Check the plug
规则 6:检查基本前提
Verify the boring, obvious assumptions before trusting an elaborate theory; is the service actually running, is the config pointing at the environment you think it is, is the clock right, is the code you're reading actually the code that's deployed. A sophisticated hypothesis built on top of a false boring assumption wastes far more time than the thirty seconds it takes to check the assumption first.
在信任复杂理论之前,先验证那些平淡但显而易见的假设:服务是否确实正在运行,配置是否指向你以为的环境,时钟是否准确,以及你正在阅读的代码是否确实是已部署的代码。建立在错误基本假设之上的复杂理论,浪费的时间远超先花三十秒检查假设所需的时间。
Rule 7: Get a fresh view when stuck
规则 7:陷入僵局时,换一个视角
If several bisection/hypothesis cycles haven't converged, stop and explain the bug out loud to someone else, or write it out as if explaining it to them (rubber-duck works even with no one there to hear it). Articulating the full chain of evidence out loud routinely surfaces the gap in reasoning that silent re-reading doesn't.
如果经过多轮二分查找或假设验证仍未收敛,就停下来,向其他人解释这个 bug,或者写出来,仿佛正在向他们解释一样。即使现场无人可听,rubber-duck(小黄鸭)调试法也有效。把完整证据链大声说出来,通常能暴露单纯反复阅读时未发现的推理缺口。
Rule 8: If you didn't fix it, it ain't fixed
规则 8:如果你没有修复它,那它就不算修复完成
A fix isn't done because the code changed and looks right; it's done when the original reproduction from Rule 2 now passes, and only then. Turn that reproduction into a regression test at a real seam (see for what makes a seam correct) so the same failure can't silently return. If no correct seam exists for a regression test, that absence is itself a finding; flag it, since it means the architecture is what's letting this class of bug reach production undetected.
tdd代码改了而且看起来正确,并不代表修复完成;只有当规则 2 中的原始复现现在通过时,修复才算完成。随后,在合适的 seam 处把该复现转化为回归测试(关于什么是正确的 seam,参见 ),防止同一故障悄然重现。如果不存在适合编写回归测试的正确 seam,那么这一缺失本身就是一个发现;应明确标记出来,因为这意味着正是架构让这类 bug 得以在未被发现的情况下进入生产环境。
tddDone when
完成标准
- The failure has a reliable (or high-rate, for flaky bugs) reproduction; a command, script, or test you've actually run.
- The search space was bisected, not scanned; each experiment changed exactly one variable.
- The audit trail shows what was tried, including dead ends, and all temporary instrumentation is removed.
- The original repro now passes, and a regression test locks it down at a correct seam (or the absence of one is explicitly flagged).
- 故障已有可靠复现,对于 flaky bug 则需达到较高复现率;并且你实际运行过相应的命令、脚本或测试。
- 搜索空间通过二分查找逐步缩小,而不是被线性扫描;每项实验恰好只改变一个变量。
- 审计记录展示了尝试过的所有内容,包括死胡同,并且所有临时调试插桩均已移除。
- 原始复现现在能够通过,并且回归测试已在正确的 seam 处锁定该问题,或者明确标记不存在这样的 seam。