AI 写代码越来越强,但它不知道我们上周修了什么 bug,把 Bug 重新引入生产环境了

12 小时 40 分钟前
 huoru

三个月前,我们花了两周时间修了一个 bug 。

用户账单偶尔会出现 ¥0.01 的误差。复现不稳定,线上偶发,客服一直在手动补差价。翻遍了日志,最后定位到一个经典问题:浮点数精度。

0.1 + 0.2 在 IEEE 754 里不等于 0.3,这个大家都听说过,但真的踩到还是很痛。

修复方案是把所有金额改成以「分」为单位存整数,展示时再除以 100 。改动不复杂,但涉及的地方很多,改完之后代码里到处是 amount * 100Math.round()/ 100,看起来有点丑,但能用,bug 消失了。

然后上个月,我让 Agent 帮我重构结算模块,优化一下代码结构。

它看到那堆乘除,觉得多此一举,顺手给「简化」了,改回了直接用浮点数。逻辑更清晰,代码更短,单元测试全过。

那个 ¥0.01 的 bug 悄悄回来了。


这件事让我开始认真想一个问题:

我们有代码审查,但我们需要「意图审查」。

这不是 AI 写了烂代码

大家讨论 AI 写代码,通常聚焦在输出质量上:架构合不合理、有没有 bug 、测试过了没有。

但我说的这个问题不一样。

Agent 那次重构质量是好的。逻辑清晰,可读性提升了,测试也没挂。它只是不知道「那堆看起来多余的乘除,是两周 debug 换来的」。

从它的角度看,price * 100 然后 / 100 是纯粹的噪音——数学上等价,白白增加了阅读负担。优化掉,天经地义。

你想想一个工作了五六年的老工程师,碰到这段代码会怎么做?

大概率不会直接删。他会皱着眉头想:「这里为什么要转成整数?不可能是手滑写的,一定有原因。」然后去翻 commit ,或者去问写这段的人。

这叫对不熟悉代码的谦逊感——被坑多了,知道看起来多余的代码往往不是真的多余。

AI Agent 没有这个。它被训练成「顺着干」,而不是「先问为什么」。代码看起来可以简化?简化它。逻辑看起来绕弯子?拉直它。它只会优化表面,不会追问历史。

新项目里这没问题,在一个有历史的真实代码库里,这会出事。

现有的方案都差点意思

每次我说起这个问题,大家都会推荐一些工具。我挨个想过,说说为什么都不够用:

注释:「// 不要修改这里,浮点精度问题」写上去当然有用。但你得先意识到这里需要警告,才会去写。当时修 bug 的人在想的是「终于修完了」,不是「三个月后的 Agent 可能会把这个改回去」。漏写的注释保护不了任何人。

AGENTS.md / CLAUDE.md:同理,你想到的禁忌才能写进去。但大多数坑是事后才知道是坑的,你没法提前把所有决策都整理成文档。

ADR / RFC:门槛太高,大多数团队第一个季度之后就不维护了。就算维护着,也是给人看的文档,不是供 Agent 在改代码前按需查询的。

Wiki / Notion / Confluence:文档会和代码脱节。「金额统一用分存储」这件事,可能在某个内部文档里提了一句,但 Agent 不会在重构代码前主动去翻 Confluence ,就算翻了,也是一堆非结构化的文字,未必能命中。

PR 描述:当时修 bug 的 PR 描述里可能写了原因,但埋在 GitHub 历史里,没人去翻,Agent 更不会主动去看。

每一个工具都是对真实问题的局部回应,但没有一个能在 Agent 动手之前,把「这里曾经踩过什么坑、为什么要这么写」这件事,可靠地送到它面前。

我们缺的是「意图审查」

现有的代码审查,回答的是:「这个变更实现得好不好?」

看 diff ,查正确性、风格、测试覆盖率。这个流程很成熟,也很必要。

但代码审查解决不了另一个问题:「这个变更,在已有的历史背景下,方向对不对?」

这个问题需要审查者记住代码库的历史,记得这堆乘除是两周 debug 换来的,记得某个绕弯子的写法是填过坑之后留下的疤。大多数审查者没这个上下文,就算是原作者,三个月后也可能忘了当时为什么。

所以我觉得我们需要一层新的审查,叫意图审查,发生在代码被改之前,而不是之后。

它问的是:

对人类工程师来说,这些审查以非正式的方式发生:发条消息、瞄一眼 commit 历史、和写那段代码的人聊三十秒。

对 AI Agent 来说,这根本不会发生。没有「去问问当时踩过坑的人」的等价操作。Agent 读当前代码,看起来可以优化,就优化了,历史上踩过的坑对它来说是不可见的。

