在这里插入图片描述

dev-skills(仓库:Jason-chen-coder/dev-skills) 是我在 AI 编程实践里沉淀的一套工作流规则集。

它想解决的不是“AI 会不会写代码”,而是“AI 写代码、修 bug、做验证、准备提交时,能不能按更稳的工程步骤往前走”:需求先对齐,方案先讲清,代码先被测试约束,修 bug 先找根因,完成前必须拿出验证证据,commit 前再 review 一遍 diff。

这篇文章展开的,是 dev-skills 里专门处理 bug 修复的工作流:dev-fix

如果说普通的 AI 修 bug 是“你给现象,它给补丁”,那 dev-fix 更像一份失败契约:先把问题稳定复现,再列出可证伪的假设,沿着调用链和数据流反向追到 root cause,只改确认过的根因路径,最后用 red → green → red 的回归验证证明测试真的能抓住这个 bug。

它不是为了让 AI 修 bug 变慢,而是为了阻止 AI 过早说“修好了”。

在这里插入图片描述

AI 修 bug 最大的问题,不是它不会改代码。

恰恰相反,它太会改了。

你贴一段报错,它马上补一个空判断。

你说页面偶尔白屏,它马上加一个默认值。

你说接口有时候超时,它马上包一层 try-catch

几秒钟后,它告诉你:

“已修复。”

这四个字听起来很让人安心。

但在真实工程里,它也可能是最危险的幻觉。

因为很多时候,AI 所谓的“修好了”,只是把最吵的症状按下去了。

报错不再弹。

页面不再白。

日志不再红。

测试表面变绿。

但真正的 root cause 还在那里。

下一次,它会换一条路径回来。

这也是我做 dev-fix 时最想解决的问题:别再让 AI 用一个补丁把你糊弄过去。修 bug 不是让错误消失,而是找到错误为什么会发生。

一、AI 最擅长的,是把症状修得像根因

我们先看一个最常见的场景。

页面报错:

Cannot read properties of undefined

你让 AI 修。

它很快给出一行:

if (!user) return null;

报错没了。

这当然可能是正确修复。

但也可能完全不是。

真正的问题可能是接口少返回了字段,可能是缓存 key 错了,可能是某个异步状态被提前消费了,可能是权限判断让用户数据在某条路径下变成了半截对象。

如果你只看报错消失,很容易觉得“修好了”。

但这个补丁只回答了一个问题:

“怎样让这里不炸?”

它没有回答真正重要的问题:

“为什么这里会拿到一个不该出现的坏状态?”

这就是 AI 修 bug 最容易出问题的地方。

它倾向于在失败出现的地方动刀。

报错在这里,就守这里。

空值在这里,就兜这里。

异常在这里,就 catch 这里。

可工程里的报错点,往往只是坏状态最终爆炸的地方,不是坏状态第一次被引入的地方。

一个靠谱的修复,不应该停在“最后炸掉的那一行”。

它要沿着调用链往回追,追到那个坏状态第一次出现的位置。

在那里修,才叫修根因。

在这里插入图片描述

二、dev-fix 的第一条铁律:没有复现,就没有修复

很多人让 AI 修 bug 的第一句话是:

“这个问题帮我修一下。”

dev-fix 的第一反应不是修。

而是问:

这个问题怎么稳定出现?

正常应该是什么样?

用户给的触发动作是什么?

最近什么时候开始出现?

严重程度有多高?

这些问题看起来慢,但它们是在把“一个模糊事故”变成“一个可执行的失败”。

因为没有稳定复现,你根本不知道后面的修复有没有用。

AI 加了一行代码,问题暂时没出现,是因为修对了?

还是因为你这次没触发到?

还是因为时序问题刚好没撞上?

还是因为测试写得太宽,永远都会绿?

dev-fix 要求先把 bug 编码成一个会失败的自动测试。

能用单测就不用浏览器。

能用最小输入就不用完整环境。

如果是时序问题,就不能写一个随缘的 sleep(500)

要等真实条件成立,而不是赌 500 毫秒以后世界会变好。

更硬的一点是:失败测试必须稳定失败。

如果它一会儿红、一会儿绿,这不是可以继续糊弄的信号。

这是 race、时序、跨系统状态不稳定的信号。

这时候就该升级到更深的排查,而不是兴奋地开始补丁。

没有复现的修复,本质上是在猜。

