我们团队的代码仓库里,现在每十次提交里至少有三次是AI生成的。不是我赶时髦,是大家确实把AI用起来了:写单元测试、补DTO、生成SQL、做重构。但热闹归热闹,我作为负责最终代码审查的人,这半年最大的感受是:AI写得快,错得也“聪明”。它不是简单地把接口写错,而是能在你疏忽的边界条件、鉴权逻辑、异常路径上,一本正经地埋雷。所以我把这段时间的审查经验沉淀成了四条清单,还打回过两次AI代码。今天就把这四条清单和两次打回的完整过程展开聊聊,希望能给同样在审AI代码的同学一些参考,也顺便说说怎么让AI代码少被打回。
1. 为什么AI代码必须走人工审查
1.1 AI写得快,错得也“聪明”
先说个反直觉的现象:AI生成的代码,单测通过率其实很高。这是因为它写单测的时候,通常会照着实现逻辑去编用例,而不是照着需求文档去编用例。也就是说,代码和测试能自洽,但需求和代码未必自洽。你让它写“查询用户列表,已离职的不要显示”,它可能生成一句WHERE is_deleted = 1,看着没毛病,但is_deleted = 1到底是“已删除”还是“未删除”,完全取决于表设计。如果语义反了,单测照样全绿,因为AI写测试时会引用同一个错误条件。真到了业务上,线上用户列表可能直接空掉一大半。
AI大模型本质是“根据上下文预测下一个token”,它擅长让代码看起来流畅、结构完整、注释到位,但它在理解业务语义方面高度依赖提示词提供的信息。如果你把需求写得太简略,它会用训练数据里的“最常见理解”补齐,而这个“最常见理解”很可能不是你真正想要的。比如产品经理说“查询时排除已删除用户”,AI有可能在没有任何依据的情况下自作主张,把“删除标记”理解成“状态字段等于某个魔法数”。这种错误不跑真实业务数据是测不出来的,这就是AI生成代码必须走人工审查的根本原因。
1.2 人工审查的定位:不是不信任,而是兜底
我经常碰到的另一个极端是:要么完全不信AI,要么完全信AI。这两种心态都会出事。完全不信,等于放弃了提效工具,团队里其他人都开始用AI写代码,你一个人守着旧流程,最后你会发现根本审不过来;完全相信,就是把一个概率模型当成了确定性系统。AI写一万行代码,其中九千行没问题,剩下的一千行往往不是明显的语法错误,而是“看起来合理”的逻辑坑。
AI代码审查不是想象中的“逐行挑刺”,更像飞机驾驶里的机长和副驾驶:AI是副驾驶,能帮你巡航、计算、提示,但起飞、降落、突发故障这些关键时刻,决策权必须在人手里。人工审查不需要把每一行都读过去,而是抓AI最容易出问题的四类点:需求符合性、安全合规、工程质量、可维护性。这四类恰好是AI这种“文字接龙模型”的薄弱环节——它不是不知道怎么写代码,而是不知道你的业务、你的安全红线、你的团队习惯。后面四条清单,就是从这四类里沉淀出来的。
2. 我总结的四条审查清单
四条清单不是一次想出来的,是打回几次AI代码后慢慢沉淀的。每条对应一个问题类别。我审查AI代码时,会把清单打开,一项一项过,就像登机前的地勤检查一样。这份清单的核心思路是:不管AI代码看起来多“漂亮”,只问四个问题——它真的满足需求吗?它安全吗?它配得上团队标准吗?它不会给未来埋雷吗?
2.1 清单一:需求符合性——代码“对”不代表“正确”
“对”和“正确”是两回事。编译通过、单测通过,只是“对”;满足业务目标,才是“正确”。AI生成的代码特别容易在“看起来正确”上发力,却在语义层面翻车。审查这一条时,我会把需求拆成三列:正常路径、异常路径、边界条件。然后对照代码逐行确认,AI最爱忽略的就是第一列的“正常路径”之外的场景。
具体翻车例子我见过很多:需求是“查询某段时间内的订单”,AI写成了WHERE create_time >= NOW() AND create_time < NOW() + 1 DAY,结果查的不是“某段时间”,而是“今天”;需求是“如果配置不存在就用默认值”,AI写成了“如果配置不存在就报错”,完全反了;需求是“对一个可能为空的列表求和”,AI直接遍历,没做空值判断。边界值、空集合、超大值、并发场景,这些都是AI的天然盲区,因为它在预测时倾向于“最常见的情况”,而真实业务里最常见的情况往往是“有数据、有权限、参数合法”。审查AI代码时,看到注释里写“根据需求实现”千万别放松警惕,那只是AI在给自己壮胆,不代表它真的理解了需求。
2.2 清单二:安全与合规——AI最会“一本正经地埋雷”
安全问题是AI代码最隐蔽的雷区。它生成的代码往往格式规范、接口完整,但鉴权、越权、输入校验、敏感信息泄露这些细节,AI会默认省略。原因不难理解:训练数据里大量示例代码为了“简洁”,会略掉生产环境的防护细节。比如直接写SELECT * FROM orders WHERE user_id = ?,看着参数化查询没问题,但接口里压根没校验当前登录用户是不是这个订单的主人,谁传一个别人的orderId,就能查到别人的订单。
所以审查第二类清单时,我会对所有接收外部输入的地方走一遍“攻击者视角”:这个入口如果是恶意用户,能不能被利用?能不能越权?能不能注入?能不能通过日志把手机号、身份证打印出来?我见过AI生成的导出接口,直接把用户手机号明文写进日志里,美其名曰“方便排查”,这在合规场景下就是事故。还有一次,AI在处理文件上传时,直接拿前端传的文件名拼到路径里,路径穿越漏洞就这么来了。安全清单不需要每行都看,但要重点检查所有新增的Controller接口、文件读写、SQL拼接、第三方调用这几类高危点。
2.3 清单三:工程质量——让代码经得起半年后的自己
工程质量的审查,主要看重复代码、命名、函数长度、圈复杂度、异常处理和魔法数。AI代码在这个维度上往往极端分化:要么特别工整,变量名、缩进、注释无可挑剔;要么特别机械,用if (type == 1) ... else if (type == 2) ...堆出一堆魔法数,生成两个几乎一样的方法,只差一个参数,然后复制粘贴十遍。
我打分时不会要求一次生成就达到天花板标准,但至少要有基本可读性。典型要打回的情况包括:一个方法写了300行,圈复杂度爆表;catch块里啥也没干,异常被静默吞掉;出现大量data、info、temp这种看不出含义的命名。更离谱的一次,AI为了“严谨”,给一个普通方法加了三层Optional判空,结果里面还有一句.get()直接可能抛异常,等于把空指针风险藏在了“看起来很稳”的包装里。审查这条清单时,我会用格式化工具和静态扫描先做“机器初审”,把明显问题筛掉,然后集中精力看逻辑。机器抓不到的才是真正需要人的经验的地方。
2.4 清单四:可维护性与演进——别让AI给项目“盖危楼”
最后一条清单,我会看这次改动是否符合团队现有架构约定,依赖方向是否正确,有没有引入新的复杂模式,是否存在过度设计。AI特别喜欢照搬训练数据里的“最佳实践”,一个两三个分支的小功能,它能给你生成一个接口、两个抽象类、四五个实现类,还用上策略工厂,理由是为了“未来扩展”。问题是你现在压根不知道有没有未来,这堆抽象反而成了团队里所有人维护的负担。
审查时,我会问自己一个很朴素的问题:“这个改动未来三个月内会不会出现多个变体?”大概率不会,那就不需要模式。我还会看一眼新增的依赖数量,如果AI为了一个小功能引入了一整个开源库,那基本可以打回。团队里其他人能否一眼看懂,是比“设计优雅”更高的标准。毕竟代码是写给团队看的,不是写给设计模式教材看的。
| 清单 | 核心检查目标 | AI常见翻车点 |
|---|---|---|
| 需求符合性 | 业务输入、输出、边界、异常路径 | 边界条件遗漏、需求理解偏差 |
| 安全与合规 | 鉴权、越权、注入、敏感信息 | 接口无权限、参数校验缺失、日志泄密 |
| 工程质量 | 可读性、重复、命名、复杂度 | 魔法数、重复代码、空catch、滥用判空 |
| 可维护性 | 架构一致性、扩展性、过度设计 | 为假想需求堆模式、引入不必要依赖 |
3. 两次打回:两个真实案例复盘
四条清单是“纲”,两次打回是“目”。我挑两个印象最深的,完整复盘一下当时我怎么审、为什么打回、最后怎么让AI改对。这两个案例,一个栽在安全上,一个栽在过度设计上,基本代表了AI代码审查里最典型的两类问题。
3.1 第一次打回:看似完美的接口,漏了鉴权
背景是一个内部运营后台,需要新增“按条件导出用户列表”接口。AI基于现有代码风格一次性生成了Controller、Service、Repository、DTO,还配了单测,PR提上来,流水线全绿。我审的时候第一眼看到的是“代码很规整”,但打开清单二往下核对时,问题立刻暴露:接口没有加任何权限注解,任何登录后的普通运营都能调用。更严重的是,导出条件里的部门ID完全由前端传入,没有从当前登录用户上下文取,一个普通员工可以把整个公司的用户数据导走。
打回时我在PR评论里写了两条硬意见:“第一,该接口仅管理员可访问,请补权限校验;第二,数据范围必须取自登录人信息,不要信任前端传参。”那会儿AI还不会“自己反思”,我直接把期望改法也写出来了:使用@PreAuthorize限定角色,数据权限从安全上下文获取。把安全约束补充到提示词里重新生成后,第二次代码就正常了。这个案例让我印象特别深,因为AI不是不会写安全代码,是我们没把安全边界说清楚。从那以后,我所有涉及接口的提示词里都强制加一句:安全要求是什么、谁能访问、数据范围从哪里取。
3.2 第二次打回:为“未来需求”写出的过度设计
第二个案例是一个“根据业务类型返回对应通知模板”的小功能。需求明确,逻辑就是两三个分支。AI生成的代码交上来,打开文件我愣了一下:一个接口、两个抽象类、四五个实现类,甚至还用上了策略工厂。功能确实实现了,但引入的类数量超过业务本身的复杂度。对一个两三个分支的映射来说,这样的设计让新同学光看懂就要花半天。
打回理由我写得很直接:“当前场景不需要策略模式,请改为简单的if-else或switch,等出现更多类型时再重构。另外,请不要照搬网上的标准设计,以团队现有代码风格为准。”AI为什么会这么写?因为训练数据里的“高质量代码”强调设计模式、强调开闭原则,AI把这些当成了安全牌,但它不理解你的团队规模、项目阶段和维护成本。修改之后,AI给了一个30行的普通方法,逻辑清晰,测试也够用。后来我在提示词模板里加了一句话:“保持简单,避免不必要的抽象,优先使用项目中原有的写法。”这个案例告诉我,审查AI代码时,“过度设计”比“功能错误”更隐蔽,因为功能测试全通过,但它会慢慢拖垮代码库的可维护性。
3.3 两次打回给我自己的教训
两次打回之后,我给自己定了三条规矩。第一条,审查顺序不能乱:先需求,再安全,再看设计。如果先看代码风格,很容易被规整的格式迷惑,跳过真正的要害。第二条,打回意见要“可执行”:AI不是人,不能领会“你自己想想哪里不对”,必须给出具体位置、具体原因、期望改法。第三条,不要带预设立场:AI代码值得被认真对待,就像同事代码一样,审查目的是守住交付质量,不是证明AI不行。
这两次打回也直接推动了团队流程变化。我们在PR标题加[AI]前缀,让reviewer第一时间知道这是AI生成的代码,从而按不同的注意力分配来审。流程上看起来是小事,实际效果很明显,大家不再“一刀切”地信任或怀疑AI,而是会认真打开清单过一遍。
4. 怎么让AI代码少被打回:审查前的“前置动作”
打回成本很高,最好的办法是让AI在生成时少踩坑。与其事后审,不如事前把条件定清楚。这一章我总结了四个很有效的“前置动作”,它们不是理论,是我在这半年里逐步试出来的。
4.1 给AI喂“足够像需求文档”的提示词
很多人把提示词当“一句话需求”来写,比如“写一个用户导出接口”。AI只能根据这句话,用训练数据里最常见的实现补全,这等于把需求二义性直接带进了代码。我的做法是用“输入、输出、约束、异常、安全”五要素结构来写提示词,把业务的关键信息都框住。
差提示词: 用Java写一个用户导出接口 好提示词: 实现一个导出用户列表的接口,要求如下: 输入:查询条件(姓名模糊、部门ID可选)、分页参数 输出:CSV文件流,包含用户名、手机号、部门名 约束:日期格式统一为yyyy-MM-dd,手机号做脱敏 异常:查不到数据时返回空文件,接口报错时走统一错误码 安全:接口需登录后访问,仅ADMIN角色可调用;导出数据范围限定在当前登录人所属部门五要素不是越多越好,而是把业务关键信息写清楚,AI就不会“自由发挥”。审查时,我也会拿着提示词原文对照代码,逐条核对。如果提示词里写了“仅ADMIN可调用”,代码里却没有对应鉴权,那问题就非常明确,直接打回。
4.2 要求AI自检,但别把自检当结果
我在提示词最后经常加一句:“请先列出该实现可能出错的5个边界场景,再输出代码。”这个做法很有效。AI“自检”产生的列表能帮你快速定位审查重点,比如它会主动列出“空值、超大分页、并发写入、无权限、参数非法”这些场景。你别把它当成质量担保,它只是在基于概率做预测,但至少给出了值得关注的线索。
有一次AI在自检列表里写“未校验上游参数为空”,我顺着这条把对应代码翻出来,果然真没校验。如果它没写这一条,我可能要按照清单慢慢找才能发现。但也有翻车的时候,AI自检写的风险清单和代码完全对不上,那说明提示词给的需求信息不够,或者上下文太长,模型已经“晕”了。这种情况建议拆任务,不要硬问下去。
4.3 拆小任务,分批合并与审查
AI生成一个500行的大功能时,前后逻辑特别容易不一致,前面定义了变量后面没用,中间生成重复逻辑,后面甚至忘了前面的约束。所以我现在会把一个需求拆成几个前后依赖的小任务,逐个生成、逐个审、逐个合,像搭积木一样。每批代码控制在100到200行左右,review效率最高。
前后依赖怎么处理?先让AI生成接口定义和DTO,审完确认边界,再让它生成实现,最后生成测试。每步都有明确边界,问题能更早暴露。之前我们一次合并一个巨型AI PR,review一次要两小时,还容易漏;改成小步提交后,每次15分钟就能搞定,而且AI的“幻觉”概率明显下降,因为每一步的上下文都更短、更聚焦。
5. 审查工具与提效技巧
5.1 静态检查工具是“初审”,人是“终审”
工欲善其事,必先利其器。我推荐的工具组合是SonarQube、ESLint、CheckStyle、SpotBugs、CodeQL或者Semgrep,具体选哪个取决于团队技术栈。工具擅长干两件事:抓重复代码、算圈复杂度、扫硬编码密钥和SQL注入模式。这些交给机器,又快又准。
但工具是有局限的。它不理解业务上下文,看不出“部门ID应该从Session取而不是从请求参数取”这种问题。我见过有人拿SonarQube扫一遍就当审查完了,这是自欺欺人。工具把你的代码格式、重复度、复杂度管住了,剩下那些真正要命的业务语义、权限设计、架构一致性,才是人工审查的存在意义。正确用法是:把静态检查接到PR流水线,机器能抓的先抓完,人再集中精力看机器抓不到的部分,这样两边效率都最高。
5.2 让AI帮我审AI:同行评审的新玩法
这个玩法是我最近三个月才开始的,效果出奇地好:用一个AI模型审查另一个AI生成的代码。具体做法是,把四条清单、AI生成的代码、原始提示词需求描述一起发给审查模型,让它按清单逐项找问题。两个模型上下文不同、训练偏好不同,互相找茬能发现各自盲区。代码生成模型可能习惯省略安全校验,审查模型却会敏感地指出来。
实操格式很简单:给审查模型的指令是“你是资深代码审查专家,请按以下四条清单审查代码,输出问题列表、严重级别和修改建议。”它返回的报告,我会当“候选问题”清单来处理。有一次它指出“该方法可能产生NullPointerException”,我一看,确实AI代码里对Optional.ofNullable的返回值直接调用了.get(),这种审查模型确实能帮上忙。当然,AI审查结果不能直接当结论,因为审查模型也可能幻觉,最终拍板的一定是人,但它能帮你节省大量大海捞针的时间。
5.3 记录问题分类,沉淀团队清单
我建议团队维护一个文档,就叫《AI代码审查问题清单》。每次打回,记录问题类别、代码片段、触发原因,不需要写长篇大论,一两句话就够了。三个月后统计,你会发现规律非常明显。我这边最近三个月的数据是:边界条件遗漏占38%,缺少安全校验占24%,过度设计占15%,需求理解偏差占13%,其他占10%。
这个数据能反哺流程。边界条件最多,就在提示词模板里强制要求AI列出边界场景;过度设计频发,就加“保持简单、避免不必要抽象”的约束;安全校验不足,就要求所有接口的提示词都必须包含安全条款。下个季度再统计,打回率明显下降。这份清单不是用来追责的,是用来改进整个AI辅助开发流程的。
6. 常见问题与排查技巧实录
6.1 常见问题速查表
整理一张速查表,覆盖我在实际审查AI代码时最常遇到的问题,按“现象、原因、排查方法、处理建议”四列列出,方便你在PR评审现场拿过来直接对照。
| 问题现象 | 可能原因 | 排查方法 | 处理建议 |
|---|---|---|---|
| 编译和单测全过,但功能不对 | AI基于实现编造自洽测试,未对齐需求 | 拿需求逐条对照代码分支 | 补充需求描述,明确输入输出和边界,打回重写 |
| 接口缺少权限校验 | 提示词未提安全要求,训练数据默认省略 | 检查所有新增Controller的鉴权注解 | 在提示词加入安全约束,补充权限代码 |
| 代码特别“华丽”,类多好几倍 | 训练数据偏好设计模式 | 统计新增类和方法的数量,对比业务复杂度 | 要求保持简单,按团队现有风格实现 |
| 日志打印手机号、身份证 | 未把敏感字段纳入约束 | 搜索日志语句中的个人信息字段 | 打回并修改日志,在框架层加脱敏 |
| 单测覆盖率高但边角全空 | AI喜欢生成正向路径用例 | 抽查测试断言是否有效,异常分支是否覆盖 | 补充边界和异常用例,要求先写自检清单 |
| 重复代码严重 | 大任务拆分不足,AI复制粘贴式生成 | 用重复代码检测工具扫描 | 拆分任务、小步提交、提取公共方法 |
6.2 避坑技巧:别让AI代码悄悄溜进主线
第一,在PR标题前缀[AI],让reviewer一开始就知道这是AI生成的代码。我们试过,加了前缀之后,AI代码的平均review时间反而更合理,因为大家不带着“AI就是不行”的预判,也不带着“机器肯定没问题”的松懈,注意力分配更准。
第二,强制在PR描述里附上“提示词原文”。审查者能对照“你让AI做什么”和“AI做了什么”,很多问题一眼就看出是对齐问题还是实现问题。如果提示词里写了“仅ADMIN可调用”而代码里没有,那就不用多废话,直接打回。
第三,设置合入门禁:没有人工review记录,AI代码不允许合入。这个过程刚开始麻烦一些,但能压住“AI一把梭”的冲动。尤其是支付、鉴权、数据处理这类高敏感模块,尽量不让AI从零生成,而是给AI一个经过验证的正确模板,让它照着改,成功率远高于裸写。
第四,也是我最近很喜欢用的一个小技巧:让AI先生成“审查自查清单”,再生成代码。它写代码时会更关注自己列出的风险点,相当于在生成阶段就埋下了一次“软检查”,比事后打回省事得多。
最后说两句实在话。我做AI代码审查这半年,最深的体会不是“AI写代码不行”,而是“审查AI代码的重点和审查人写代码的重点不一样”。人写的代码,你会下意识怀疑逻辑;AI写的代码,你会下意识相信格式。格式越漂亮,越要记得打开清单,一条条核对需求和边界。现在我的工作台上还贴着那四条清单,每次review AI代码都过一遍,打回率从最初的七八成降到了两三成。如果你也在审AI代码,不妨从第一条开始试试:别相信注释,去看分支。