AI代码审查实战:2022年Java老项目挑出20个坑,老炮只认15个(附人机对照表)
老炮踩坑录 · A02 · AI 时代系列· 基于「企业融合评估平台」真实源码· 关键词AI 代码审查 · 误报分析 · 人机对照 · 遗留系统 欢迎阅读个人主页知守观我的专栏老炮踩坑录当前内容AI代码审查实战文章目录测试方法人机对照表AI 找对的 15 个真坑安全类4 个稳定性类7 个性能类2 个数据处理类2 个AI 找错的 5 个——这才是最有价值的部分#15 SimpleDateFormat 线程不安全——判对了对象搞错了位置#16 Transactional 放在 Controller 层——原则有错但能工作#17 JSONObject 贯穿全链路——技术上对上下文里错#18 count 线程安全问题——不存在#19 Controller return null 违反 RESTful 规范——它已经通过 response 写完了5 个误报背后的共同规律那 75% 的命中率够用吗怎么用 AI 做代码审查——我的实操建议写在最后上一篇我把一个 1600 行的 Controller 喂给 AI 做重构建议结论是方案看起来专业但一条都没敢用。有读者私信留言“如果把整个项目交给 AI 做代码审查呢它总不能全错吧”这是个好问题。于是我做了这个实测把整个项目的 Java 源码交给 AI 让它做一次完整的代码审查目标是找出 20 个坑——安全漏洞、性能隐患、设计缺陷等等都算。然后我逐条人工复核。结果AI 报了 20 个我认了 15 个否了 5 个。命中率 75%。看起来还不错但仔细看那 5 个误报你会发现 AI 犯的错误有一个共同规律——它总是技术上正确上下文里错误。这个规律比命中率本身更值得探讨。测试方法把src/main/下全部源码交给 AI提示词“对这个项目做一次代码审查找出 20 个最严重的问题按严重程度排序。安全问题 稳定性问题 性能问题 代码规范问题。”AI 输出 20 条我逐条对照源码做人工复核。判定标准真坑问题确实存在且确实有危害我同意改半对问题存在但 AI 的严重性判断标准或解决方案不对幻觉AI 报的问题在实际代码中不成立人机对照表先看完整结果再逐类展开命中率15 真 1 半对 16 条有效 / 20 条总计 80%。如果只算完全正确是 15/20 75%。看起来不错但关键不在那 15 个对的——那 15 个对的任何一个有经验的 Java 开发都能找出来。AI 的价值不在于找到了已知的问题在于在找到的问题里有多少是真的需要修改的。下面我们逐一展开分析。自查打卡搜一下你的项目grep -r “printStackTrace” src/ | wc -l数字打在评论区。我看看谁的项目最“健康”。AI 找对的 15 个真坑这 15 个我不逐条分析了——每条都是实锤直接上证据。安全类4 个#1 fastjson 1.2.37 反序列化漏洞!-- pom.xml --artifactIdfastjson/artifactIdversion1.2.37/versionfastjson 1.2.68 之前存在多个 autoType 绕过漏洞CVE-2019-17571 等攻击者可以通过构造恶意 JSON 实现远程代码执行。这是最高优先级的修复项没有之一。完整漏洞如下图#3 AES/ECB 模式// AesEncodeUtil.javapublicstaticfinalStringAES_TYPEAES/ECB/PKCS5Padding;ECB 模式对相同的明文块总是产生相同的密文块攻击者可以通过密文模式分析推断明文结构。前后端登录密码加密用的就是这个——AES/ECB/PKCS5Padding密钥还写死在前端 JS 里。应改为 AES/CBC 或 AES/GCM推荐。#4 AES 密钥硬编码// AesEncodeUtil.javaprivatestaticfinalStringAES_KEYt0b3pevklwcsd;密钥直接写在源码里任何能访问代码仓库的人都能拿到。应该通过配置中心或环境变量注入。稳定性类7 个#5 25 处 printStackTrace全项目扫出来 25 处e.printStackTrace()分布在ExcelUtil、FileDownloadUtil、AppInfoServiceImpl等多个文件。异常进了 stdout 黑洞生产环境搜日志根本搜不到。应该统一用log.error(msg, e)。#6 Executors.newFixedThreadPool 无界队列// AsyncElsServiceImpl.java, HWFileServiceImpl.java 等 3 处ExecutorServiceexecutorServiceExecutors.newFixedThreadPool(5);newFixedThreadPool内部用的是LinkedBlockingQueue无界任务堆积时队列无限增长最终 OOM。阿里 Java 开发手册第一条规约就禁止了这种写法。应改用ThreadPoolExecutor手动指定有界队列。#7 ThreadLocal 静态变量跨请求污染// AsyncService.javapublicstaticfinalThreadLocalMapString,ObjectthreadLocalnewThreadLocal();staticThreadLocal 线程池复用 上一个请求的上下文数据泄漏到下一个请求。在 Tomcat 线程池环境下用户 A 的登录数据可能出现在用户 B 的请求里。#8 Transactional try-catch 事务失效// UserInfoController.javaTransactional(rollbackForException.class)publicObjectdeleteReport(HttpServletRequestrequest)throwsException{try{userInfoService.deleteObject(enterpriseId);diaService.delete(enterpriseId);// ...}catch(Exceptione){e.printStackTrace();returnResultInfo.setResultInfo(true,500,error,删除失败,null);}}catch 把异常吞了Spring 看不到异常事务不会回滚。删了一半数据数据库里就是脏数据。这是本次审查中最隐蔽的 bug——代码看起来有Transactional看起来有rollbackFor但实际效果等于没加。#9 10 处 Transactional 缺少 rollbackFor// EnterpriseRegistServiceImpl.java, LoginServiceImpl.java 等 10 处Transactional// 没有 rollbackFor Exception.classSpring 默认只对RuntimeException回滚。如果方法内部抛了受检异常IOException等事务静默不回滚。应统一加rollbackFor Exception.class。#10 Guava Cache maximumSize50 踢人// AuthAspect.javaprivatestaticfinalCacheString,ObjectCACHESCacheBuilder.newBuilder().maximumSize(50).expireAfterWrite(30,TimeUnit.MINUTES).build();用本地缓存做 Session 恢复最大只存 50 条。第 51 个用户登录时最早的那条就被踢出去了——该用户下次请求会被判定为未登录强制踢下线。并发稍高就会出事。#11 Scheduled 裸 catch定时任务静默死亡// SchedulerTask.javaScheduled(cron0 0/3 * * * ?)privatevoidprocessReportResult(){// ... 130 行业务逻辑 ...}catch(Exceptionex){log.error(ex.getMessage(),ex);}}每 3 分钟跑一次的同步任务catch 了所有异常只打日志。如果某天外部诊断系统的接口地址变了这个任务会持续失败但没有任何告警——只有人去看日志才能发现。数据断了三个月没人知道。性能类2 个#12 synchronized 锁整个类// AsyncService.javasynchronized(HWFileServiceImpl.class){params.put(enterpriseid,enterpriseid);applyFileService.modifyFileName(params);}锁的是HWFileServiceImpl.class——所有线程、所有用户共用一把锁。任何时刻只有一个线程能执行这段代码。在高并发上传场景下这是一个串行化性能瓶颈。#13 System.out.println 代替日志// SchedulerTask.javaSystem.out.println(start (count));System.out.println(end(count));定时任务用System.out输出没有日志级别、没有时间戳、没有日志文件。生产环境排查问题时stdout 的内容早就被日志轮转清掉了。数据处理类2 个#14 catch 后 return null// EnterpriseRegistController.java 的多个导出方法try{// ... 导出逻辑 ...returnnull;}catch(Exceptione){log.error(e.getMessage(),e);returnnull;}成功返回 null失败也返回 null。前端拿到 null不知道是 “没有数据” 还是 “出错了”。应该至少返回一个带错误码的 Result 对象。AI 找错的 5 个——这才是最有价值的部分#15 SimpleDateFormat “线程不安全”——判对了对象搞错了位置AI 说SimpleDateFormat是线程不安全的项目中多处使用存在并发风险。实际情况// ReportPbServiceImpl.java 等多处SimpleDateFormatsdfnewSimpleDateFormat(yyyyMMddHHmmss);项目里每个方法都是 new 出来的局部变量根本不存在共享也就不存在线程安全问题。AI 的规则库告诉它 “SimpleDateFormat 线程不安全”但它没有去看这个变量是 static 共享的还是局部创建的。真正的浪费不是线程安全而是每个方法都 new 一个 formatter——完全可以提取成ThreadLocal或DateTimeFormatter线程安全复用。但 AI 没看到这个优化点。误报原因AI 按关键词匹配规则不看变量的作用域。#16 Transactional 放在 Controller 层——原则有错但能工作AI 说Transactional不应该出现在 Controller 层违反分层原则。应该下沉到 Service。// UserInfoController.javaTransactional(rollbackForException.class)publicObjectdeleteReport(HttpServletRequestrequest)throwsException{实际情况Spring 的Transactional是基于 AOP 代理实现的放在 Controller 层技术上完全能工作。只要方法不是被同一个类内部调用自调用事务就能正常生效。这条建议 “原则上正确”但严重性被 AI 高估了——它不是 bug只是代码组织问题。在遗留系统里为了 “分层纯洁性” 去改一个能正常运行的事务边界风险大于收益。误报原因AI 把代码规范问题和功能缺陷 混为一谈严重性判断标准有问题。#17 JSONObject 贯穿全链路——技术上对上下文里错AI 说全链路使用 JSONObject 作为方法参数丢失类型安全。应使用强类型 DTO。实际情况这条我在上一篇文章A01里详细分析过。JSONObject 不是某一个接口的偷懒而是贯穿 Controller → Service → Mapper → XML 的架构决策。只改 Controller 层反而增加转换成本要改就得改 80 个文件。在一个没有测试的项目里这种全链路改造就是赌博。AI 不知道这个项目有多少文件、有没有测试、改动一个参数类型会波及多少层。误报原因AI 只看代码形式不看改造成本和项目约束。#18 count “线程安全问题”——不存在AI 说SchedulerTask中的count字段使用count在多线程环境下存在竞态条件。// SchedulerTask.javaprivateintcount0;Scheduled(cron0 0 0 * * ?)privatevoidprocess(){System.out.println(start (count));}实际情况Spring 的Scheduled默认使用ScheduledThreadPoolExecutor核心线程数是1。也就是说同一时刻只有一个定时任务在执行根本不存在并发。count在这个场景下是安全的。AI 看到 “共享可变状态 非原子操作” 就报线程安全但它不知道 Spring 定时任务的默认调度模型是怎么的。误报原因AI 不了解框架的默认行为把理论风险当成了实际 bug。#19 Controller return null “违反 RESTful 规范”——它已经通过 response 写完了AI 说多个导出方法返回null违反 RESTful 规范应返回合适的 HTTP 状态码。AuthBackPostMapping(back/export)publicResultbackExport(HttpServletResponseresponse,RequestBodyJSONObjectparams){try{// ... 通过 response.getOutputStream() 写入 Excel 二进制流 ...elecExcelExportUtil.exportByTpl(response,1,企业清单);returnnull;// ← AI 说这里不该返回 null}catch(Exceptione){log.error(e.getMessage(),e);returnnull;}}实际情况这个方法通过HttpServletResponse直接写入了 Excel 二进制流。到return null时HTTP 响应已经提交状态码 200 Content-Type 文件内容都写完了。Spring 检测到 response 已提交不会再尝试序列化返回值。return null在这里不会造成任何问题。这是文件下载场景的常见写法不算优雅但也不是 bug。误报原因AI 不理解 HTTP 响应的生命周期只看方法签名的返回值。5 个误报背后的共同规律把 5 个误报放在一起看5 个误报100% 是同一个原因AI 只看到了代码的静态形式没有看到代码的运行时上下文。它知道SimpleDateFormat在教科书上是线程不安全的但不知道这个变量是方法内的局部变量。它知道Transactional应该放在 Service 层但不知道放在 Controller 层也能正常工作。它知道 JSONObject 不如 DTO 安全但不知道改一个参数类型要波及 80 个文件。这就是 AI 代码审查的天花板它是一个非常博学的规则匹配器但不是一个理解运行时上下文的工程师。有个细节我没完全确认AI审查的效果在不同规模的项目上差异到底有多大我测的是一个200多个文件的老项目如果是几千行的小项目误报率会不会低一些有踩过坑的兄弟评论区指点一下。那 75% 的命中率够用吗回到最初的问题AI 报了 20 个坑命中率 75%这个结果好不好我的判断作为辅助工具够用作为独立审查者不够。够用的部分AI 确实帮我快速定位了 fastjson 漏洞、Executors 无界队列、ThreadLocal 污染这些 “扫描类” 问题。这类问题的特征是模式固定、判断标准明确——要么有 CVE 编号要么违反明确的规约。AI 做这种 “规则扫描” 比人快得多。不够的部分那 5 个误报每一个都需要我花时间去验证。如果我对项目不够熟很可能就信了——然后花时间去修一个不存在的问题或者改一个能正常工作的代码。更危险的是漏报——AI 没发现的问题。这次测试里AI 就没有发现登录接口 type 参数被 JS 硬编码覆盖上篇 F02 的坑 4 讲过后台登录没设置SESSION_COMPANY_TYPE导致鉴权失败坑 6雪花算法 workerId 硬编码导致多实例 ID 重复上篇 F14 的坑这些问题不是 “代码模式” 问题而是业务逻辑 跨文件调用链的问题。AI 的审查粒度到不了这一层也是它的局限性。怎么用 AI 做代码审查——我的实操建议经过这次实测我总结了一个 “人机协作” 的审查流程第一步让 AI 做规则扫描命中率最高把代码交给 AI让它找安全漏洞CVE、过时的依赖版本、明确的规约违反阿里规约、SonarQube 规则。这类问题 AI 的准确率接近 100%。第二步AI 的设计建议全部打问号当 AI 说应该用 XXX 模式、应该重构为 XXX时先问自己三个问题和 A01 的三问法则一样有测试能证明改完之后行为不变吗我理解这段代码背后的业务决策吗改错了代价是什么第三步人工补漏报AI 扫不出来的问题是跨文件的业务逻辑错误、前后端数据结构不一致、配置和代码的隐含约定。这些只有理解业务的人才能发现。写在最后回到标题的数字20 个坑认 15 个。75% 的命中率说明 AI 做代码审查确实有用——至少比 “不审查” 强得多。但那 25% 的误报和漏报提醒我们AI 的审查报告不能直接当施工图纸用它更像是一张待验证的线索清单。每一条线索都需要一个懂业务、懂上下文、懂改造成本的人来判断这是真坑还是虚惊一场。AI 能发现代码里写了什么但只有人能判断代码意味着什么。这也是为什么标题叫老炮只认 15 个——不是老炮比 AI 聪明而是老炮知道哪些 “看起来像坑” 的东西其实不是坑。好了今天的内容都讲完了。这个系列的下一篇打算聊聊 AI 生码率进 KPI 这件事。作为一个写了 18 年代码还在一线的人我想说说当 AI 写代码的速度成了考核指标老炮的护城河到底在哪最后问一句你用AI审过代码吗它报的问题你是照单全收还是逐条复核评论区聊聊我看看有多少人敢把AI审查报告当施工图纸用。下期预告《AI 生码率进 KPI 了18 年老炮的三个保命技能》AI审代码我认了15个坑剩下5个是幻觉。但比“AI能不能审代码”更现实的问题是AI生码率已经进了KPI末位淘汰不是段子。下期讲三个老炮的保命技能拆需求、验代码、兜底线。我是老炮18年Java老兵仍在一线。关注「Java老炮踩坑录」少踩坑。

