☰
AI代码审查实战:老炮如何从AI的20个问题中筛出15个?
2026/9/29 18:05:38 网站建设 项目流程

带着AI去做代码审查,扫一个2022年落地的Java老项目,AI一口气挑出来20个坑,站在旁边的老炮工程师一条一条过,最后只签收15个。剩下5个不是AI说错了,而是它不懂"这个项目为什么长成这样"。这个场景这两年越来越常见:AI代码审查确实能把人从逐行读代码里解放出来,但真正决定审查质量的,仍然是人。

这次实战用的项目是一个典型的2022年Spring Boot工程,JDK8,Maven多模块,代码风格带着那个年代特有的味道:Controller厚、Service薄、日期用Date和LocalDateTime混着来、异常处理靠try-catch吞。写这篇东西,主要是想记录一下AI代码审查的完整流程:AI怎么用、结果怎么筛、老炮的判断逻辑是什么,给同样打算拿AI审老代码的人一份可以直接抄的作业。

1. 项目整体设计与思路拆解:AI审查老代码,到底选什么姿势

1.1 为什么直接让AI扫全量代码不靠谱

很多人一上来就把整个项目压缩包丢给AI,让它"全面审查",结果要么被上下文长度卡住,要么AI只看了一个大概就开始编。老项目动辄几万行代码,模型窗口根本装不下,AI为了回答你,会产生幻觉,把根本不存在的行号报给你,或者把A类的问题安到B类头上。我这次实际跑下来,直接投喂整个模块的效果极差,输出里充斥着"这里建议使用Stream优化""那里建议引入Optional"之类的通用话术,真正有价值的并发和资源泄漏问题反而被稀释了。

正确的用法是把它当成"结对审查的初级工程师":一次给一个模块,给足上下文,让它在一个有限范围内充分发挥。AI不需要看完全部代码才能发现问题,它更需要的是"这一段代码在什么场景下被调用、依赖了哪些对象、有没有并发访问"这些精准信息。给足这些,它给出的判断质量会有质的提升。

1.2 三种落地姿势对比:全量投喂、模块切片、静态工具加AI复核

我后来把AI代码审查的常见姿势整理成了三种,各有适用场景:

方式优点缺点适用场景
全量投喂操作最省事,丢进去就跑上下文溢出,幻觉率高,问题流于表面几百行的小工具类、单文件
模块切片投喂上下文可控,幻觉率低,能审出跨方法问题需要提前拆代码、写上下文说明老项目审查的主力方式
静态工具扫描加AI复核自动扫描覆盖全,AI做解释和排序工具本身噪音大,仍需人工过滤成熟团队、长期维护的代码库

老项目最适合第二种。先把一个包或者一个核心类拿出来,连同周边调用关系告诉AI,让它在这个范围里做深入检查,再把各个模块的输出统一汇总。这样做的好处是:AI每一次的分析都是完整的,不会因为上下文被截断而漏掉关键链路。

1.3 老炮复核是过滤层,不是审核层

AI擅长的是模式识别:空指针、并发问题、资源泄漏这些有固定套路的,它一眼就能看穿,甚至比很多人快得多。但AI没有业务背景,不知道这个接口一个月调用几次,不知道这段代码明年就会被重写,也不知道团队为什么在命名上坚持某种"不标准"的风格。老炮的工作不是把20个问题重新审一遍,而是用"线上会不会出故障、现在要不要动、改了会不会引入新问题"这三个维度做过滤。

用一句话概括:AI给的是可能性,老炮给的是决策。AI负责把所有可疑的点全部挖出来,哪怕挖多了也没关系;老炮负责判断哪些真正值得进入修复计划,哪些只需要记录归档,哪些干脆划掉。两者配合,审查效率和准确率都比单靠人肉逐行读高得多。

2. 核心细节解析:AI挑出的20个坑,逐一过一遍老炮的验收逻辑

2.1 被认可的15个坑:哪类问题是AI真正抓得准的

把20个坑摊开看,会发现一个规律:老炮签收的15个,全部是"模式和套路明确、一旦触发就是真实故障或显著劣化"的问题。这种问题AI一抓一个准,因为它们大量出现在训练数据里,特征非常标准化。我按当时的审查清单整理如下:

编号问题位置(示例)AI给出的结论风险等级老炮认可理由
1UserService.getUserById返回null未判空,调用链存在NPE高线上接口会因为一条脏数据直接500,必须提前兜底
2OrderServiceImpl.listByPage循环内逐条查数据库,典型的N+1查询高列表接口数据量过百以后会明显变慢,属于性能债
3DateUtilsSimpleDateFormat定义成static字段且被多线程共享高偶发性时间解析错乱,是最难排查的那类并发问题
4ReportGenerator.export在for循环里用加号拼接大字符串中循环量不大时不致命,但数据量上来会频繁GC
5FileLoader.loadInputStream没有用try-with-resources高文件句柄泄漏,开发环境看不出,压测或长期运行就暴露
6Goods.equals重写了equals但没重写hashCode中放入HashSet或HashMap后去重失效,行为不可预期
7VipService.checksynchronized锁了整个方法,里面还有远程调用中锁粒度太大,高并发下吞吐直接掉一大截
8TaskExecutor用Executors.newFixedThreadPool创建线程池中无界队列,任务积压到一定程度就会OOM
9PayService.pay事务方法内部用this调用,@Transactional失效高数据不一致是所有支付类系统的红线
10RemoteClient所有HTTP接口没有设置连接和读取超时高依赖方一旦变慢,线程池集体阻塞,可能引发雪崩
11PriceUtil.compareBigDecimal用equals比较大小中1.0和1.00不相等,金额比较会给出错误结果
12CacheManager用HashMap做多线程读写的缓存中并发扩容可能形成链表环,属于JVM里的著名事故
13ExceptionAspect捕获异常后只打印e.getMessage()中没有堆栈等于没打日志,线上问题只能靠猜
14OrderController几百行业务逻辑写在Controller里中代码腐化的起点,后续测试根本无从下笔
15StatusConstants状态值到处硬编码魔法数低改一个状态要全局搜索,确实容易出事

这15个里没有一个需要AI懂业务。它不需要知道订单是什么、VIP是什么,光是"代码出现了什么结构"就足够判断了。这也是AI代码审查最可靠的部分:结构化缺陷。这类问题用静态工具也能扫出一部分,但AI的厉害之处在于它能把"跨方法的调用关系"串起来,例如第9条事务自调用,单纯看类文件很难发现,AI把代理机制和调用链一结合,问题就浮出来了。

2.2 AI报的另外5个"坑":为什么老炮不认

再来看看被否掉的5个。这几个更微妙——从纯代码角度看,AI说得不算错,但老炮基于项目实际把它划掉了。我把当时的争议列一下:

第一,AI认为"类名和包名不符合最佳实践,需要按DDD重构"。项目是一个内部管理系统,包名用controller、service、dao、entity这套经典分层已经跑了两年,团队没人觉得它阻碍开发。此时做DDD重构,动的是全工程,风险远大于收益。AI看的是"理论上的整洁",老炮看的是"动这一刀要流多少血"。

第二,AI建议"两层if-else应该用策略模式替换"。它还贴心地给出了三个类的示范代码。但那段逻辑就两个分支,而且业务上几乎不可能再加第三种。强行上策略模式,等于用一个复杂的框架解决一个简单的问题,后面来维护的人大概率会骂人。设计模式是用来消除重复和分支爆炸的,不是用来给代码上装饰的。

第三,AI要求"所有public方法补齐Javadoc"。实际方法名和参数名已经表达得很清楚,比如getOrderAmountByUserId,看一眼就知道是什么。AI把可读性和写注释混为一谈,老炮最烦这种为注释而注释的建议。注释应该解释"为什么",而不是复述"做了什么"。

第四,AI认为"某查询没有加索引,建议加索引"。那条SQL对应的表只有几千行数据,查询频率一天几百次,加索引后的收益约等于零,还白占存储空间、增加插入开销。AI在训练数据里见过太多"慢查询加索引"的案例,但它不知道这张表的量级,所以给出的方案属于典型的过度优化。

第五,AI提出"代码里出现了Date和SimpleDateFormat,要求全部迁移到java.time"。项目是JDK8,大部分时间操作已经用了LocalDateTime,剩下的Date出现在对接第三方老接口和Excel导出的历史代码里。为了现代化去动稳定代码,业务价值为零,回归风险却真实存在。这属于"正确但不做"的典型。

这5个不是AI蠢,而是它没有成本概念、没有历史包袱概念、也没有"这个项目的下一任维护人是谁"的概念。AI只能比较"理论最优"和"当前代码",老炮比较的是"改了会怎样"和"不改会怎样"。

2.3 老炮筛选问题时的三把尺子

真要说老炮凭什么只认15个,核心是三把尺子:

  • 线上故障尺子:这个问题如果不改,会不会在特定场景下直接导致线上报错、数据错乱、系统不可用?会,就进名单。NPE、事务失效、线程池无界队列,都属于这一类。
  • 改动成本尺子:修复这个问题要改多少文件、会不会牵连历史逻辑、有没有现成测试保护?如果改动成本高于潜在损失,优先级就往后放,甚至直接划掉。
  • 未来演进尺子:这段代码是不是马上要重写?如果半年内会迁移到新系统,现在花精力去优化它就是在浪费子弹。

