再学 AI
第 41 课 / diagnose
历史对照阅读 → 对照 → 问答 → 场景

历史对照:从主观反馈信心到明确门槛

这是早期诊断入口,已有复现、假设、观测和回归检查。与当前 diagnosing-bugs 对照,可看见完成标准怎样被收紧。

历史文件,不在当前目录

本课使用下方注明的历史提交原文,帮助理解旧文章和旧提示词。请勿据此认定当前版本仍能直接调用该名称。

还不清楚 Skill、Agent、安装和调用?先读 从零开始的6节入门课

在关系图中查看 diagnose 与其他技能的关联 →

先把必要的概念讲清楚

这是诊断循环的历史版本。它同样强调先建立反馈循环,但阶段一的完成表述较模糊,后来的 diagnosing-bugs 将目标 bug 已报红、已运行的要求写得更明确。

下面是老师补充的入门说明;原作者的要求保留在中英对照正文中。所有例子均为帮助理解而构造的教学情境。

fixture / harness|测试样本与运行装置

fixture 是为复现或测试准备的已知输入和初始数据,例如固定课程清单。harness 是把代码、输入和检查串起来的运行装置,例如一次命令启动最小服务并断言结果。它们帮助每次在相同条件下比较,而不是凭上次页面看起来怎样判断。

regression|原来正常,修改后退化

已有能力在后续修改中变坏,可以是功能错误或性能变慢。回归测试把曾经的故障场景固定为检查,帮助以后发现同类问题。但测试只覆盖写明的输入和边界,单条回归测试不是绝对不会再出错的保证。

seam|测试接入点

作者在测试语境中指测试进入系统、触发行为并观察结果的公共边界。例如调用“收藏课程”接口,再通过“我的收藏”接口检查结果。入口可以是模块公开函数、服务接口或页面,并非一定是浏览器。选择高层稳定入口能覆盖内部多个步骤;入口少不等于测试场景少。

interface / API|接口

使用者与一项能力交互时遵循的约定,包括可调用什么、传入什么、得到什么、失败怎样表示。接口可以是程序函数,也可以是网络请求。创建收藏接口接收用户和课程信息、返回收藏结果;它不需要向调用者暴露数据库表结构。这里的接口通常不是指页面外观。 本教材涉及更广义的“接口”时,还包括错误、调用顺序和约束等使用约定;不能把接口一概等同于网络 API。

mock|替代真实依赖的测试对象

测试时用可控制的对象替代真实服务,例如让模型调用固定返回一句答案。这样可以稳定检查程序如何处理响应,但无法据此证明真实模型总能生成好答案。过度替代内部组件还会让测试和内部写法绑定,一重构就要改测试。

CI|持续集成检查

把修改提交到共享流程时,自动运行测试、类型检查等约定检查,尽早发现集成问题。绿灯表示配置的检查通过,不代表所有需求都正确;红灯也可能由环境故障导致,应看具体证据。需要先检查“配置了什么”,才能解释绿灯的意义。

读原文,理解每一步为什么这样做

左右内容按小节对应;窄屏先中文、后英文。两种语言均完整展示,对应讲解紧接在小节之后。译文传达原文要求;老师讲解补充概念、原因、例子与适用边界。

译注

“90%”“50% 可调试、1% 不行”等为原文的经验化表达,不是经过本教材验证的定量结论。

中文译文English · 英文原文
中文译文
name: diagnose
description: "针对疑难 bug 和性能回退的纪律化诊断循环:复现、最小化、提出假设、插桩、修复、回归测试。用户要求诊断或调试、报告 bug、说明功能损坏/抛错/失败,或描述性能回退时使用。"
English · 英文原文
name: diagnose
description: Disciplined diagnosis loop for hard bugs and performance regressions. Reproduce → minimise → hypothesise → instrument → fix → regression-test. Use when user says "diagnose this" / "debug this", reports a bug, says something is broken/throwing/failing, or describes a performance regression.
中文译文

诊断程序问题

这是一套处理难以定位的 bug 的工作纪律。只有明确说明理由时,才可以跳过阶段。

探索代码库时,使用项目业务术语表理解相关模块;同时检查涉及区域的架构决策记录。

English · 英文原文

Diagnose

A discipline for hard bugs. Skip phases only when explicitly justified.

When exploring the codebase, use the project's domain glossary to get a clear mental model of the relevant modules, and check ADRs in the area you're touching.

中文译文

阶段 1:建立可反复运行的验证流程

这是本技能的核心。 先得到一个快速、明确的通过或失败信号,而且它必须会在这个具体 bug 出现时失败。二分定位、假设验证和诊断记录都依靠它。作者强调:缺少这个信号,只盯代码看不能代替验证。