相关新闻

人生代码 · 创作手记

人生代码 · 创作手记

从讲台到讲台 1996年,我是一名教师。 教的是机械制图、金属工艺学、机械设计、焊接工艺、铆接工艺。 三年后,我转行了。1999年,成了一名程序员。 那时候没有“人生代码”这个念头。只有代码,只有bug,只有上线前夜的…

2026/10/18 4:51:35 阅读更多 →
论文查重率为何居高不下?查重机制与AI改写降重全流程指南

论文查重率为何居高不下?查重机制与AI改写降重全流程指南

每年论文季,总有人拿着查重报告来找我,开口第一句基本都是同一个问题:“我明明是自己一个字一个字写的,为什么查重率还是百分之十几二十几?”这个问题背后,藏着一个被很多人误解的事实:查重系统…

2026/10/18 4:51:35 阅读更多 →
从vibe coding到harness engineering:打造可靠的AI编程自动化工作流

从vibe coding到harness engineering:打造可靠的AI编程自动化工作流

把一段话丢给AI编程助手,让它自己写代码、自己跑测试、自己改、再跑,今天凌晨入睡前扔进去的任务,早上醒来已经在仓库里躺好一份完整的改动、一套测试报告、一段“我为什么这么改”的说明。这种感觉第一次出现的时候,我心里不是兴…

