先说结论,省得你往下翻:

AI 写的代码,把关的方式不是「看得更细」,是「换个东西看」。

代码对不对——交给机器,但必须逼它真正跑起来,不是嘴上说通过了。
改动能炸多大——这才是留给人的活,也是现在机器还替不了你的那一块。

下面说我具体怎么干。

一次 3 行的改动,炸了三个项目

我现在每天有十几个 Agent 同时给我干活。它们不吃饭、不睡觉、不摸鱼,代码是成批往我这儿涌的。而我,一个人,两只眼睛。

那天我 review 其中一个 Agent 提交的改动:换个图标,改了 3 行。我扫了一眼,干净,通过。

第二天早上,隔壁三个项目的构建全红了。

那 3 行本身一点毛病没有。问题是那个静态资源目录是几十个子项目共用的,仓库里塞着七十多个小应用,都从那儿取图。AI 不知道,我也没想起来。

那天我想明白一件事:我逐行看过、还点了头的代码,炸点根本不在我看的那些行里。

这不是我不够仔细。这是逐行 review 这件事,到了 AI 时代已经行不通了。

一、为什么逐行看是条死路

第一,快慢完全对不上。 它 20 分钟写 800 行,我一小时看 200 行。你哪是在看代码,你是在被灌。看到第三个文件的时候,你的眼睛已经在滑了——这时候你不是在把关,你是在给自己制造「我看过了」的心理安慰。

第二,AI 的 bug 长得不像 bug。 人写的 bug 有手感:变量名打错、大小括号少一个、复制粘贴忘了改。你扫过去会「咯噔」一下。AI 不会犯这种错,它的每一行都语法正确、命名规范、还配着注释。它错在它没写的那部分,和它当成理所当然的那些事上。

一句话:人写的 bug 长得像 bug,AI 写的 bug 长得像正确答案。

第三,炸点常常不在 diff 里。 上面那 3 行就是。改动本身完美,炸的是没出现在 diff 里的那七十多个项目。你把 diff 看穿了也看不见它——diff 只显示改了什么,不显示碰了什么。

二、第一关交给机器:让它自己测,但得是「真启动」

「让 AI 自己写测试」这句话,坑就坑在中间那两个字:自己。你不给标准,它一定给自己出最容易的那张卷子。

骗局一:编译过 ≠ 能跑

现在的前端构建工具,为了快,默认不做类型检查——它把 TypeScript 的类型当注释直接剥掉,只做语法转换。所以你少 import 了一个函数,构建照样绿,包也照样打出来,用户点到那个按钮才崩,控制台一行 xxx is not defined

这就像过安检只称包的重量,不看包里装了什么。轻,就放行。

我们线上真吃过这个亏,而且吃过不止一次:改动本身逻辑完全正确,就是漏了一行 import,全流程绿灯直达生产。

骗局二:单元测试全绿 ≠ 能跑

AI 写单元测试有个极其稳定的坏习惯:把所有依赖都换成假的。 数据库返回什么,它编;接口返回什么,它也编。然后被测的那段代码,在这个由它亲手搭建的理想世界里,跑得非常漂亮。

这叫自己出题、自己判卷、自己打满分。

真实的翻车长这样:假数据里数据库返回一个规规矩矩的对象,真库里那一列三年前存的是空值,代码一拿来用,空指针。测试 100% 覆盖率,生产 100% 崩。

一句话:AI 写的测试默认在证明「我是对的」,你得逼它去证明「我可能是错的」。

所以:必须真正启动,跑集成测试

有三类问题,只有真的把服务起起来、连上真库、走一遍真接口,才抓得到

  1. 启动才炸:两个 bean 重名、注入了实现类而不是接口、配置项拼错——编译的时候全对,一启动就炸。
  2. 数据和想象不一样:库里的老数据,长得跟你想的不一样。测试库是干净的,生产库是有历史的。
  3. 前后端对不上:字段名对不上、类型对不上、时区差八小时、枚举多一个值。两边各自的单测都绿,一联调就散。

这三类,恰恰是 AI 最容易翻车的三类——因为它写代码时看得见你的代码,看不见程序真正跑起来是什么样。

逼 AI 真测的四个动作(可以直接抄)

  1. 验收标准你给,别让它自选。「跑通」太模糊。要写成:起服务,调这个接口,把接口返回的原文贴给我。
  2. 要证据,不要结论。 它说「已验证通过」的时候,八成没跑,或者跑的是它自己拿假数据搭出来的世界。让它贴日志、贴返回内容、贴报错行。我被这句话骗过:Agent 信誓旦旦说修复已生效,我自己复现了一遍,发现代码根本没走到那个分支。
  3. 让它先写一个会失败的测试。 测试得先挂一次,再改到通过。AI 默认就写「一上来就过」的测试,那种等于没写——它只是把现在的表现原样抄了一遍。
  4. 把容易出事的情况点名。 正常情况它一定会测,出问题的情况它一定不测。你得点名:依赖超时怎么办、返回空怎么办、用户手快点两下怎么办、这条数据已经被别人删了怎么办。

做到这四条,「对不对」这件事基本就不用你再盯着了。剩下的才是你的活。

三、第二关留给人:只看能炸多远

人看代码,本来就不该盯着「这行写错了没」——那件事机器比你细。人要回答的是另一个问题:

这个改动,最多能牵连到谁?

我把它拆成三个圈,一圈一圈往外看。越往外,炸得越大。

第一圈:代码——这段东西有多少人在用

新增一个文件、500 行,影响范围是零,炸也只炸自己。
改一个公共方法、3 行,影响整个仓库,炸了全家一起。

一句话:diff 的行数和风险没关系,引用数才有关系。

所以看 diff 的第一件事不是读逻辑,是看文件路径。路径里带 common、base、utils、assets、interceptor、公共组件目录的,风险自动升一级——不管它只改了几个字符。

好消息是:数引用这件事,正是 AI 最擅长的体力活。搜集让它干,你只负责拍板。 让它先交一份清单:

这次改动涉及的每个函数/文件/字段,
列出仓库里所有引用它的位置,
按「本模块内 / 跨模块 / 跨项目」分组给我。

跨项目那一栏只要有东西,这个改动就不能顺手放行。

第二圈:数据——新代码碰上老数据还能跑吗

AI 看得见你的代码,看不见你数据库里躺了三年的东西。

典型翻车:加一个非空字段,存量几万行全是空的;把整数换成枚举,老行的值根本不在枚举里;加了个布尔字段,历史数据存的是空字符串,读出来一转换直接崩。

这类 bug 有个共同特征,测试环境永远不炸,生产必炸——因为测试库的数据是你上周才造的,干净得像样板间;生产库的数据是三年里各种历史版本、各种手工订正、各种导入失败留下的沉积岩。

所以但凡改动碰到了字段、类型、状态值,人必须问一句:碰上库里最老、最脏的那一行,还能跑吗?

第三圈:时间——发版那一刻,正在跑的怎么办

这条最容易被漏掉,因为它在代码里完全看不见。

你发版的那一秒,系统不是静止的:有几十张单子正走在审批流程中间,有用户正填着表单没提交,有浏览器里跑着上个版本的前端包,还有定时任务正跑到一半。

改了流程定义,没走完的老单子还钉死在旧版本上,撤都撤不回来;改了接口字段名,用户手里的旧页面还在按老格式发请求。

这就像火车还在跑,你去换铁轨——光看图纸,新轨道没有任何问题。

三个圈之外,再问三句

  • 最多能炸到谁? 说不清楚就别放行。说不清楚,本身就是最大的危险信号。
  • 炸了多久能发现? 有没有告警、有没有日志。半小时发现和一周后被客户发现,完全不是一个级别的事故。
  • 炸了能不能一键回滚? 能回滚的可以放宽,不能回滚的必须最严——改过的老数据、发出去的消息、扣掉的钱,都是回不来的。

这三句话,我在评审会上问了很多年。AI 来了之后,我发现它们不但没过时,反而是唯一还得人来干、AI 替不了的那部分

四、一张放行清单

改动类型 范围 把关方式
纯文案、纯样式、独立新页面 AI 自测跑通即可,人扫一眼路径
改业务逻辑(本模块内) 必须真启动跑一遍主流程 + 一条异常情况
改公共组件 / 工具类 / 拦截器 / 共享资源 人必须看谁在用它;有跨项目引用,就单独过一遍
动表结构、动字段类型、改库里的老数据 最大 先看老数据长什么样;在有脏数据的库上验;回滚脚本先写好
动流程、状态机、定时任务 最大 必须回答正在跑的数据怎么办

一句话概括这张表:风险不看它改了多少,看它碰了多少。

最后

以前我最得意的技能,是扫一眼就看出这行有问题。这技能现在贬值得厉害——机器比我细,比我快,还不困。

但有一样东西它没有:它知道全世界怎么写才算好,却不知道我们那个目录被七十多个项目共用,不知道那张表三年前导进过一批脏数据,不知道生产上还挂着一百多张走到一半的单子。

AI 有全世界的知识,但没有你们公司的记忆。

所以把关不是看得更细,是换个东西看:对不对,让它自己跑给你看;能炸多大,你自己判。

我之前写过一篇《AI 帮我省下的时间,最后全用来给它擦屁股了》,说验 AI 的活比自己写还累。这篇算是那篇的下半场——累,是因为你还在验它写得对不对。那件事该交给机器。你的活是判断它能炸多大。

这活它干不了。因为它没在这家公司待过三年。

说到「三年」

这篇讲的是 AI 没有你们公司的记忆。反过来也一样:你从一篇文章里能拿走的只有知识,从一个人身上才能拿走标准——而后者同样要靠时间熬。

我写过一篇《关注一个人,是一种习惯熏陶》,标题起得挺一般,但里面那个故事我自己一直记得:

早年有个女生主动提出来跟我合租,说想看看我是怎么学日语的。她很快过了二级,速度惊人。然后她搬走了。不是吵架——跟高标准的人住在一起,前三个月是激励,第四个月开始是折磨。环境会传染,但传染需要时间,而大部分人会在传染完成之前跑掉。

要是你打算在我这儿待够三年,那篇值得看看。

posted on 2026-08-18 11:42  编程一生  阅读(500)  评论(4)    收藏  举报