在这里投入比通常更多的精力,积极尝试,灵活换方法,不轻易放弃。

English · 英文原文

Phase 1 — Build a feedback loop

This is the skill. Everything else is mechanical. If you have a fast, deterministic, agent-runnable pass/fail signal for the bug, you will find the cause — bisection, hypothesis-testing, and instrumentation all just consume that signal. If you don't have one, no amount of staring at code will save you.

Spend disproportionate effort here. Be aggressive. Be creative. Refuse to give up.

老师讲解 · 对应上方原文 · 含教学举例

反馈循环为什么比猜原因更优先

循环就是能够反复运行的输入、操作和检查。收藏偶尔丢失时,“我昨天见过一次”不能指导修复;“固定输入连续执行收藏和刷新,检查记录是否仍存在”才开始形成可检验信号。

测试、HTTP 脚本、CLI、浏览器、轨迹重放和最小测试装置,是不同构造方法。选择能触达症状且可重复的入口,而不是把所有方法都跑一遍。旧新版本差分适合比较同输入输出,二分适合缩小问题首次出现的版本范围。

**所谓 tight,**是执行快、结果较稳定、启动负担低。每次循环耗半小时且需要你手工点十步,就很难连续检验假设。可以缓存无关准备、固定随机种子、隔离测试数据,提高信号质量。

用一次重复收藏故障,理解什么叫能抓住问题的检查

假设用户报告:快速点两次收藏,会出现两条记录。 正确验证必须触发两次请求,再检查同一用户和课程只有一条记录。只打开页面确认没报错,抓不住这个问题。

把触发与检查写成一条命令,是为了每次修改后都在相同条件下比较。先看到它失败,证明检查确实能识别这个 bug,而不是一个永远通过的装饰。

然后再列候选原因:前端重复发送、后端未防重复、并发请求同时通过检查。每种都带预测,例如“顺序发送不重复,同时发送容易重复”。接下来观察能区分它们的证据,而不是一次乱改多处。

修复后既运行小测试,也回到用户原来的完整操作。小场景通过,不自动证明原始问题完全消失。最后清理临时日志并记录原因,后续才知道为什么这样改。

原文的“解决九成”以及复现概率数字是强调方法的经验表达,不是保证成功率。历史 diagnose 还在后文使用了最小复现,却没有像新版一样给出完整缩小阶段;阅读时需要看到这个版本差别。

中文译文
构建方法:大致按以下顺序尝试
  1. 从能触发问题的入口写失败测试,可以是单元、集成或端到端测试。
  2. 用 Curl 或 HTTP 脚本请求正在运行的开发服务器。
  3. 用固定测试输入调用命令行程序,将标准输出与已知正确的快照比较。
  4. 用 Playwright 或 Puppeteer 编写无界面浏览器脚本,操作页面,并检查页面结构、控制台或网络行为。
  5. 回放捕获记录:将实际请求、数据或事件日志保存成文件,再沿相关代码路径单独重放。
  6. 搭建一次性诊断运行环境:只启动必要的一小部分系统,例如一个服务与模拟依赖,用一次函数调用触发问题路径。
  7. 使用属性或随机输入循环:对于有时输出错误的问题,运行例如 1000 组随机输入,寻找失败模式。
  8. 建立二分定位流程:已知问题在两个提交、数据集或版本间出现时,自动启动某个状态、检查、再换状态,使 git bisect run 能帮助定位。
  9. 建立差异比较:同一输入分别经过旧版和新版,或两套配置,对照输出。
  10. 最后才采用需要人参与的 Bash 流程。若确实必须人工点击,使用 scripts/hitl-loop.template.sh 组织步骤,将捕获的输出反馈给 Agent。

作者用“找到正确验证流程,问题就解决了九成”强调这一步的价值。

English · 英文原文
Ways to construct one — try them in roughly this order
  1. Failing test at whatever seam reaches the bug — unit, integration, e2e.
  2. Curl / HTTP script against a running dev server.
  3. CLI invocation with a fixture input, diffing stdout against a known-good snapshot.
  4. Headless browser script (Playwright / Puppeteer) — drives the UI, asserts on DOM/console/network.
  5. Replay a captured trace. Save a real network request / payload / event log to disk; replay it through the code path in isolation.
  6. Throwaway harness. Spin up a minimal subset of the system (one service, mocked deps) that exercises the bug code path with a single function call.
  7. Property / fuzz loop. If the bug is "sometimes wrong output", run 1000 random inputs and look for the failure mode.
  8. Bisection harness. If the bug appeared between two known states (commit, dataset, version), automate "boot at state X, check, repeat" so you can git bisect run it.
  9. Differential loop. Run the same input through old-version vs new-version (or two configs) and diff outputs.
  10. HITL bash script. Last resort. If a human must click, drive them with scripts/hitl-loop.template.sh so the loop is still structured. Captured output feeds back to you.