2026/10/18 4:51:35 阅读更多 →

最新新闻

TensorFlow Lite端侧推理集成指南:模型转换、性能优化与避坑实践

TensorFlow Lite端侧推理集成指南:模型转换、性能优化与避坑实践

做端侧AI这么久,TensorFlow Lite集成这个坑,我前后踩了大半年才彻底理顺。去年接手一个离线质检项目,现场环境没有稳定网络,只能在某个嵌入式设备上跑一个图像分类模型。模型在训练环境里精度很正常,一到端侧不是转不动…

2026/10/18 17:43:44 阅读更多 →
Matlab小波周期分析实操指南:解决水文气候序列节律失稳问题

Matlab小波周期分析实操指南:解决水文气候序列节律失稳问题

简介:本资源是一套面向水文、气象与地理领域科研人员及高校师生的Matlab小波周期分析实操工具包,聚焦Morlet小波时频分析方法在真实时间序列中的落地应用,解决多尺度周期识别、主振荡周期提取与结果可视化等核心问题。压缩包共20个文件&#…

2026/10/18 17:43:44 阅读更多 →
TensorFlow Lite端侧AI集成实战:从模型量化到性能调优

TensorFlow Lite端侧AI集成实战:从模型量化到性能调优

很多端侧AI功能,做出来跟跑通是两回事。模型在服务器上精度再高,到了用户手机上一跑就卡、一跑就崩,体验直接归零。我自己在集成TensorFlow Lite这件事上,前前后后折腾过不少轮,踩过的坑比文档里写的多得多。这篇就当作…

