使用 Claude Code 的可复用循环进行 Bug 分类:重现、最小化、识别可能原因、添加回归测试,并交付带有校验的窄幅修复。

当每个报告都变成一次独立的谜题时,错误会显得很随机。你随手去改改代码、试几种办法,然后希望问题消失。有时候确实会消失,但你并没有学到多少,类似的问题可能会以另一种形式再出现。
Bug 分类正好相反。它是减少不确定性的快速方法。目标不是立刻修好所有东西,而是把模糊的抱怨变成一个清晰、可测试的陈述,然后做出最小的改动去证明这个陈述现在是假的。
这就是为什么循环很重要:重现、最小化、用证据识别可能原因、添加回归测试、实现窄幅修复并验证。每一步都能消除一种特定的猜测。跳过步骤通常会让你以后付出代价:更大的修复、意外副作用,或者所谓“修好了”但实际上根本没修的 bug。
举个现实例子。用户说:“保存按钮有时什么也不做。”没有循环的做法可能是在 UI 代码里翻来覆去改定时、状态或网络调用。用循环的话,你首先把“有时”变成“在这些精确条件下每次发生”,比如:“编辑标题后快速切换标签页,Save 保持禁用。” 这一句已经是进展了。
Claude Code 能加快思考过程:把报告变成精确的假设,提示应该查看的地方,并提出一个应该失败的最小测试。它在扫描代码、日志和最近的 diff 以快速生成合理解释时尤其有用。
但关键的核验仍然得你来做。确认 bug 在你的环境中是真实的。优先证据(日志、追踪、失败的测试)而不是听起来合理的故事。保持修复尽量小,用回归测试证明它,并用清晰的检查来验证,以免把一个 bug 换成另一个。
回报是一个小而安全的修复,你能解释、捍卫,并防止其回归。
好的修复始于干净的工作区和单一、清晰的问题陈述。在你向 Claude 提问之前,先挑一个报告并把它改写为:
“当我做 X 时,我期望 Y,但得到 Z。”
如果你写不出这句话,那你还没有一个 bug,你只有一个谜团。
事先收集好基础信息,避免反复回来确认。这些细节让建议可测试而不是模糊不清:应用版本或提交(以及是否在本地、预发布或线上)、环境详情(操作系统、浏览器/设备、功能开关、地区)、精确输入(表单字段、API payload、用户操作)、谁会看到它(所有人、某个角色、单个账号/租户)以及“期望”是什么意思(文案、UI 状态、状态码、业务规则)。
然后在证据还新鲜时保存它。一条时间戳可能节省数小时。捕获事件前后的日志(尽可能同时收集客户端和服务端)、截图或短录屏、请求 ID 或追踪 ID、精确时间戳(含时区)以及触发问题的最小数据片段。
例子:一个由 Koder.ai 生成的 React 应用显示 “Payment succeeded” 但订单仍然是 “Pending”。记录用户角色、确切订单 ID、API 响应体和该请求 ID 的服务器日志行。现在你可以让 Claude 专注于单一路径,而不是只说大概。
最后,设定一个停止规则。在开始编码前决定什么算作已修复:一个特定测试通过、UI 状态改变、错误不再出现在日志中,以及一份你每次都会运行的简短验证清单。这能防止你“修复”了症状却发布了新 bug。
混乱的错误报告通常把事实、猜测和情绪揉在一起。在求助前,把它转换成 Claude 能用证据回答的明确问题。
首先写一句一句概括,点明功能和失败。好的例子: “保存草稿有时会删除标题(移动端)。” 不是好的例子: “草稿坏了。” 这句概括将成为整个分类线程的锚点。
然后把你看到的和你期望的分开。保持冷静具体:你点击的确切按钮、屏幕上的消息、日志行、时间戳、设备、浏览器、分支、提交。如果没有这些,就说明缺失。
一个可以直接粘贴的简单结构:
如果缺少细节,用是/否问题去问,这样别人可以快速回答:是否发生在新账户?只在移动端?只在刷新后?是否在上次发布后开始?能在隐身模式复现吗?
Claude 也可以作为“报告清理器”。粘贴原始报告(包括截图复制的文字、日志和聊天片段),然后问:
“把它改写成一个结构化的检查表。标出矛盾点。列出前 5 个缺失事实(以是/否问题)。不要猜测原因。”
如果队友说“随机失败”,把它推动到可测试的方向: “在 iPhone 14、iOS 17.2 上保存两次快速点击时 2/10 次失败。” 现在你可以有目的地去复现它。
如果你不能按需触发 bug,后面的每一步都是猜测。
从能展示问题的最小环境开始:本地 dev 构建、最小分支、很小的数据集,并尽可能关闭无关服务。
把确切步骤写下来,让别人能无须提问地复现。做成可复制粘贴的形式:命令、ID、示例 payload 都应精确无误。
一个简单的捕获模板:
频率会改变策略。“总是”发生的 bug 适合快速迭代。“有时”发生的 bug 往往指向时序、缓存、竞态或隐藏状态。
有了复现笔记后,请 Claude 给出减少不确定性的快速探针,而不是改写应用。好的探针很小:在出问题的边界处打印一行日志(输入、输出、关键状态)、给单个组件加一个调试标志、强制确定性(固定随机种子、固定时间、单 worker)、生成一个能触发问题的极小种子数据,或重放一个失败的请求/响应对。
例子:注册流程“有时”失败。Claude 可能建议记录生成的用户 ID、邮件规范化结果和唯一约束错误详情,然后用相同 payload 重跑 10 次。如果失败只发生在部署后第一次运行,那就是检查迁移、缓存预热或缺失种子数据的强烈线索。
一个好的复现很有用,但最小复现更强大。它让理解 bug 更快、调试更容易,也不容易被“巧合”修好。
去掉所有非必需项。如果 bug 出现在一段冗长的 UI 流中,找到触发它的最短路径。移除可选页面、功能开关和无关集成,直到 bug 消失(你删掉了必要项)或保持(你找到了噪声)。
然后缩减数据。如果 bug 需要大 payload,尝试使其成为最小能触发的 payload。如果它在 500 条列表时发生,看看 5 条、2 条、1 条是否仍然失败。逐个字段移除。目标是尽可能少的动作部件还能复现 bug。
一个实用方法是“减半再测”:
例子:结账页面在应用优惠券时“有时”崩溃。你发现它只在购物车里至少有一个已折扣商品、优惠码是小写字母且配送方式为“自取”时失败。那就是你的最小用例:一个已折扣商品、一个小写优惠码、一个自取选项。
一旦最小用例明确,要求 Claude 把它变成一个小的复现脚手架:调用失败函数的最小测试、对一个端点发出简化 payload 的短脚本,或访问某一路由并执行一次操作的小型 UI 测试。
当你能复现问题并有小测试用例后,停止无端猜测。目标是列出一小组合理原因,然后逐个证明或否定它们。
有用的规则是把假设控制在三条以内。超过三条通常说明你的测试用例还太大或观察还不够具体。
把你看到的现象翻译成可能出问题的部件。UI 的表现不一定就是 UI 出错。
例子:React 页面显示 “Saved” 提示,但记录后来丢失。这可能指向(1)UI 状态、(2)API 行为、或(3)数据库写入路径。
让 Claude 用通俗语言解释可能的失败模式,然后问每个模式什么证据能证实。目标是把“也许”变成“检查这个确切东西”。
三类常见假设及要收集的证据:
保持笔记简洁:症状、假设、证据、结论。当一个假设与事实匹配时,你就可以锁定回归测试并只修复必要的地方。
好的回归测试是你的安全带。它证明 bug 存在,并在修复后告诉你是否真的解决了问题。
先选择与真实失败匹配的最小测试。如果 bug 只在多个部分协同工作时才出现,单元测试可能看不到它。
当单一函数返回错误值时用单元测试;当边界问题(处理器 + DB、客户端 + API)出现时用集成测试;只有在需要完整用户流程时才用端到端。
在让 Claude 写任何东西之前,把最小化用例重述为严格的期望行为。例如:“当用户保存空标题时,API 必须返回 400 且消息为 ‘title required’。” 这样测试目标就很明确。
然后让 Claude 起草一个首先会失败的测试。保持初始化最小,只复制触发 bug 的数据。把测试命名为面向用户的描述,而不是内部函数名。
做一个快速自检:
测试在为正确原因失败后,你就可以有把握去实现一个窄幅修复。
一旦有小复现和失败的回归测试,抗拒“清理代码”的冲动。目标是用最小改动让测试以正确的原因通过。
好的窄幅修复只改动最小表面面积。如果错误在某个函数里,就修那个函数,而不是整个模块。如果缺少边界检查,就在边界处加检查,而不是在整个调用链上修改。
如果用 Claude 帮忙,请求两个修复方案并比较它们的范围与风险。例子:React 表单在字段为空时崩溃,你可能会得到:
通常方案 A 是分类阶段的选择:更小、易审查、也更不容易引入新问题。
为保持修复窄小,尽量少改文件,偏向局部修复而非重构,在坏值进入处添加守卫/校验,并把行为改动显式化为一个清晰的前后对比。只有在原因不明显时才加注释。
具体例子:一个 Go API 接口在可选查询参数缺失时 panic。窄幅修复是在处理器边界处理空字符串(用默认值解析,或返回 400 并给出清晰信息)。除非回归测试证明共享解析逻辑有问题,否则不要去改共享解析工具。
改动后,先跑失败的测试和一两个附近的测试。如果修复需要更新许多不相关测试,那说明改动过于广泛。
验证能帮你抓住那些容易漏掉的问题:修复通过了单个测试但破坏了附近路径、改变了错误信息,或引入了慢查询。
首先,重跑你添加的回归测试。如果它通过了,再运行最近邻的测试:同文件、同模块以及覆盖相同输入的测试。错误常常藏在共享 helper、解析、边界检查或缓存里,所以最相关的失败通常会在附近出现。
然后用原始报告的步骤做一个快速人工检查。保持简短具体:相同环境、相同数据、相同点击或 API 调用序列。如果报告模糊,就测试你用来复现它的那个精确场景。
如果你想保持专注,可以让 Claude 基于你的改动和失败场景给出一份简短验证计划。说明你改了哪个文件、期望的行为是什么,以及哪些方面可能受到影响。最好的计划短且可执行:5 到 8 项检查,每项都有明确的通过/失败标准。
最后,把你做过的验证记录在 PR 或说明里:你运行了哪些测试、尝试了哪些人工步骤、以及任何未测试的限制(例如“未测试移动端”)。这会让修复更容易被信任和以后回溯。
浪费时间的最快途径是接受一个“修复”但你不能按需重现问题。如果你不能可靠地让它失败,就无法判断到底改进了什么。
一个实用规则:在你能够描述一个可重复的环境(确切步骤、输入、环境及“错误”的表现)之前不要求修复。如果报告模糊,用你最初的几分钟把它变成一份你能执行两次并得到相同结果的检查表。
在没有可复现案例时就修复。 需要一个最小且“每次都会失败”的脚本或步骤。如果只是“有时失败”,抓住时序、数据大小、功能开关和日志,直到它不再随机。
过早最小化。 在确认了原始复现之前就压缩用例,可能会丢掉关键信号。先锁定基线复现,然后一步步缩减。
让 Claude 去猜原因。 Claude 可以提出可能原因,但你仍需证据。要求 2–3 个假设,并为每个给出确切的观察项以证实或否定(日志行、断点、一条查询结果)。
回归测试因错误原因通过。 一个测试可能“通过”只是因为它根本没触及失败路径。确保在修复前测试会失败,并且失败理由是你预期的那种。
处理症状而非触发器。 如果你加了空值检查但真正的问题是“该值本不该为空”,你可能掩盖了更深层的错误。优先修复产生坏状态的条件。
在改动前后运行新的回归测试和原始复现步骤。如果结账 bug 只有在在更改配送方式后应用优惠码时才发生,保留完整序列作为你的“真相”,即便你最小化的测试更小。
如果你的验证依赖于“看起来现在没问题”,至少加上一条具体检查(日志、指标或特定输出),这样下一个人可以迅速验证。
在时间紧张时,一个小而可重复的循环胜过英勇式调试。
把最终决定写成几行,方便下一个人(通常是未来的你)信任它。一个有用的格式是:“根因:X。触发器:Y。修复:Z。为什么安全:W。我们没改的:Q。”
后续步骤:把能自动化的留给自动化(保存的复现脚本、标准测试命令、根因笔记模板)。
如果你用 Koder.ai(koder.ai)构建应用,Planning Mode 可以在你动手改代码前帮助你勾勒改动,快照/回滚让你在处理棘手复现时更容易安全试验。一旦修复验证通过,你可以导出源码或部署并托管更新后的应用,包括在需要时使用自定义域名。
Bug 分类是一种把模糊报告转成清晰、可测试陈述的习惯,然后做出最小的改动以证明该陈述不再成立。
它的重点不是“立刻修复所有问题”,而是一步步减少不确定性:重现、最小化、基于证据形成假设、添加回归测试、窄幅修复、验证。
因为每一步都能消除不同类型的猜测。
把它重写为:“当我做 X 时,我期望 Y,但得到的是 Z。”
然后只收集让它可测试的必要上下文:
先确认能在最小仍能展示问题的环境中重现(通常是本地 dev 加一个小数据集)。
如果是“有时发生”,尝试通过控制变量让它确定化:
在能按需触发之前不要继续,否则后续步骤都是猜测。
最小化就是去掉所有非必要项,同时保持 bug 仍然发生。
一个实用方法是“减半再测”:
同时缩减步骤(更短的用户流)和数据(更小的 payload、更少字段/条目),直到得到最小重复触发条件。
把精力放在列出 2–3 个合理假设,并为每个假设定义能证实或否定它的证据。
常见思路:
把笔记保持精简:症状、假设、证据、结论。一旦有假设与事实匹配,就可以锁定回归测试并只修复必要部分。
回归测试是你的安全带:它证明了 bug 的存在,并在未来告诉你是否真正修好了。
先选最小且能覆盖真实失败场景的测试层级:
先写一个会在当前代码上失败的测试,确保断言具体(状态码、错误信息、渲染文本),并且只覆盖一个问题。测试在修复前必须因为预期的原因失败。
用让回归测试通过所需的最小改动。
经验法则:
通常会给出两种修复方案:一个小而风险低的临时护栏(优先选择),一个更彻底但范围大的重构(需要更多审查和测试)。
验证是你捕捉细微问题的环节:修复通过了单个测试,但可能破坏附近路径、改变错误信息或引入慢查询。
一个简短的验证清单:
把你运行过的验证记录在 PR 或变更说明里,标明你没有覆盖的部分,这会增加修复的可信任度。