Build the right feedback loop, and the bug is 90% fixed.

中文译文
继续打磨验证流程

把验证流程本身当成产品。得到可运行版本后,继续问:

  • 能否更快?例如缓存准备结果、跳过无关初始化、缩小测试范围。
  • 判断能否更准确?检查用户的具体症状,而不只是“没有崩溃”。
  • 结果能否更稳定?例如固定时间、随机种子、文件系统状态或网络条件。

等待 30 秒却时好时坏的流程,帮助有限;两秒得到稳定结论的流程,可以显著提高排查效率。

English · 英文原文
Iterate on the loop itself

Treat the loop as a product. Once you have a loop, ask:

  • Can I make it faster? (Cache setup, skip unrelated init, narrow the test scope.)
  • Can I make the signal sharper? (Assert on the specific symptom, not "didn't crash".)
  • Can I make it more deterministic? (Pin time, seed RNG, isolate filesystem, freeze network.)

A 30-second flaky loop is barely better than no loop. A 2-second deterministic loop is a debugging superpower.

中文译文
偶发问题怎样处理

先提高复现概率,不要求一开始就每次百分之百出现。可以连续触发 100 次、并行执行、增加压力、缩小关键时序范围或加入等待。

作者以 50% 与 1% 的复现率比较,强调应持续提高出现频率,直到足够便于诊断。

English · 英文原文
Non-deterministic bugs

The goal is not a clean repro but a higher reproduction rate. Loop the trigger 100×, parallelise, add stress, narrow timing windows, inject sleeps. A 50%-flake bug is debuggable; 1% is not — keep raising the rate until it's debuggable.

中文译文
确实无法建立流程时

停下来,明确说明,并列出尝试过的方法。请用户提供可以复现的环境访问方式;或捕获材料,如 HAR 网络记录、日志、内存转储、带时间戳录屏;或允许临时增加生产环境诊断记录。

没有验证流程,不继续猜原因。

只有得到你认为可靠的验证流程,才能继续。

English · 英文原文
When you genuinely cannot build a loop

Stop and say so explicitly. List what you tried. Ask the user for: (a) access to whatever environment reproduces it, (b) a captured artifact (HAR file, log dump, core dump, screen recording with timestamps), or (c) permission to add temporary production instrumentation. Do not proceed to hypothesise without a loop.

Do not proceed to Phase 2 until you have a loop you believe in.

老师讲解 · 对应上方原文 · 含教学举例

红灯必须是用户报告的那个问题

一条命令运行失败,不一定算建立了复现。如果程序因缺少依赖无法启动,和“刷新后收藏丢失”是两个问题。完成标准要求循环已经针对目标症状报错,并真实运行观察过。

不稳定问题可以提高出现概率:重复触发、施加并发压力、控制时间窗口。原文的概率数字是经验化表达,不是统计学定理。应报告尝试次数与实际失败次数,而不只写“稳定复现”。

确实无法复现时,要交代已试方法和缺少什么材料,例如真实请求样本或目标环境访问。没有循环就继续猜原因,会让后面的修改失去验证依据。

中文译文

阶段 2:重现问题

运行流程,看到问题出现,检查失败。确认:

  • [ ] 失败现象就是用户报告的那个,而不是附近另一个问题。
  • [ ] 多次可复现;偶发问题也有足够高的出现频率。
  • [ ] 已记录准确错误消息、输出或耗时,供后面比较。

成功复现之前,不继续下一阶段。

English · 英文原文

Phase 2 — Reproduce

Run the loop. Watch the bug appear.

Confirm:

  • [ ] The loop produces the failure mode the user described — not a different failure that happens to be nearby. Wrong bug = wrong fix.
  • [ ] The failure is reproducible across multiple runs (or, for non-deterministic bugs, reproducible at a high enough rate to debug against).
  • [ ] You have captured the exact symptom (error message, wrong output, slow timing) so later phases can verify the fix actually addresses it.

Do not proceed until you reproduce the bug.

老师讲解 · 对应上方原文 · 含教学举例

旧版本的复现阶段,以及不能默默补进去的规则

旧文明确要求运行循环、看到用户描述的失败、多次可复现并记录症状。它没有当前版本独立展开的最小化步骤,虽然后面提到最小复现。教学可以补充最小化原理,但不能声称旧文已详细规定。