意图审查要怎么做

要真的能用起来,得满足三点:

第一,决策必须是结构化的。

「金额统一用分存储,避免浮点精度」这句话写在 Wiki 上供人阅读没问题。但 Agent 需要的是:决定了什么、为什么这么决定、涉及哪些文件、哪些操作是被明确禁止的。自由文本把这些结构藏起来了,Agent 每次都要重新解析一遍,还不一定能命中。

第二,决策必须住在代码旁边。

Wiki 会漂移,Notion 会被遗忘,Slack 消息串会被淹没。唯一能和代码永远保持连接的是 git 本身。决策活在 git 里,就能跟着代码一起被 clone 、被 fork 、被带到三个月后还没接手过这块的人面前。

第三,查询必须自动发生,在改代码之前。

如果需要提醒 Agent 「先查一下历史决策」,它就不会查。这个步骤必须内嵌在正常工作流里,就像它在重构前会先 grep 符号定义一样,查历史的摩擦要低于直接动手的摩擦。

我做了个工具

我最近在开发一个叫 Mainline 的东西,把团队决策以结构化记录的形式存进 git 本身,让 Agent 在改代码前可以查询。

每条「意图记录」长这样:

如果当时修浮点 bug 的时候封存了一条意图记录,三个月后 Agent 重构结算模块,运行 mainline context billing,就会看到:「金额统一用分存储,直接用浮点运算会导致 ¥0.01 偶发误差,已确认线上踩坑,禁止还原。」

那次「简化」大概率就不会发生了。

用了一个月,有几点出乎我意料:

摩擦不在我以为的地方。 我以为工程师会抗拒写这些记录,结果没有——因为 Agent 负责起草,工程师只是过目和调整。真正的摩擦在于:什么时候该封存一条记录?封太频繁是噪音,封太少会漏掉重要的坑。

收益来得比预期晚。 第一周感觉纯粹是额外负担。第三周开始出现「这段为什么要这么写?」的时刻,答案就在日志里。第六周,Agent 开始不用提示就把历史决策当作上下文来用。

两个人协作比一个人用难得多。 一个人用的时候,意图记录是写给自己的备忘。两个工程师同时工作时,它变成了一个协调协议——你得知道对方封存了什么,你们的方向有没有冲突。

工具是开源的( Apache 2.0 ),目前小范围私测,地址在 mainline.sh

https://github.com/mainline-org/mainline

说回这件事本身

AI Agent 现在在很多团队里写相当比例的新代码。代码审查的负担没有降低,反而在升高——因为审查 AI 写的代码认知成本更高,你没办法像问同事一样问它「你为什么这么改」。

现在的情况是,每次代码审查都得身兼两职:一边看实现,一边猜方向对不对。大多数时候,方向验证是静默失败的。审查者不知道那堆乘除是修 bug 留下来的,点了 approve ,坑就回来了。

这个问题靠更好的 prompt 解决不了,靠更大的上下文窗口解决不了,靠更强的模型也解决不了。

这是一个结构性问题:团队踩过的坑住在哪里?

现在它住在人的脑子里、Slack 里、PR 描述里——Agent 不会主动去看的地方。

要让 AI 真正能在一个有历史的代码库里可靠地工作,我们需要把这些知识搬到它会可靠地去看的地方。对大多数团队来说,那就是 git 。

我们有代码审查。我们需要意图审查。


你们有没有被 Agent 悄悄还原过某个 bug 修复?现在是怎么防的?

4811 次点击
所在节点    推广
98 条回复
iugo
10 小时 48 分钟前
"用户账单偶尔会出现 ¥0.01 的误差。复现不稳定,线上偶发,客服一直在手动补差价。" 将这句话喂给 AI, 我相信只需要几分钟 AI 就可以定位问题.

后续的问题, 也和 Agent 有关, 如果 Agent 稍微聪明一点, 或者 MCP 好用一些, 应该通过 git 历史定为到这个修改, 并且结合 git commit message 理解意图, 不需要我们特地说.
kloge
10 小时 47 分钟前
写的真好
(另外单测也覆盖不到 amount * 100 这种粒度吧...)
muzig
10 小时 44 分钟前
我觉得“补测试”是必要但还不够,金额这种坑最好再往代码结构里沉一层,比如 Money value object / 统一计算 API / lint 或 review rule 。这样历史意图不只是写在文档里,而是变成调用方绕不过去的约束。Agent 看到一堆 *100 / /100 容易觉得啰嗦,但如果入口类型和测试一起约束住,它想“简化”回 float 的成本就高很多。
yyysuo
10 小时 43 分钟前
@iugo 同意,喂给 ai 100 个这样的坑,这代码也别改了,ai 顺利继承了人类程序员写屎山的技能。
huoru
10 小时 43 分钟前
@iugo 是的,现在 agent 都不太会这个,除非特意说明。 所以我们后来根据这些问题做了 mainline 。