AI永远不会自动评估这三把尺子,因为它缺两个关键输入:当前系统的用户规模、业务生命周期。老炮的真正价值,就是把这些外部信息补进来,把AI的输出从"建议列表"变成"决策列表"。这也解释了为什么同一份AI报告,在不同团队手里会得出完全不同的整改清单。

3. 实操过程与核心环节实现:从代码库到问题清单的完整流水线

3.1 准备工作:先把老项目"切"成AI能处理的小块

实际操作中,我不会直接把整个Maven工程喂给AI。原因是上下文装不下,装下了也容易在还没看到关键类时就开始输出。我的标准流程是:

  1. git checkout 出要审查的那个发布分支,确保代码状态可复现。
  2. 先用IDEA的Inspections和SonarQube跑一遍,记录所有非风格类告警作为基线。
  3. 按模块拆代码:一个service包、一个controller包、一个工具类集合,分别打包成片段。
  4. 对每一个要审查的核心类,把类名、职责、主要依赖、调用方关系整理成一两句话,随代码一起给AI。

这一步决定了后续整个过程的质量。你给AI的信息越结构化,它返回的问题就越集中。你只是丢一堆文件让它"看",它就会东一榔头西一棒子,最后给你一堆通用废话。别嫌准备工作烦,它花费的时间最后都会从筛选环节省回来。

3.2 审查提示词:决定AI当"流水线工人"还是"理论派顾问"

AI代码审查的效果,一半靠提示词。下面是我当时用的模板,你可以直接抄:

你是一位有10年经验的Java架构师,正在参与一次代码审查。 请只审查我提供的代码片段,忽略命名风格和缩进等纯格式问题。 请聚焦以下五类问题: 1. 正确性:空指针、逻辑错误、异常处理不当 2. 并发:线程安全、锁粒度、共享可变状态 3. 性能:N+1查询、循环内耗时操作、资源泄漏 4. 可维护性:过长方法、重复代码、明显坏味道 5. 安全:SQL注入、路径遍历、敏感信息泄漏 对每个问题,请严格按以下格式输出: - 位置:引述代码原文或方法名 - 问题:一句话说清是什么问题 - 严重级别:高/中/低 - 影响场景:什么情况下会触发 - 修复建议:给出最小改动方案 如果你不确定某个建议是否适用于当前场景,请单独标注[需人工确认]。 不要为了凑数量而提出没有把握的问题。

这个模板有几个关键设计。明确要求"忽略命名风格",AI就不会把一半输出浪费在命名和缩进上;要求"不要凑数量",能压制AI为了显得勤快而硬凑的冲动;要求"引述代码原文"而不是报行号,能大幅降低编造行号的概率,因为行号模型真的记不住,代码片段它反而能准确复读;最后要求"最小改动方案",AI就不会一上来就给你重构一个全新的设计。

3.3 喂代码与采集结果:用最小改动换取最大覆盖

我的做法是一次喂一个类,最多附带一两个关联方法。比如审UserService的时候,把UserMapper接口的定义和getUserById的SQL注解一起给过去,AI就能判断"返回null后调用方判不判空"这种跨层问题。只给一个类文件,AI只能在这个类内部找事,很多真正的问题在调用链上。

一个小技巧:有对比例子的时候,效果会翻倍。我先给AI看一段没有问题的同类代码做基线,再给它看要审查的代码,让它"找出两段代码的差异"。实测下来,这个方式比直接让AI找bug误报率更低,因为AI会真的去对比差异,而不是基于模糊记忆开始自由发挥。审查老代码的时候,你甚至可以拿新写的模块当对照,让AI看看老模块缺了什么防护。

采集完每个模块的输出后,统一汇总到一张问题清单里,去掉重复项、合并同类项,就得到了最初的20个问题。这个阶段先不删任何东西,让AI把所有想法说完。宁多勿漏,过滤的事后面再说。

3.4 交叉验证与人工决断:静态工具、AI、老炮三方对质

20个问题出来之后,关键一步是和静态工具的结果做交叉比对。我当时的做法分四步:

  1. 把AI报的每条问题,在IDEA和SonarQube里重新定位,看工具能不能扫出来。
  2. 工具能扫出来且AI也报了的,属于"大概率真实",直接进入处理队列。
  3. 工具没扫出来但AI报了的,单独开会讨论。这类往往是AI真正提供增量价值的地方,比如循环内远程调用无超时、事务自调用失效,静态工具很难从语法层面识别。
  4. 工具报出来但AI没报的,通常是风格类和命名类问题,这类不需要AI,统一过滤掉。

最后老炮拿着一份汇总表逐个打勾或画叉。打勾的进修复计划,画叉的写一行理由归档。这个过程非常快,因为三把尺子已经心里有数,大多数问题几秒钟就能下判断。

当时最终形成的表格字段大概是:ID、所属模块、问题描述、AI级别、工具是否检出、老炮结论、结论理由、修复负责人、计划版本。列不列完整清单不重要,重要的是这个结构能让所有争议有据可查,而不是开会的时候凭印象吵。

4. 常见问题与排查技巧实录:AI代码审查避坑指南

4.1 五种最典型的AI误报,见过一次就会分辨了

除了这次实战,我后面又拿其他项目试了几次,发现误报类型高度可预测,基本是下面这五类:

  • 理论正确但实践无益:比如"用Optional替代所有null判断""所有集合改成不可变集合"。这类建议本身没错,但放进老项目里就是给自己找麻烦,改出一堆编译错误还要擦屁股。
  • 把设计决策当缺陷:老代码里出现全局变量、静态缓存,很多时候不是作者不懂,而是在当时的技术选型和并发模型下,这是成本最低的妥协。AI会把这些当成坏味道,但它看不到背后的约束。
  • 把风格当规范:命名规则、注释数量、方法长度阈值,每个团队有自己的尺度。AI默认的是"主流开源项目的平均数",不等于你们团队的代码标准。
  • 把单一场景当巨大风险:AI看到一个没有索引的查询,就会联想到千万级数据量。但你心里要清楚,这个表可能永远只有几百行。场景不匹配,建议就失去了意义。
  • 幻觉式报错:AI混淆了类名、方法名,甚至报出了项目里根本不存在的文件。这类问题必须靠代码定位来甄别,不能直接采信。

4.2 如何快速判断一个问题"值不值得改"

给所有踩坑的人一条实用建议:不要问AI"这是不是问题",要问自己"这个问题不修,最坏会怎样"。如果最坏情况是"线上偶发500,且可以快速重启恢复",那可以下个版本修;如果最坏情况是"资金数据错了还没告警",当天就得上线修复。

为了把这个判断做得更稳,我有意用了一个很朴素的优先级公式:

优先级 = 故障发生概率 x 故障影响范围 ÷ 修复成本

概率和影响范围主要靠拍脑袋,但这个公式能让讨论聚焦在"三个数字分别打几分"上,而不是停留在"AI说很严重"这种情绪层面。AI给出问题之后,我们一般先给前两项打分,再讨论修复成本,分数高的进迭代,分数低的直接归档。这套做法在实际运转中比"按严重级别排序"更贴近老项目的真实需要,因为有些中等级别的问题修复起来非常便宜,顺手就改了,有些高等级问题却因为改动面太大,只能排期。

4.3 给AI代码审查写提示词的几条经验

这部分是我踩了几次坑之后总结出来的,每一条都对应过实际翻车现场:

  • 先声明"忽略代码风格",否则AI一半输出都在说命名和缩进。
  • 明确要求"不要凑数量",AI为了显得勤快会硬凑,这条能减少两成噪音。
  • 给足上下文:把类和调用方一起喂进去,AI才有判断跨方法问题的基础。
  • 要求最小改动方案:AI默认倾向"重构成最优设计",你要逼它给出最小改动,它才会更贴近老项目实际。
  • 分批处理,别贪多:一次只审一个模块,效果远好于一次给十个文件。
  • 最后让AI标注"需人工确认":它不确定的时候会主动说出来,而不是硬撑着给一个错误结论。

4.4 我的个人体会:AI是副驾,方向盘从来都在人手里

最后聊点个人感受。我见过两种极端:一种是把AI输出当成天条,逐条整改,结果项目被搞成四不像,改完还说不清收益在哪;另一种是看了几眼AI报告就扔进回收站,觉得全是形式废话。我认为正确的是第三条路:让AI做大量低成本的初筛,把那些一眼就能判断的机械问题处理掉,把剩余的高价值问题留给有经验的人做决策。

老炮的15个对AI的20个,差的5个并不是AI无能,而是它看不到业务的生命周期。AI代码审查真正的价值,不在于抓住每一个bug,而在于把那些你早就习以为常的坏味道重新摆到台面上,逼你在"改"与"不改"之间做一次认真选择。有些问题你天天看,早就免疫了,AI以一个新人的视角重新指出来,你才意识到"原来这里一直埋着雷"。

这个工作流我现在已经沉淀成了固定套路:先静态工具扫基线,再AI模块化初筛,然后老炮拿三把尺子逐条过滤,最后顺手把过滤理由写进清单里,给后面的维护者留个上下文。这套流程跑下来,AI可能还是给出20个问题,但最终落到修复计划里的每一个,都是经得起推敲的。如果你也在做类似的事情,我给的最朴素建议是:永远让AI做充分的输出,永远由人类做克制的决策。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询