旧版本说拥有“你相信有效的循环”才继续,较依赖 Agent 的主观判断。对照新版本更明确的完成标准,可以学习文档如何从抽象要求演进为可核查条件。

旧版最后明确要求修复后反思架构,并在缺少合适测试入口等情况下交给架构改进技能。这一转交不表示修 bug 前必须先完成大重构。

中文译文

阶段 3:提出可以推翻的假设

验证前先列出 3—5 个候选原因,并按可能性排序。只想到一个解释,容易被第一个看似合理的想法固定住。

每个假设必须给出可检验预测,例如:“如果 X 是原因,改变 Y 应让问题消失;改变 Z 应让问题加重。”说不出预测的只是直觉,应删除或进一步明确。

验证前将排序展示给用户。他可能知道刚上线了什么改动,或哪些原因已经排除。这个交流能节省时间;用户暂时不在时,不必一直等待,按当前排序继续。

English · 英文原文

Phase 3 — Hypothesise

Generate 3–5 ranked hypotheses before testing any of them. Single-hypothesis generation anchors on the first plausible idea.

Each hypothesis must be falsifiable: state the prediction it makes.

Format: "If <X> is the cause, then <changing Y> will make the bug disappear / <changing Z> will make it worse."

If you cannot state the prediction, the hypothesis is a vibe — discard or sharpen it.

Show the ranked list to the user before testing. They often have domain knowledge that re-ranks instantly ("we just deployed a change to #3"), or know hypotheses they've already ruled out. Cheap checkpoint, big time saver. Don't block on it — proceed with your ranking if the user is AFK.

老师讲解 · 对应上方原文 · 含教学举例

多个可证伪假设,怎样避免第一印象绑架

假设应给出不同的可观察预测。例如收藏丢失可能来自未保存、查询缓存未刷新、当前用户身份变化。若记录确实已在存储但查询仍旧,单纯“保存失败”的解释就被削弱。

先列 3–5 项并排序,可以暴露竞争解释。你有业务背景时,可以补充“昨天刚换过缓存策略”,帮助重排,而不是替 AI 在没有资料时盲猜。

好假设形如:“如果缓存未更新是原因,绕过缓存查询应能看到记录。”坏假设只是“可能是缓存”。试验要能让不同解释给出不同结果,才值得执行。

中文译文

阶段 4:加入针对性的观察

每个观察点都要对应前面的具体预测,一次只改变一个变量。

优先使用环境支持的调试器或 REPL;一个合适断点可能胜过十条日志。其次在能区分不同假设的位置记录日志。不要把所有东西都记下来,再全文搜索碰运气。

所有临时日志加独特前缀,如 [DEBUG-a4f2],便于最后一次搜索找齐并清理。

如果是性能退化,通常不先堆日志。建立基准计时,使用计时脚本、performance.now()、性能分析器或查询计划,再二分定位。先测量,再修复。

English · 英文原文

Phase 4 — Instrument

Each probe must map to a specific prediction from Phase 3. Change one variable at a time.

Tool preference:

  1. Debugger / REPL inspection if the env supports it. One breakpoint beats ten logs.
  2. Targeted logs at the boundaries that distinguish hypotheses.
  3. Never "log everything and grep".

Tag every debug log with a unique prefix, e.g. [DEBUG-a4f2]. Cleanup at the end becomes a single grep. Untagged logs survive; tagged logs die.

Perf branch. For performance regressions, logs are usually wrong. Instead: establish a baseline measurement (timing harness, performance.now(), profiler, query plan), then bisect. Measure first, fix second.

老师讲解 · 对应上方原文 · 含教学举例

插桩:在能区分假设的位置观察

插桩是在程序中临时增加观测,例如断点、针对性日志或计时。一次只改变一个变量,是为了能把结果变化归因到刚做的操作。

例如在写入完成和读取返回两个边界记录同一个请求标识,可以判断丢失发生在哪段。把所有变量都打印出来,可能淹没关键线索,还增加信息暴露和清理成本。

性能问题要先建立耗时基线、分析调用或查询,再改实现。日志说“运行完成”不能证明速度是否退化。临时日志用唯一标记,是为了最后能够完整找到并删除。

中文译文

阶段 5:回归测试与修复

先写回归测试,再改代码,但前提是有适合验证真实问题模式的测试接入点。

如果问题需要多个调用方配合才能出现,只测一个调用方就太浅;如果触发依赖一连串调用,单元测试无法重现这条链,也可能给出虚假的安心感。

没有合适入口,本身就是架构方面的发现。记录这个限制,说明为什么现在不能用恰当测试防止复发,并带到下一阶段。