而猜对一次,不等于建立了工程可信度。

在这里插入图片描述

三、第二条铁律:先列假设,不许直接动手

AI 的默认姿势太积极。

它看到错误,就想改。

但真正的调试不是“看到哪里可疑就改哪里”,而是先建立几条能被证伪的假设。

dev-fix 会逼 AI 写出类似这样的东西:

H1:后端接口没有返回 user.email
预测观察:接口响应里 user 存在,但 email 字段缺失
证据收集:查看接口响应和序列化逻辑

H2:前端缓存 key 错误,拿到了另一个页面的旧 user
预测观察:同一请求没有发出,但缓存命中异常
证据收集:检查 query key 和缓存日志

H3:权限路径下 user 被裁剪成部分字段
预测观察:只有低权限账号复现,高权限账号正常
证据收集:用两类账号跑同一个测试

这里最关键的不是“列了几个 H”。

而是每个假设都必须能预测观察结果。

如果 H1 成立,应该看到什么?

如果 H2 成立,应该看到什么?

如果 H3 成立,应该看到什么?

不能预测观察的假设,基本就是一句“我感觉”。

而 AI 最容易把“我感觉”包装成“我判断”。

默认模式下,dev-fix 至少会让它列出一到两个假设。

遇到偶发、race、跨系统、生产事故,就进入 --deep:强制列三到五个不同维度的假设,比如代码逻辑、数据问题、并发时序、环境差异、甚至“是不是我们误解了需求”。

这件事很重要。

因为调试最怕单一路径思维。

你以为是前端按钮没禁用。

结果根因是后端写操作没有幂等。

你以为是缓存没清。

结果根因是接口协议漂移。

你以为是某个对象为空。

结果根因是上游状态机允许了一个不该存在的中间态。

好调试不是更快下注。

好调试是让每个下注都有证据成本。

在这里插入图片描述

四、第三条铁律:不要在第一个可疑帧打补丁

dev-fix 里有一个很硬的动作:反向追溯。

当某个假设看起来成立时,AI 不能马上在眼前那一行改。

它要沿着调用栈、数据流、状态流往回问:

这个坏值是在这一层第一次出现的吗?

还是从上一层传进来的?

如果是上一层传进来的,那上一层为什么会给它?

再上一层呢?

一直追到坏状态第一次被引入的位置。

举个例子。

你看到 user.emailundefined

最差的修法是在使用处写:

if (!user?.email) return;

稍好一点,是在组件入口做兜底。

但真正的 root cause 可能在 getUserById():DB 查询时根本没有 select email 字段。

那你就应该修查询,而不是在每个消费 email 的地方贴创可贴。

再看一个更典型的 race。

用户连点两次下单按钮,系统创建了两个订单,还扣了两次款。

AI 很容易先修前端:

按钮点击后 disabled。

这当然有价值。

但它不是根因。

因为只要后端写操作没有幂等,用户可以刷新、重试、网络抖动、移动端重复提交、脚本请求,照样绕过这个按钮。

真正的根因在后端:创建订单这个写操作没有 idempotency key,没有唯一约束,没有并发保护。

前端禁用按钮只是缓解症状。

后端幂等才是 root cause fix。

这就是 dev-fix 和“随手修一下”的分水岭。

AI 不许在第一个可疑帧停下。

它必须继续往回追,直到找到坏状态最早出现的位置。

在这里插入图片描述

五、第四条铁律:只改 root cause 路径,不许夹带重构

AI 还有另一个很迷人的坏习惯:

一旦开始修,它会顺手把旁边的代码也“优化”了。

变量名顺一下。

helper 抽一下。

日志格式改一下。

错误处理换一套。

测试框架顺手重排。

这些改动单独看都可能有道理。

但放在 bug fix 里,它们会污染判断。

你本来只想知道:这次 root cause 是否被修掉。

结果 diff 里混进了十几个“顺手优化”。

如果测试绿了,究竟是哪一行让它绿的?

如果线上又出问题,究竟是修复没修对,还是新重构引入了风险?

dev-fix 对这点很克制:只改 confirmed root cause 直接相关的路径。

发现旁边也有问题,可以列 Follow-up。

发现同类模式在别处也存在,可以做 Pattern analysis。

但不要在这次 fix 里一起修。

这不是保守。

这是为了让修复可解释。

bug fix 最重要的不是“代码看起来更好”,而是“因果链更清楚”。

