接手一个2022年就停更的Java老项目时,我的第一反应不是直接抡起键盘重构,而是先把整个代码库完整过一遍。这个项目用的是Java 8 + Spring Boot 2.x,七八万行代码堆在那里,缺注释、缺测试、部分模块连编译顺序都要靠猜。我这次的做法是先用AI做了一轮全量代码审查,再拿自己踩坑多年的经验去复核。AI一共给我标出20个问题,我最后只认了其中15个,剩下5个不是错,而是优先级不够、改了反而添乱。
这篇文章就把整个实战过程完整讲一遍:AI代码审查工具怎么选、老项目的坑到底长什么样、AI的结果怎么人工复核、以及“老炮只认15个”背后的判断标准是什么。如果你手上也有一个历史包袱很重的Java项目,这篇文章应该能帮你少走不少弯路。
1. 老项目为什么值得先做一轮AI扫描
1.1 老项目的典型画像:Java 8、Spring Boot 2.x、手写SQL
先说清楚这个项目长什么样。它不是一个濒临报废的玩具项目,而是一个还在线上跑、但维护团队已经换了好几拨的业务系统。技术栈停在Java 8和Spring Boot 2.x,数据库是MySQL,缓存用的Redis,没有微服务拆分,属于典型的“大单体”结构。代码风格也不统一:有人用Lombok,有人手写getter/setter;有人用MyBatis-Plus,有人直接在XML里写超长SQL;异常处理有的抛有的吞,日志打印有的带敏感字段有的啥都不打。
这种项目的共同痛点是:上线时间越长,代码里埋的雷就越多。你不是不知道有雷,而是不知道雷在哪、有多少、什么时候炸。人肉review一遍七八万行代码,既不现实也没必要,因为很多代码路径你可能一整年都碰不到。这时候AI的作用就体现出来了——它能不眠不休地把全量代码扫一遍,从空指针隐患到并发问题,从资源泄漏到事务失效,快速给出一张风险地图。
1.2 为什么挑AI来当“第一轮质检员”
我的思路很简单:让AI干“体力活”,我来干“决策活”。AI代码审查最擅长的是大面积扫描和模式识别,它能在几分钟内把几百个文件过完,把可疑点标出来。但AI不懂你的业务,不知道哪段代码是核心交易链路,不知道哪个接口虽然写得丑但稳定跑了三年没出过事。这些判断必须靠人来补。
所以我给AI的角色定位是“第一轮质检员”,不是“最终裁判”。它负责把所有可疑点摊开,我负责逐一复核,决定哪些要修、哪些不修、哪些要立刻修。这种分工的好处是效率极高:过去团队花两周做的代码走查,现在一个下午就能完成首轮排查,剩下的时间主要花在“判断”而不是“寻找”上。
2. AI代码审查工具怎么选:我实测后的三个档位
2.1 静态扫描、AI对话、PR机器人,三类工具各有侧重
市面上做AI代码审查的工具不少,但用下来大致分三类。第一类是传统静态分析工具的AI增强版,代表是SonarQube(配合AI插件)、Qodana,这类工具规则引擎成熟、覆盖面广,适合做全量扫描。第二类是纯AI对话式,比如把代码喂给GPT-4、Claude这类大模型,用提示词要求它从架构师视角审代码,灵活度高,但效率和稳定性取决于你怎么提问。第三类是PR机器人类,比如CodeRabbit、CodiumAI,它们会在Pull Request上自动发表审查意见,适合日常增量审查。
我的建议是组合着用,而不是迷信某一个。全量摸底用SonarQube这类静态扫描工具,重点模块的精读用AI对话,日常PR卡点用机器人。下面是三类工具在实战中的表现对比:
| 工具类型 | 代表 | 适合场景 | 优点 | 明显短板 |
|---|---|---|---|---|
| 静态扫描AI增强 | SonarQube、Qodana | 全量代码摸底、历史债务盘点 | 规则全、可配置、能出报告 | 误报多、风格类检查占比高 |
| 纯AI对话 | GPT-4、Claude、通义灵码 | 重点模块逐行精读、疑难杂症分析 | 理解上下文能力强、能追问 | 每轮有长度限制、结果不稳定 |
| PR审查机器人 | CodeRabbit、CodiumAI | 开发阶段的增量审查 | 接入Git工作流、可以配置规则 | 老项目首次接入噪音很大 |
2.2 老项目实际使用的组合配置:扫描为主、对话为辅
我这次的实际配置是“SonarQube 全量扫描 + GPT-4 重点模块精读 + 人工复核”。SonarQube先跑一遍,把所有代码味道和Bug类问题拉出来,导出CSV;然后我根据线上请求量和最近半年事故记录,挑出交易、支付、对账三个核心模块,把关键类逐个喂给AI做精读;最后拿着两份结果,结合自己的经验一张张过。
这里有个细节值得单独说:老项目配置SonarQube时,一定要把规则集调成针对Java 8的版本,并且关闭大量纯风格类的检查。默认规则集里有一堆“建议使用var”“建议优化lambda”之类的新语法偏好,对老项目毫无意义,只会让报告充满噪音。我实际操作时把规则集中在五个维度:正确性、并发、性能、安全、异常处理。这样扫出来的问题才真正值得你看。
3. 一次完整审查的落地流程:从静态扫描到按业务排序
3.1 跑通全量扫描,关键是把噪音降到最低
如果你也想复现这个流程,第一步是把SonarQube跑通。这里我强调一句:老项目第一次扫描的结果一定很难看,Technical Debt动辄几百个小时,Bug类问题几十个。别慌,也别试图一下全清。你要做的是先把报告导出来,按文件路径排序,然后做三件事:第一,过滤掉测试代码和生成代码;第二,把所有“严重”和“阻断”级别的问题捞出来;第三,按模块归属分组,对应到各自主程手里。
第一次扫描我印象很深,SonarQube报了大概200多个问题,但过滤掉测试代码后剩下不到一半。再按严重级别筛一遍,真正值得看的就三四十个。这个“层层过滤”的过程非常关键,否则你会在无关紧要的风格问题上浪费大量时间。
3.2 重点模块交给AI精读,提示词决定产出质量
SonarQube扫完,我再把核心交易链路的十几个类喂给GPT-4。这里有个经验:提示词写得越具体,结果越能用。我用的提示词大致是这样的:
你是一名有十年经验的Java架构师。以下是xxx项目交易模块的核心类,请重点检查: 1. 线程安全性(共享变量、并发集合、线程池) 2. 事务边界(自调用、传播行为、锁与事务的顺序) 3. 空指针与资源泄漏 4. 性能隐患(循环查库、大列表内存、不合理的锁粒度) 5. 异常处理是否合理 请按“严重级别 / 问题描述 / 建议修复 / 是否紧急”四列输出,不要输出无关建议。加上“上线五年、日活十万、MySQL单库、支付链路”这些背景信息之后,AI的输出质量明显提升。它会把“这段代码在高并发下可能出现问题”这类话换成更具体的分析,比如指出某个事务方法内部调用了RPC但锁已经提前释放,这类问题靠肉眼扫很难发现。
3.3 一份可执行的清单:从AI输出到人工定级
最后我会把所有问题汇总成一张表,列分明:问题描述、所在文件、问题级别、AI建议、人工复核结论、是否修复。这里的“问题级别”不是AI给的,而是我按“线上事故概率 × 影响范围 × 修复成本”自己重新排的。
一个典型的例子:AI把“日志中打印了完整的用户手机号”标为中等严重,但在我这里它直接排到P0,因为这是合规风险;反观AI给的“某方法建议用StringBuilder替代字符串拼接”,我直接标为“暂不处理”,因为该方法的调用量一天不到一百次,优化了也没人感觉得到。AI负责发现,人负责优先级,这就是整个流程的核心。
4. 20个坑的完整清单:AI挑出来的,老炮只认15个
4.1 先看老炮认账的15个:每一个都可能成为线上事故
这一节是整个项目审查的核心结论。下面这15个坑,是AI标出且我愿意盖章确认的,每一个都有可能在某个深夜把线上环境搞挂。我尽量写得具体一些,你可以在自己的项目里逐条对照。
| 序号 | 问题类型 | 风险描述 | 修复建议 |
|---|---|---|---|
| 1 | 线程安全 | SimpleDateFormat被声明为static并多线程共用 | 使用DateTimeFormatter(线程安全)或线程局部变量 |
| 2 | 性能 | for循环内逐条执行count查询,N+1问题严重 | 改用批量查询或SQL联表聚合 |
| 3 | 一致性 | 订单状态更新无幂等控制,重复回调会重复入账 | 引入唯一索引或分布式锁做二次校验 |
| 4 | 正确性 | 两个Integer用==比较,-128到127之外直接判断失败 | 使用Objects.equals()或者拆箱后比较 |
| 5 | 事务 | 大事务包住了所有远程调用和纯查询逻辑 | 缩小事务边界,远程调用移到事务外 |
| 6 | 并发 | Executors.newFixedThreadPool()定义线程池,未自定义拒绝策略 | 手动new ThreadPoolExecutor并设置饱和策略 |
| 7 | 精度 | 退款金额用double计算,存在精度误差风险 | 金额一律用BigDecimal并设置小数点策略 |
| 8 | 空指针 | 对象从Map取出后直接调用方法,无空值保护 | 统一做空判断或用Optional |
| 9 | 并发 | HashMap在多个线程间共享且存在写操作 | 改用ConcurrentHashMap |
| 10 | 资源 | 查询数据库的ResultSet和Connection未在finally中关闭 | 使用try-with-resources或在finally关闭 |
| 11 | 事务 | @Transactional方法内部调用本类方法,事务失效 | 通过代理对象调用,或拆分到另一个Bean |
| 12 | 安全 | 日志直接打印用户对象或请求体,包含手机号、地址 | 日志脱敏,只打印必要ID |
| 13 | 分布式 | 分布式锁key的过期时间固定为10秒,无续期机制 | 使用Redisson看门狗或设置合理的超时与续期 |
| 14 | 性能 | 内存中一次性load大量数据做distinct,高峰期直接OOM | 在SQL层面完成去重和分页 |
| 15 | 可维护性 | 魔法值散落各处,改一个配置需要全局搜索 | 抽取常量或配置项集中管理 |
我重点展开几个容易踩坑的。第1个SimpleDateFormat真的算上古经典了,代码里一个static实例被几十个线程共用,平时看着没事,热点活动一到就报错。第4个Integer用==比较也很有意思,AI报出来的时候团队里还有人质疑,说“明明能跑啊”,那是因为数值恰好落在缓存区间,一旦超出范围就原形毕露。第11个事务自调用是老项目的重灾区,很多人以为加了@Transactional就万事大吉,实际上同类内部调用根本不会进代理,坑得无声无息。
4.2 再看老炮不认的5个:不是不对,是改了更亏
那5个我为什么不认?不是AI说错了,而是它没考虑修改成本、团队能力和业务优先级。我给它们一个统一的归类叫“低性价比建议”。
第一个是“建议把所有字符串拼接改成StringBuilder”。这个建议本身没错,但在老项目里,很多字符串拼接是发生在日志输出或一次性配置加载上,调用频率极低,改成StringBuilder之后代码可读性反而差。第二个是“建议用Optional.ofNullable替代所有可能为空的返回值”。我不反对Optional,但老项目里大量代码还在用@Nullable和if判空,强行全量改造成Optional,代码风格撕裂,团队学习成本也不低。
第三个是“捕获异常后log.error,建议重新throw”。这个要看业务场景。有些模块的异常本来就是预期内的业务分支,比如“对账无差异”就是好事,捕获并记录日志是正确的,重新抛出去反而让上层报一堆没意义的错误。第四个是“建议把所有字段和方法加final”。改造成本不小,收益几乎为零,属于强类型洁癖,老项目没必要。第五个是“建议抽取公共工具类来消除重复代码”。看着合理,但两个相似方法之间其实有微妙差异,强行抽成一个方法后,参数加了三四个,调用方反而更混乱。
这5个判断标准说白了就一条:改动风险对新老代码一致吗?如果改动本身会引入回归风险,而业务收益又不明确,那就不值得动。老项目稳定压倒一切。
5. 为什么“只认15个”:AI审查的局限性到底在哪
5.1 AI误报和“伪建议”的三个根源
复盘完这20个坑,我想认真聊聊AI代码审查的局限性。为什么老炮会砍掉四分之一?我归结为三个原因。第一是缺业务上下文。AI不知道这段代码对应什么业务场景,它看到“长事务”就报警,但可能这个接口本身就是低频后台任务,事务长一点无所谓。第二是缺线上证据。AI不知道这个接口的实际调用量、响应时间、异常频率,它只能按代码静态特征做推断。第三是缺团队技术约束。团队是Java 8起家,你让AI提出“用switch表达式”这类建议,等于让团队为一个无关紧要的改动去升级基础技能栈。
这三个根源决定了AI的审查结论只能作为“线索”,不能作为“结论”。我见过一些团队把AI的审查报告直接转成TodoList,全员埋头按清单改代码,结果改完上线反而出问题,就是这个原因。AI可以帮你看得更全,但它永远替代不了你对业务、成本和风险的判断。
5.2 老炮复核时的判断框架:概率、影响面、修复成本
我复核时会用一套自己的判断框架,四个问题轮流套:问题触发概率高不高?一旦触发影响面多大?修复成本是否可控?当前业务压力下是否有更值得做的事?第1个和第2个决定了问题要不要处理,第3个和第4个决定了什么时候处理。
举个例子,AI报了一个“商品详情接口每次都在数据库里查一次库存”。这个问题的触发概率接近100%,影响面是数据库压力,修复成本改一行SQL就能解决,那肯定立刻处理。另一个问题“某管理后台导出方法可能OOM”,触发概率只有月底一次,且最多卡住管理员自己,修复成本却涉及改造查询逻辑,那就排到月底前再处理。AI给的所有问题都要过这一套框架,最后留下来的才是你真正要修的。
6. 日常如何用好AI审查:把体检变成习惯
6.1 审查前置:Diff阶段就让AI介入,而不是上线前补救
这次全量审查做完后,我更想强调的是日常习惯。代码审查不应该是一年一次的运动,而是每次提交代码时的流程动作。现在的PR机器人类工具已经做得很好了,提交Pull Request时自动跑一遍,把问题评论区直接挂在代码行上。对老项目来说,这一步的收益尤其明显:新改动不再给烂代码堆里添新债,存量债务逐步消化。
我的做法是设置“新增代码零容忍”策略:AI在Review阶段提出的一切正确性问题,必须在合并前修复或明确解释理由。这样运行半年后,你会发现新代码的质量明显好于旧代码,技术债的增速得到了控制。这一步做起来不难,难的是坚持。
6.2 从15个坑反向生成团队的审查Checklist
AI挑出的这15个坑,除了当时修掉之外,我还做了一件事:把它们变成团队自己的Code Review Checklist。比如“事务方法内部是否有RPC调用”“常量是否还在代码里写死”“日志是否打印了敏感字段”,这些直接作为PR模板里的勾选项。
这个转化过程非常有效。AI虽然不会一直盯着你,但一份沉淀下来的Checklist会成为团队共同的技术记忆。尤其是新同事入职时,与其让他读几百页代码规范,不如让他先看这份由真实事故沉淀出来的清单,理解“为什么这样写会出问题”。从AI审查结果到团队知识资产,这一步才是把一次审查的战果固化下来的关键。
6.3 给AI喂“老炮语料”,让它越来越懂你的项目
还有一个进阶技巧,我觉得值得分享一下:把这次审查的结论作为语料喂回给AI,让它后续的审查风格更贴近团队需求。具体做法是,把15个已确认的坑的描述、修复前后的代码示例、以及我们为什么否决另外5个建议的理由,整理成一份文档,在后续和AI协作时,把这份文档连同提示词一起提供给它。
实测下来,AI给出的建议明显更“懂事”,不再提那些低性价比的风格类修改,而是集中火力在并发、事务、空指针这些真正致命的点上。这个过程相当于给AI做了一次团队定制化训练,你喂给它的经验越多,它输出的结果越能用。
7. 实战中遇到的三个典型问题和排查实录
7.1 误报太多怎么办:把规则集从“全部”改成“按业务圈定”
第一个常见问题是AI报告里一堆无关痛痒的误报。我的处理方式是分两步:静态扫描工具层面,关闭所有纯风格类规则,只保留正确性、并发、安全、性能四类;对话式AI层面,在提示词里明确加一句“忽略代码风格、命名规范、注释缺失类问题,只关注可能导致线上故障的问题”。这样过滤下来,报告的有效率从不到三成提升到了八五成左右。别看只是小调整,对实际干活的人来说体验天差地别。
7.2 核心模块代码太长,AI分析断章取义怎么办
第二个问题是核心类代码太长,AI对话窗口装不下,分片喂又容易丢失上下文。我的办法是让AI先读“骨架”,再读“血肉”:先把类的方法签名列表扔给它,让它判断哪些方法是核心路径;再按方法逐一精读。这样既控制了每一轮的输入长度,又保证了分析不断档。如果某个方法超过200行,我还会再拆成逻辑块逐段分析,然后让它汇总最终结论。
7.3 团队对AI审查结果抵触怎么办:拿事故案例说话
最后一个问题和工具无关,而是“人”的问题。很多团队会觉得“AI凭什么对我的代码指手画脚”。我处理这个矛盾的做法是搞一次“事故复盘对照”:从线上当时发生过故障的代码里抽出一个典型案例,演示如果当时AI在场,能在代码评审阶段就发现这个问题,省下多少排查时间。一旦大家意识到这是一道“安全网”而不是“监控摄像头”,抵触心理就好多了。
8. 最后想聊的一点大实话
如果你问我这次实战最大的收获是什么,我会说:AI代码审查最大的价值不是替你做决定,而是帮你把原本需要几周的人肉排查压缩成一个下午,让经验用在刀刃上。
我自己的体会是,老炮真正值钱的地方不在于比AI多发现二十个问题,而在于当它把二十个问题甩到你面前时,你知道哪十五个值得改、哪五个不碰为妙。所谓“只认15个”,不是盲目自信,而是一种经过大量线上事故和半夜响应练出来的取舍能力。
最后顺手沉淀一个小建议:每次修完一个坑,都把这个坑的触发原因、修复方案、如果拖到最后会出什么事记成一条两三行的笔记,喂回给你日常用的AI对话工具。坚持一年,你会发现自己团队的代码库越来越干净,AI的审查报告也越来越精准。到那时候,AI就不再是那个爱小题大做的实习生,而是真正懂你们项目的老搭档了。