有合适入口时:

  1. 将最小复现变成失败测试。
  2. 实际运行,看到失败。
  3. 应用修复。
  4. 重新运行,看到通过。
  5. 回到最初未缩小的完整场景,再运行阶段 1 的验证流程。
English · 英文原文

Phase 5 — Fix + regression test

Write the regression test before the fix — but only if there is a correct seam for it.

A correct seam is one where the test exercises the real bug pattern as it occurs at the call site. If the only available seam is too shallow (single-caller test when the bug needs multiple callers, unit test that can't replicate the chain that triggered the bug), a regression test there gives false confidence.

If no correct seam exists, that itself is the finding. Note it. The codebase architecture is preventing the bug from being locked down. Flag this for the next phase.

If a correct seam exists:

  1. Turn the minimised repro into a failing test at that seam.
  2. Watch it fail.
  3. Apply the fix.
  4. Watch it pass.
  5. Re-run the Phase 1 feedback loop against the original (un-minimised) scenario.
老师讲解 · 对应上方原文 · 含教学举例

回归测试要复现真实故障模式

如果问题必须由两个并发调用触发,只写一个串行单调用测试,即使通过也没有锁住问题。正确的测试接入点要能包含造成失败的真实交互。

有合适入口时,把最小复现先变成失败测试,再修复、看它通过,最后重跑原始场景。没有合适入口时,记录架构妨碍验证这一事实;不要造一个无关测试来满足“有测试”字段。

清理包括临时日志、试验代码和相关说明。完成报告应说清成立的原因、修复方式、已验证场景和尚未覆盖范围,让下一个人有可靠起点。

中文译文

阶段 6:清理与复盘

宣布完成前确认:

  • [ ] 原始复现已不再失败,阶段 1 流程已重跑。
  • [ ] 回归测试通过;没有适合入口时,限制已记录。
  • [ ] 带 [DEBUG-...] 前缀的临时诊断代码已全部清理。
  • [ ] 一次性原型已删除,或移到明确标注用途的调试位置。
  • [ ] 提交或 PR 说明写明最终得到验证的原因,供下一位排查者学习。

然后问:怎样才能让这个问题原本就不发生?若涉及缺少测试入口、调用方缠绕或隐藏依赖等架构问题,把具体发现交给 /improve-codebase-architecture。在修复完成后再建议,因为此时认识比刚开始调查更充分。

English · 英文原文

Phase 6 — Cleanup + post-mortem

Required before declaring done:

  • [ ] Original repro no longer reproduces (re-run the Phase 1 loop)
  • [ ] Regression test passes (or absence of seam is documented)
  • [ ] All [DEBUG-...] instrumentation removed (grep the prefix)
  • [ ] Throwaway prototypes deleted (or moved to a clearly-marked debug location)
  • [ ] The hypothesis that turned out correct is stated in the commit / PR message — so the next debugger learns

Then ask: what would have prevented this bug? If the answer involves architectural change (no good test seam, tangled callers, hidden coupling) hand off to the /improve-codebase-architecture skill with the specifics. Make the recommendation after the fix is in, not before — you have more information now than when you started.

原作者:Matt Pocock · 中文翻译为非官方译本

来源:skills/engineering/diagnose/SKILL.md ↗

固定版本:7afa86d3a5dd96edde06ffa014e16c64e733681e

先作答,再看参考思路

Q1 · 理解

用自己的话说明:它解决什么问题,完成后会留下什么?

请各用一句话回答。若它只做规划或解释,不要把“已开发”“已部署”写成产物。

Q2 · 判断

为什么“已经运行并捕捉到症状”比“相信这个检查有用”更适合 Agent?

我已思考,查看参考思路

它提供可观察的门槛,减少模型凭主观判断提前进入分析。仍需检查捕捉的确实是用户问题,而非别的失败。

Q3 · 追问

原文中哪条要求在你的环境下可能不成立?

说出具体一句及其前提,例如工具不可用、资料缺失、已有项目约定冲突,或它只是作者偏好。把你的答案带回课堂,我们据此继续讨论。

课堂回传格式:第 41 课 / 我的理解 / Q2 回答 / 仍不理解的原句。这里是阅读教材;实时问答在我们的对话中进行。

把方法放进一个具体情境

教学案例:“我相信这是缓存问题”不足以推进。当前更好的完成条件是固定输入,运行检查,观察到缓存状态变化与症状相对应。

边界与容易误读的地方

这里的经验性百分比不是统计证据。历史规则不应覆盖当前版本更具体的脱敏与复现要求。

讨论后再实践:先判断上述情境是否适用,再选择真实任务。现在无需安装、运行命令或修改现有项目。

关联阅读