这次失败为什么发生。

这次改动为什么能阻止它再次发生。

这次测试为什么能抓住它。

讲不清这三件事,再漂亮的 diff 都不值得放心。

在这里插入图片描述

六、最狠的一步:绿了以后,还要证明测试真的会红

很多流程到这里就结束了。

失败测试写了。

代码改了。

测试绿了。

AI 说:

“已完成。”

dev-fix 还不让它走。

它要求再做一个反向证明:

临时移除 fix,测试必须重新变红。

恢复 fix,测试必须重新变绿。

这一步非常朴素,也非常狠。

因为它直接打掉了一类最常见的假验证:

测试其实没测到 bug。

断言太宽。

路径没走到。

mock 写错了。

测试数据绕开了真实失败条件。

环境里刚好没触发。

如果移除 fix 以后测试还是绿,那这个测试就不是回归测试。

它只是一个看起来很努力的摆设。

dev-fix 逼 AI 做 red → green → red,本质上是在问一句:

你说 bug 被修了,证据在哪?

不是“我觉得”。

不是“理论上”。

不是“应该可以”。

而是:bug 以前能被这个测试抓住,现在被修掉;把修复拿掉,它又会被抓住。

这才叫回归证明。

在这里插入图片描述

七、找到同类风险,但不要一口气全修

dev-fix 还有一个很实用的要求:Pattern analysis。

当这次 root cause 被确认以后,要问一句:

这种 bug 的形状,在仓库其他地方还有没有?

比如这次问题是“写操作缺幂等”。

那就去搜其他 POST 写操作。

这次问题是“异常输入没有 schema 校验”。

那就去搜其他入口。

这次问题是“空值在边界没挡住”。

那就去搜同类边界函数。

注意,这一步不是让 AI 把所有命中都顺手改掉。

正好相反。

dev-fix 要求把它们列出来,作为 Follow-up。

为什么?

因为这次 fix 的目标,是修掉这个被复现、被定位、被验证的 bug。

Pattern analysis 的目标,是把同类隐患变成可见的后续工作。

两者不能混在一个 diff 里。

AI 很喜欢“一次性帮你都处理了”。

工程上,很多灾难就来自这句“一次性”。

在这里插入图片描述

八、dev-fix 真正改变的,是你和 AI 的关系

表面上看,dev-fix 是一个修 bug 的 skill。

但更准确地说,它是在改变 AI 修 bug 时的权限边界。

以前是:

你给现象。

AI 猜补丁。

AI 宣布修好。

你相信或不相信。

现在是:

你给现象。

AI 先复现。

AI 列假设。

AI 收集证据。

AI 反向追根因。

AI 只改根因路径。

AI 用 red → green → red 证明测试有效。

AI 留下 RCA 和后续风险。

这两种工作方式,差别不是流程多了几步。

差别是责任从“生成代码”转向了“证明因果”。

AI 写代码的速度当然重要。

但修 bug 时,速度不是第一优先级。

第一优先级是不要被错误的确定感骗过去。

“修好了”不是一句话。

“修好了”应该是一组证据:

我知道它怎么失败。

我知道为什么失败。

我知道改在哪里。

我知道为什么这次改动能挡住它。

我知道把改动撤掉,测试会重新失败。

我知道同类风险在哪里,但这次不乱改。

如果 AI 不能给出这些东西,它就不应该说“修好了”。

最多只能说:

“我写了一个补丁。”

这两个句子,中间隔着真实工程。

在这里插入图片描述

结尾

我越来越觉得,AI 编程时代最稀缺的不是“会写代码的模型”。

这样的模型会越来越多,也会越来越快。

真正稀缺的,是能约束它的工程协议。

什么时候该问。

什么时候该查。

什么时候该停。

什么时候不许改。

什么时候必须拿证据说话。

dev-fix 做的就是这件事。

它不是为了让 AI 修得更慢。

它是为了让 AI 不再用速度掩盖不确定。

下一次,当 AI 对你说“已修复”的时候,你可以先别急着信。

问它四个问题:

你怎么复现的?

你排除了哪些假设?

root cause 是哪一层第一次引入的?

把 fix 拿掉,测试会不会重新红?

如果这四个问题答不上来,那就别让它说“修好了”。

它只是暂时让错误安静了。

而真正危险的 bug,最擅长的就是安静地活着。

在这里插入图片描述

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