尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

AI代码审查实战:老炮如何从AI的20个问题中筛出15个?

AI代码审查实战:老炮如何从AI的20个问题中筛出15个? 带着AI去做代码审查扫一个2022年落地的Java老项目AI一口气挑出来20个坑站在旁边的老炮工程师一条一条过最后只签收15个。剩下5个不是AI说错了而是它不懂这个项目为什么长成这样。这个场景这两年越来越常见AI代码审查确实能把人从逐行读代码里解放出来但真正决定审查质量的仍然是人。这次实战用的项目是一个典型的2022年Spring Boot工程JDK8Maven多模块代码风格带着那个年代特有的味道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循环内逐条查数据库典型的N1查询高列表接口数据量过百以后会明显变慢属于性能债3DateUtilsSimpleDateFormat定义成static字段且被多线程共享高偶发性时间解析错乱是最难排查的那类并发问题4ReportGenerator.export在for循环里用加号拼接大字符串中循环量不大时不致命但数据量上来会频繁GC5FileLoader.loadInputStream没有用try-with-resources高文件句柄泄漏开发环境看不出压测或长期运行就暴露6Goods.equals重写了equals但没重写hashCode中放入HashSet或HashMap后去重失效行为不可预期7VipService.checksynchronized锁了整个方法里面还有远程调用中锁粒度太大高并发下吞吐直接掉一大截8TaskExecutor用Executors.newFixedThreadPool创建线程池中无界队列任务积压到一定程度就会OOM9PayService.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。原因是上下文装不下装下了也容易在还没看到关键类时就开始输出。我的标准流程是git checkout 出要审查的那个发布分支确保代码状态可复现。先用IDEA的Inspections和SonarQube跑一遍记录所有非风格类告警作为基线。按模块拆代码一个service包、一个controller包、一个工具类集合分别打包成片段。对每一个要审查的核心类把类名、职责、主要依赖、调用方关系整理成一两句话随代码一起给AI。这一步决定了后续整个过程的质量。你给AI的信息越结构化它返回的问题就越集中。你只是丢一堆文件让它看它就会东一榔头西一棒子最后给你一堆通用废话。别嫌准备工作烦它花费的时间最后都会从筛选环节省回来。3.2 审查提示词决定AI当流水线工人还是理论派顾问AI代码审查的效果一半靠提示词。下面是我当时用的模板你可以直接抄你是一位有10年经验的Java架构师正在参与一次代码审查。 请只审查我提供的代码片段忽略命名风格和缩进等纯格式问题。 请聚焦以下五类问题 1. 正确性空指针、逻辑错误、异常处理不当 2. 并发线程安全、锁粒度、共享可变状态 3. 性能N1查询、循环内耗时操作、资源泄漏 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个问题出来之后关键一步是和静态工具的结果做交叉比对。我当时的做法分四步把AI报的每条问题在IDEA和SonarQube里重新定位看工具能不能扫出来。工具能扫出来且AI也报了的属于大概率真实直接进入处理队列。工具没扫出来但AI报了的单独开会讨论。这类往往是AI真正提供增量价值的地方比如循环内远程调用无超时、事务自调用失效静态工具很难从语法层面识别。工具报出来但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做充分的输出永远由人类做克制的决策。
返回列表