2026/10/18 17:43:44 阅读更多 →
487万条结构化图书数据集:开箱即用的推荐与NLP工程基底

487万条结构化图书数据集:开箱即用的推荐与NLP工程基底

简介:这是一份面向文本挖掘、自然语言处理与机器学习初学者及研究者的轻量级书籍数据集资源,聚焦于中文文本分析基础任务的训练与验证需求。压缩包共3个文件(1.59MB),含CSV格式的结构化书籍元数据(标题、作…

2026/10/18 17:43:44 阅读更多 →
从题目到终稿:用AI辅助工具高效完成毕业设计论文与实践报告

从题目到终稿:用AI辅助工具高效完成毕业设计论文与实践报告

毕设这东西,绝大多数人第一次接触时根本不知道从哪下手。我自己当年也是,题目定下来之后整整两周没动笔,打开文档半小时,写了删、删了写,最后屏幕上就剩个标题。后来陪好几届同学把整套流程完整跑下来,才想…

2026/10/18 17:43:44 阅读更多 →
Window 显示鼠标的坐标:用 GetCursorPos 与 SetCursorPos 做实时坐标面板

Window 显示鼠标的坐标:用 GetCursorPos 与 SetCursorPos 做实时坐标面板

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 17:42:44 阅读更多 →

日新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 0:55:02 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →