提效数字喊得越响,我越担心代码库里那些藏在Diff后面的东西。最近团队里不少人在用AI编程助手写代码,项目总结里动辄“提效300%”,效果确实有:写原型、补脚本、生成模板,速度比以前快一大截。但等新功能上线、热乎劲过去,总有人小声问我一句:“这个模块现在怎么这么乱?”
这里的Copilot审查,不是指某款叫Copilot的审查工具,而是指对AI编程助手产出做代码评审这件事。我观察到的现实是:大多数团队只审了“代码能不能跑”,没审“跑了之后留下什么”。AI生成的代码从单个文件看往往合格,但放到整个项目里,架构约束、存量债务、依赖安全这三个维度经常被漏掉,等烂摊子成形,再想收拾就得付出好几倍成本。
这篇文章不会讲大道理,只聊我实际踩过的坑和现在团队跑通的审查方法,适合正在小规模引入AI辅助开发的团队负责人、要做Code Review的成员,以及一个人维护多个项目的开发者。
1. 盲区一:只审“能跑”不看设计,架构一致性成了隐形雷区
1.1 AI补丁为什么会绕过项目约定
先看一个最常见的场景。老项目里有一套约定:Controller不能直接碰数据层,必须经过Service,再由一个门面类统一处理权限和日志。某次业务方提了个小需求,开发用AI助手生成了一段改动,Prompt里只描述“新增一个查询接口”,结果AI直接让Controller去调Mapper,把整条调用链跳过去了。
代码能跑吗?能跑。测试能过吗?单测能过。但架构上的约定被绕过了。更麻烦的是,等下一个功能在这个Controller上叠加时,新人照着这个“新范式”继续写,分层约束就慢慢废掉了。
为什么会这样?AI模型的训练数据来自海量公共代码库,它最擅长输出的是“最常见写法”,不是“你们团队内部约定”。上下文窗口也有限,它看得到当前文件,但未必记得清楚项目里模块边界、统一响应包装、异常策略这些约束。而且它没有“敬畏心”,不知道某条看起来很绕的代码为什么存在——比如那个门面类,可能是为了审计合规硬加的,AI觉得多余,就给你“优化”成最短路。
很多团队review的时候只看功能对不对,不看“位置对不对”。但架构问题恰恰是AI代码里最隐蔽的烂摊子:它不报错,不上线后立刻爆炸,而是等你在上面叠加第三、第四个需求时才爆发。
1.2 架构一致性审查的落地清单
我现在要求团队在评审AI生成的代码时,不再只回答“这段需求对不对”,而是额外回答一个问题:这段代码放在这里对不对。为此整理了一份检查清单,每项都在Diff之外做个对照:
- 模块分层:改动是否跨层调用?Controller是否直接访问了数据访问层?Service是否被另一个Service当工具类用了?
- 统一基类与注解:项目里已有的统一基类、自定义注解,AI是否绕过了?比如有鉴权注解,它却用了裸接口。
- 响应与异常包装:返回值是否走了统一结构?异常是否用了项目里的自定义异常体系,还是直接抛了RuntimeException?
- 序列化与格式:日期格式、枚举序列化方式、日志格式,是否和项目现状一致?
- 命名风格:AI容易生成“通用名”,比如DataHolder、InfoModel,这在老项目里会显得非常突兀。
实际操作时有个小技巧:不要把清单放在脑子里,而是直接打开Diff,搜索关键词。比如import新增了哪些包,new Service()出现在哪里,@Override是否被错误地加到不该加的方法上。扫描一遍这些点,比通读全部代码快得多。
另外,我还见到过一个更有效的做法:把团队架构约束写进一个简短的说明文件,放在仓库根目录,每次让AI生成代码之前先让助手读取这个文件,然后在生成完成后对比约束逐条自检。实测下来能减少相当一部分“位置不对”的改动,但没法完全消灭,所以人工审查这一关必须保留。
2. 盲区二:只盯新代码不管存量债务,重复逻辑和反向修正越积越厚
2.1 “测试全绿”是如何骗过所有人的
第二个盲区更隐蔽,因为它藏在“看起来一切正常”的背后。AI擅长两件事:复制粘贴和“合理化”老代码。前者让重复逻辑滚雪球,后者让旧逻辑被偷偷改动。
先讲复制。有一次同事让AI补三个类似功能,生成完以后代码量涨了800行,但其中700行是同一个模式复制三份改参数。看起来每份都能跑,实际上任何逻辑修正都要同时改三处,漏改一处就出bug。我用一个简单的脚本扫了下新增函数和已有函数的相似度,发现三处超过90%的重复。可如果只是人工看Diff,很容易觉得“风格一致,没什么问题”。
再讲“合理化”。AI看到一段try-catch,里面catch之后什么都没做,会认为它是多余代码,直接删掉。问题是那段空的catch是有意为之,用于兜底某个线上数据异常,测试环境永远不会触发。这种改动不会让CI变红,测试照样全绿,但生产环境一出事,排查的人会想破脑袋也想不明白为什么原有的保护不见了。
更要命的是AI生成的单元测试。它写测试时通常测的是“理想路径”,输入构造得漂漂亮亮,断言写得松松散散,真正的边界值、异常分支、超时场景一个都不碰。覆盖率看着上去了,但都是虚高。
2.2 把“存量兼容”写进评审动作
我们的应对办法是给AI改动增加“存量兼容”审查维度,核心是回答四个问题:
- 原有接口签名有没有变?特别是被AI“简化”掉的参数,可能下游还在用。
- 原有异常类型有没有被替换?AI可能会把项目自定义异常改成普通Exception。
- 原有兜底逻辑有没有被删?凡是AI删掉的catch、if、默认值,一律要求提交者解释。
- 测试断言是不是AI自产自销?AI写的测试必须至少补充一条失败场景。
实际操作时,我要求Diff超过一定规模的AI改动,不能只看新增行,还要看删除行。很多人审AI代码时只看新增功能,不关心删掉的部分。我后来养成了一个习惯:把Diff里所有的删除行单独拉出来看一遍,再问一遍“这行代码为什么会存在”。这个动作帮我拦下过至少三次上线后才可能爆发的线上问题。
另一个实用的方法是重复代码扫描。不用复杂的工具,在项目目录里跑一个简单的字符串相似度对比,或者直接用IDE自带的查找重复功能,把AI新增代码里相似度高的片段标出来。标准很简单:一段逻辑如果出现过两次,就该抽取公共方法;出现过三次,就该重构;出现三次以上还散落各处,这个烂摊子早晚要有人去还。
3. 盲区三:依赖、权限与调用链安全,三处失控都藏在Diff之外
3.1 一个新依赖可能带来的连锁风险
第三个盲区是最容易被人忽略的,因为问题根本不在Diff里。AI写代码时为图省事,经常会在生成结果里引入第三方依赖,甚至推荐一个体积很小但来路不明的工具库。
我见过一次:让AI处理一个日期格式的兼容问题,它没有用Java 8自带的API,而是推荐了一个老旧的第三方日期库。原因是AI的训练数据里这个库出现频率高。开发人员一看“代码能跑”,就把依赖加进来了。后来一查,这个库的维护状态早已停止,许可证类型也和公司合规要求冲突,只能临时回滚方案,白白浪费一天时间。
依赖问题之所以叫“盲区”,是因为它不在代码审查的常规视野里。你审的是业务逻辑,是新代码有没有Bug,但依赖引入通常只出现在一个不起眼的配置改动里,不会触发任何测试失败。
3.2 数据暴露与越权的几个高发模式
权限和数据暴露是另一个高发问题。AI对“前置鉴权”这件事没有很强的意识。它根据Prompt里的描述“把用户信息导出成文件”,就真的生成一段直接查询全量用户表并写入文件的代码。
我整理过几个在AI生成代码里反复出现的高风险模式:
- 接口直接返回实体类内部字段,而不是视图对象。
- 导出功能没有校验当前用户是否有权限,只要登录就能触发。
- 管理端接口被当成普通接口生成,绕过内部管理系统的权限中间件。
- 日志里打印了凭据、令牌、个人信息等敏感字段。
还有一类问题是数据污染。有人为了方便,把包含真实日志或线上配置的文本直接贴进Prompt,结果AI把这些内容当成上下文的一部分写进了代码或文档,敏感信息就这样跟着合并进仓库。这种事测试很难发现,因为功能不影响,但合规上是雷。
3.3 给安全审查补上“调用链”视角
对AI生成的代码做安全审查,不能只看文件内部逻辑,要看整条调用链。我建议在评审时增加三个关卡:
第一关,依赖白名单。凡是新增的第三方库,不能跟着代码合并一起进,必须先单独登记,说明用途、许可证、维护状态,确认与公司策略一致后才能整合。
第二关,权限与数据扫描。在Diff里搜索token、apiKey、password、user这类关键词,逐条确认它们出现的上下文。只要是跟用户数据或系统配置相关的调用,必须确认前方有鉴权校验。
第三关,调用链走查。不要只在编辑器里看这个函数,要用IDE的引用查找功能,往上找谁调用了它、请求从哪个入口进来,往下找它会访问哪些存储和外部服务,再想一想“如果调用方的权限不够,这段代码会拦截吗”。
有人觉得这些步骤太重,会拖慢AI提效的速度。但我的判断是:安全审查慢半拍,好过上线一小时就被人拿脚本刷数据。AI提效省下的时间,省不出安全备案和权限审批的时间。
4. 可落地的AI代码评审SOP:角色、节奏与门禁
4.1 谁评审、谁守门:三个角色缺一不可
聊了这么多盲区,下面说说团队现在用的评审流程。这套流程不复杂,但强调角色分离。
第一个角色是提交者,也就是让AI生成代码并做人工调整的人。TA必须在提交时标注哪些改动是AI生成的,哪些是人工修过的。这很重要,审查者在看Diff时,如果知道某段代码是AI原生、没经过人工修改,会带着更谨慎的态度去看。
第二个角色是评审人,要求是熟悉这个模块、但不参与本次改动的人。这块必须避免“谁提交谁自己审,因为AI代码一个人审,很难跳出自嗨循环。AI生成的代码往往看起来很有说服力,尤其是刚生成完,开发者还沉浸在“它能跑”的兴奋里,很难冷静挑毛病。换一个没有参与生成的人来看,反而容易发现“这段逻辑为什么放在这里”的问题。
第三个角色是守门人,一般是团队负责人或资深工程师,负责做最终合并决定。守门人不一定要看每一行代码,但要对门禁记录负责:是否有人工的审查结论、是否有已知风险的说明、是否需要回滚预案。
4.2 小步合并与变更说明卡片
第二点是控制每次AI改动的粒度。我们有一个很硬性的建议:单次AI生成的代码合并量控制在300行以内。超过这个量,人眼审Diff的能力就断崖式下跌。
我试过几百行的AI改动,当时强迫自己看了两遍,还是漏掉了一个被AI顺手删掉的默认值。不是我不认真,是人面对巨大Diff时确实会“信息过载”。小步合并虽然慢,但每一步都可靠,出问题也容易定位。这和人工代码评审的道理一样,AI代码更要如此,因为AI不会主动告诉你它改了哪些不该改的地方。
我们还会要求提交者填一张很小的变更说明卡片,不用长篇大论,四五个问题:
- 本次改动改变了哪些接口的语义?
- 有没有新增第三方依赖?
- 有没有直接访问数据层或绕过中间层?
- 有没有修改权限相关的逻辑?
- AI生成的测试覆盖了哪些场景?人工补了什么测试?
这张卡片随着合并请求一起提交。它真正的价值是逼着提交者在自己脑子里再过一遍“我到底带进来什么”。不少问题在填卡片的时候就自己发现了。
4.3 评审门禁怎么判定通过、打回
评审结论我们分成三档,很朴素:
- 通过:改动在既定架构约束内,测试补齐了关键场景,没有新增依赖或依赖已通过审批。
- 打回:绕过分层、删除兜底逻辑、新增未备案依赖、跳过鉴权校验。任何一条命中,直接打回。
- 带警告通过:功能正确,但代码结构或者命名存在问题,允许先合入,同时记录一条技术债务,约定时间修复。
这三档判定必须写清楚,特别是打回条件。团队里一旦形成“AI生成的都放行”的风气,烂摊子就会指数级增长。相反,明确打回标准,AI才能被当成一个需要管理的工具,而不是替你做决定的同事。
5. 常见问题速查与一线实操心得
5.1 高频问题速查表
| 现象 | 常见根因 | 排查路径 |
|---|---|---|
| AI改动测试全过,但老接口在线上报错 | 改动删掉了旧兜底逻辑或默认值 | 单独拉出Diff的删除行,逐条追问“为什么会存在” |
| 编译通过,但新增接口不返回统一格式 | AI没有感知项目通用响应包装 | 检查Controller返回值类型,是否用了统一基类 |
| AI生成的单测覆盖率很高,但边缘场景全裸奔 | 测试只覆盖理想路径 | 至少补一条边界/异常场景测试,再放过 |
| 合并后重复代码暴涨 | AI倾向于复制改写相似逻辑 | 用重复代码扫描工具或IDE功能扫Diff内新增片段 |
| 新功能引用了没人认识的第三方库 | AI基于训练数据推荐了“高频库” | 新依赖走单独登记,不随代码合并 |
| AI把Prompt里的日志文本写进了代码 | 数据污染 | 禁止在Prompt中粘贴敏感配置和真实日志 |
这张表不是标准答案,而是我们在实际审查中频次很高的几类问题。遇到相似情况,先按对应路径查一遍,往往能比漫无目的地看Diff更快定位问题。
5.2 我在实际评审中的几条习惯
最后分享几个我长期坚持的小习惯。第一条,审查AI代码时我特别爱问一个问题:“项目里已经有什么,为什么AI不用它?”这个问题能同时暴露架构意识缺失和重复代码问题。如果答不上来,多半是AI没有理解项目上下文。
第二条,凡是AI生成代码,我坚持要求提交者至少人工重构其中一处再提交。哪怕只是改一个命名、抽一个变量,这个动作的目的不是优化那行代码,而是强迫提交者从“AI写完了我看看”变成“这段代码我来负责”。这个小技巧能让AI代码的质量明显提升,因为它打断了“生成—复制—提交”的自动驾驶过程。
第三条,我会把架构约束和审查清单沉淀成文件,在执行评审时直接用清单逐项对照,而不是只凭经验和感觉。AI时代,人写代码的比例降低了,但定义约束的比例必须提高。一个没有约束的AI助手,只会帮你更快地制造一个更规整的烂摊子。
如果你也在团队里引入AI编程助手,建议先别急着追求“提效300%”,先把这三个盲区盯住。等你能连续几个迭代稳稳定义清楚“代码是否合规、是否存在重复、是否带来风险”,再回头谈速度也不迟。毕竟,提效是为了少干活,不是为了留个更大的摊子给自己擦。