故事式问题复盘 是做什么的?
用户要求理解一个他们未曾亲历的问题。他们不熟悉组件、术语或事件发生的顺序。一份发现清单或差异摘要对他们帮助不大。但一个故事可以:它先逐一介绍每个角色,然后按照时间顺序展示每个角色所做的事,直到故障自然发生。
请先进行调查。故事的优劣完全取决于其背后的事实,因此在动笔之前,请阅读代码、日志、工单、文档和历史记录。如果调查已在对话的早期阶段进行过,则直接复用其结果,避免重复工作。
回答的结构
以一句话开场,设定框架,例如:“以下是整个故事从头开始的叙述,假设你不了解代码。”然后使用以下部分。如果某个部分没有实质内容可写,请跳过,不要凑字数。
角色介绍
介绍故事所需的每一个角色,且只介绍这些:组件、类、消息、定时器、数据;对于流程,还包括相关人员、团队、表单和设置。为每个角色赋予一个粗体名称,并用一两句简单的话说明它是什么,以及在这个故事中它做什么。
- 精确命名每个角色,并将其链接到代码。 请写出
price_cache_key()并附上文件:行号的链接,而不是使用“键生成器”这种模糊的标签。模糊的标签会迫使读者停下来猜测其含义。如果一个角色涉及多段代码(例如一个方法及其周围的框架钩子),请在此说明。对于代码之外的问题,请链接到其来源:文档和步骤、表单字段或设置路径。 - 为抽象机制选择一个物理隐喻,并在整个故事中保持一致。 例如,消息头可以变成“贴在信封上的便签”,缓存可以变成“一块白板”,锁可以变成“一把钥匙”。在真实名称旁边介绍这个隐喻(“价格缓存,相当于每台Web服务器旁的一块白板”),以便读者能将其映射回代码。不要在过程中切换隐喻,也不要同时使用两个隐喻。
- 给出每个角色遵循的规则,用斜体表述为该角色自己的想法:“今天就是我时钟显示的日期。” Bug和流程故障通常源于两个合理的规则发生冲突,而这种方式能让冲突在后续章节中变得清晰可见。
章节
按时间顺序讲述事件,每个步骤一个章节:## 第一章:购物者在午夜前查看价格,## 第二章:新价格到达,依此类推。标题要简短,说明发生了什么事。
- 保持每个章节简短:几句话或几个要点。
- 加粗每个章节的关键细节,即读者绝对不能错过的一个事实(“导入操作擦除了白板的错误角落”)。
- 当两件事同时发生时,为它单独设立一个章节,并逐一展示两边的情况。
- 故障章节需引用确切的错误消息(使用块引用),并说明是哪个代码捕获了它以及记录了哪些日志。
症状为何如此表现
故障之后,解释观察到的现象模式:为什么只影响前几个小时,为什么只影响部分服务器,为什么只在重启后发生。将其与工单或日志实际显示的内容联系起来。这是检验故事是否正确的检查点,请务必保留。
当问题是一个风险,而非一次故障
有时故事是关于可能出错的事情:一条审查评论、一个潜在的bug、一个“如果...会怎样”。既然没有发生故障,就没有日志行或工单症状来作为故事的锚点。请以不同的方式讲述,因为看到引用错误的读者会假定它已经发生了。
- 尽早说明这尚未发生。 在开头或第一章中就指出,这是一个通过阅读代码发现的潜在故障,而非在日志或测试中观察到的。
- 明确列出条件。 故障章节精确列出故障发生必须满足的条件,作为编号条件(例如:“1. 操作在作用域关闭后调用了另一个服务,且 2. 该调用周围没有新的作用域”)。然后指出具体会出什么问题:断言、异常类型、代码行。不要引用没人见过的错误消息。如果你命名了一个异常,请说明它来自代码阅读。
- 将“症状为何如此表现”替换为“为何今天没有触发”。 逐一检视每个真实的调用者或流程,展示它未能满足哪个条件,并附上链接。这部分承载了故事的分量,因为读者需要判断风险是否真实存在。
- 寻找你尚未检查的调用者。 在编写风险的“严重程度”列表时,是发现未检查路径的地方:在非正常位置发出的调用、不同的传输方式、后台任务。将每个都列为“未检查”,而非忽略它们,并说明存在的证据(例如,一个干净的测试运行虽然没有追踪,但已执行了该路径)。
- 在修复方案部分比较成本。 修复一个风险可能引入一个真正的故障。如果是这样,请直白地说明:该修复用一个没人会触发到的风险,换取了一个肯定发生的故障。
为什么现在开始出现
如果该行为属于回归,请说明发生了什么变化、何时发生的,以及为什么那个变化本身是合理的。先用文字提及PR或提交,然后括号内加上ID。读者需要知道,之前的变化并非疏忽大意,而仅仅是不完整。
严重程度如何
列出实际影响:数据、数字、告警、日志噪音。区分已确认的和推断出的情况,因为读者会向他人复述这些说法。
- 将每个说法标记为已检查(以及如何检查的:测试、日志查询、代码路径通读)或未检查。
- 除非有证据证实,否则不要写“数据没问题”。一个看似合理但最终错误的说法的代价,大于一个诚实的“尚未检查”。
如何修复
从小到大(例如一个快速修复和一个正确修复)给出选项,说明每个选项在故事层面上改变了什么(“让两个角色使用同一个日历”),以及它的局限性。最后给出简短的建议。
风格
- 使用平实的语言。每个术语第一次出现时就定义它,否则就省略它。
- 将代码标识符放在反引号中,紧贴其隐喻,使故事始终可追溯到代码。
- 优先使用简短段落和要点,避免长篇大论。
- 遵循用户
CLAUDE.md或AGENTS.md文件中关于写作风格的规则。
故事之后
准备好回答关于某个角色的后续问题(例如:“所以X就是处理器?”)。通过将角色定位到精确代码来回答:按顺序列出运行的代码,附上 文件:行号 链接,并说明哪一部分执行了关键的操作。如果问题表明原始故事中的命名不够精确,在回答中修正命名,而不是为其辩护。
示例
references/example-stale-prices.md 中包含了一个为缓存过期bug编写的完整故事,以及关于它哪里出了问题的注释。在同一个会话中编写你的第一个故事之前,请先阅读它,以校准篇幅和语气。