我理解为 AI 时代的 git , 让 agent 跟人使用的,关注意图,而不是关注代码

Ai 改代码之前永远先看意图,之后更新代码也更新意图(对人手动挡来说也一样)
chairuosen
10 小时 41 分钟前
写注释
paceewang1
10 小时 40 分钟前
如果是程序里普遍出现的 bug ,就放 rule 里,如果不是,这一行要加上注解为什么要这么写,应该后续就不会被改掉了
nomansky
10 小时 40 分钟前
感觉就是软件工程管理的问题,单元测试 corner case 没有测到。
cookii
10 小时 40 分钟前
这是 ai 的问题吗,这不是你们方案的问题?谁家用浮点数算钱?
paceewang1
10 小时 37 分钟前
@paceewang1 #26 就好像,开源项目的 bug fix ,也是要注解标明为什么要这么干,然后再贴一个具体 bug 的 issue 说明问题吧
quietnode
10 小时 36 分钟前
现在主要用 cursor ,这种情况目前我都是在项目中 doc 文档啦下 加一个类似于 ai-必看,然后 rule 指定了这个文件
mywaiting
10 小时 33 分钟前
与钱相关的坑,包括但不限于浮点数、币种、汇率转换、金额显示方式等等

也包含数据库事务处理上的扣款、退款、退款队列操作等等

都有很多现成的、经过实战检验的、无数人踩过的坑后,用真金白银堆出的“最佳实践”,准确来说,不能说最佳,应该用行业军规

为什么不重写为独立的 package/interface 去统一处理?

为什么要搞这些莫名其妙的、自以为已经解决问题的修补方式?

实在难以理解
banricho
10 小时 29 分钟前
我感觉还是你们的问题比较大,这种东西本来就应该抽象出来单独处理,而不是分散在业务中
如果自己都没办法做到逻辑收束,ai 只会跟着一起摆烂
guanzhangzhang
10 小时 29 分钟前
虽然下面对 ai 的看法是对的,但是你这个例子不对,这种涉及到金额的必须写单元测试避免出现回退
HENQIGUAI
10 小时 24 分钟前
抛开你的推广不谈,这就是架构设计上的问题
exonuclease
10 小时 23 分钟前
谁家好人存财务相关的数据不用 decimal 啊 还要自己发明奇怪方案 这也要怪 AI 吗。。。
而且你修 bug 的时候为啥不加新的 test case 来覆盖这种情况?
vonfry
10 小时 19 分钟前
> 0.1 + 0.2 在 IEEE 754 里不等于 0.3 ,这个大家都听说过,但真的踩到还是很痛。
虽然但是,这么多楼居然没人提定点数。涉及金融用定点数是常识吧?怎么会用浮点数来计算啊。

> 修复方案是把所有金额改成以「分」为单位存整数,展示时再除以 100 。改动不复杂,但涉及的地方很多,改完之后代码里到处是 amount * 100 、Math.round()、/ 100 ,看起来有点丑,但能用,bug 消失了。
这个看业务,以分为单位存其实也并不好,比较好的是以“基点”(变动的最小单位)。不过你们的业务可能不需要这么高的精度就是了。
jchnxu
10 小时 18 分钟前
@cowcomic 俺也是这个思路
justdoitzZ
10 小时 10 分钟前
我之前遇到情况,在算力紧张的平台,通过对浮点型进行方法,转换为整型计算,以节约资源。就间接解决了你说的这个问题。

不过,确实,AI 缺少和真实世界的互动,缺少真实世界的经验,而这些经验,我推测 AI 大厂都在疯狂的收集用户数据来积累。

我每次解决完此类问题,都是让 AI 总结,形成 md 格式的项目经验文档,放到项目根目录,然后后面就可以访问到了。不知道你这个软件有什么差异。

很多人现在都不看代码了,但是在敏感和高风险领域,每一个 AI 修改每一处代码,都需要仔细 review 。比如我的工业生产领域,AI 修改的代码,我都会手动复制到真正的生产代码里面,然后进行仔细的测试,才能上线
6ugman
10 小时 6 分钟前
犯这种错误的团队为什么还不被开掉

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://study.congcong.us/t/1211151

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX