不再抗拒AI写代码-Java后端工程师的AI提效工作流
不再抗拒 AI 写代码:一名 Java 后端工程师的 AI 提效工作流

一篇不站队的文章。既不说"AI 让你十倍速",也不说"AI 写的代码全是垃圾"。
全文约 8000 字。读完你会拿到:一组可以自己复核的行业数据、一套针对 Java 后端的 AI 工作流、7 个高频坑的具体堵法、一份 60 行以内的
AGENTS.md模板,以及一套不靠"感觉变快了"的度量方法。所有引用的论文、报告与数据都在文末列了出处,可以点进去核对。
目录
- 0. 一次预发事故,让我关掉了 Copilot
- 1. 先把证据摆上桌:AI 到底快了多少
- 2. 为什么 Java 后端是"重灾区"
- 3. 我的工作流:七个工位,一条红线
- 4. Java 后端的七个高频坑
- 5. 第二曲线:用 Java 给 AI 造壳
- 6. 怎么证明自己真的变快了
- 7. 三个月落地路线
- 8. 结尾:抗拒的从来不是 AI
- 附录 A:可直接复制的 AGENTS.md 模板
- 附录 B:代码审查提示词模板
- 附录 C:引用清单
- 附录 D:发布配置
0. 一次预发事故,让我关掉了 Copilot
事情很小,后果很典型。
一个"取消订单"的接口要加退款前置校验。我让 Copilot 帮我把原来写在 cancelOrder 里的几段逻辑抽出来复用,它做得很漂亮:新建了一个 private void doCancel(...),把 @Transactional 挪到了这个方法上,编译一次过,单测全绿。
预发环境跑完一轮回归,运营发现有一批订单状态变成了"已取消",但优惠券没退、库存没回滚。
原因不用猜:@Transactional 标在 private 方法上,再加上是 this.doCancel() 的自调用,Spring 的代理根本没进场。事务注解成了一个装饰品。单测之所以全绿,是因为我 mock 掉了 mapper,压根没碰真实事务。
那天下午我做的事,是把 Copilot 插件关掉,而且关了三个月。
现在回头看,我当时拒绝的其实不是 AI。我拒绝的是一种没有验收标准的加速——它让我写得更快,但没有让我更确定。车跑得快从来不是问题,刹车失灵才是。
转折发生在那年冬天的一次代码迁移。一个跑了七年的老模块要从 javax.* 迁到 jakarta.*,两百多个文件,全是机械改动:改包名、改 import、改配置、改测试里的 mock。这种活人干起来最痛苦,因为不需要思考,但不能出错。
我抱着"反正要手工改"的心态试了一次:把迁移规则写成一份清单,让它按文件批次改,每批改完先跑编译和 ArchUnit 规则,失败了就把错误贴回去让它自己修。三天半干完,人工复核了两轮,漏了 4 个文件。
从那天起我就不抗拒了。因为我终于搞清楚了它擅长什么:边界清楚、验收便宜、错了能立刻发现的任务。而我之前让它干的活——业务语义、事务边界、并发正确性——恰好三条全不满足。
1. 先把证据摆上桌:AI 到底快了多少
关于"AI 编程到底有没有提效",你在网上能看到两个完全相反的数字:快 55.8% 和 慢 19%。这两个数字都是真的,来自两组严谨的实验。它们不矛盾,它们描述的是两种不同的工作。
1.1 同一件事,两个答案
答案一:快 55.8%。 GitHub Next 和微软首席经济学家办公室的 Peng、Kalliamvakou、Cihon、Demirer 在 2023 年做了一次随机对照试验(RCT):招募 95 名开发者,任务是"用 JavaScript 尽快实现一个 HTTP Server",随机分成有 Copilot 和没有 Copilot 两组。结果是实验组平均 71.17 分钟完成,对照组 160.89 分钟,提速 55.8%,p 值 0.0017,95% 置信区间 [21%, 89%]。论文自己也承认:这是一个标准化的小任务,而且没有考察代码质量。(arXiv:2302.06590)
答案二:慢 19%。 METR 在 2025 年 7 月发布了另一场 RCT:16 名资深开源开发者,在自己熟悉的、平均超过 100 万行的成熟仓库里,完成 246 个真实 issue,每次任务随机决定"允许"或"禁止"使用 AI 工具,主要用 Cursor Pro 加 Claude 3.5/3.7 Sonnet。开发者事前预测 AI 能省 24% 的时间,做完之后仍然认为省了 20%,而实际计时是慢了 19%(置信区间 +2% 到 +39%)。(METR, 2025)
值得注意的是 METR 在 2026 年 2 月的自我修正:他们用更新一代工具做了第二轮实验,原始估计变成了"老参与者省 18%、新招募的省 4%",但所有置信区间都跨零,而且 30% 到 50% 的开发者拒绝在"没有 AI"的条件下接任务——选择偏差严重到他们自己认为这批数据不足以作为证据。(METR, 2026-02)
这三组数字放在一起,结论其实很清楚:
AI 不是"打字加速器",是"认知负载的再分配器"。 它把成本从"写"转移到"判断",收益的正负取决于你把判断成本压得多低。
写 HTTP Server 为什么快 55.8%?因为验收标准写在题面上:跑起来、返回 200、任务结束。写成熟开源仓库里的真实 issue 为什么慢 19%?因为验收标准藏在五年积累的隐式约定里,AI 给出的每一行都需要你用领域知识去核对,而核对比重写更贵。
1.2 中间地带:Google 内部的迁移项目
最能说明"什么条件下有用"的,是 Google 自己发表的一篇内部研究。他们对多处代码库做 LLM 驱动的代码迁移(比如大规模的框架升级),结果是:落地的 CL 中约 80% 的代码修改完全由 AI 生成,工程师自报端到端耗时下降约 50%。同一篇论文里还提到,通用补全的接受率是 38%,但这 38% 的接受覆盖了 67% 的代码字符。(arXiv:2501.06972)
这个场景和 METR 的实验几乎是反面:迁移任务的验收标准极其明确(编得过、测试过、行为不变),改动高度重复,而且验证可以自动化。这恰好是我前面说的三个条件全中。
1.3 大样本调查:采用率在涨,信任度在跌
Stack Overflow 2025 年开发者调查(全球数万名开发者)给出了一个反直觉的剪刀差:
- 84% 的开发者正在使用或计划使用 AI 工具,比 2024 年的 76% 又涨了;
- 但只有 29% 的人相信 AI 输出是准确的,比 2024 年的 40% 掉了 11 个百分点;
- 46% 明确表示不信任,只有 3% 表示"高度信任";
- 66% 的人说,自己花在修"看起来对、其实不对"的 AI 代码上的时间变多了;
- 45% 的人把"AI 给的答案几乎对、但就是不完全对"列为最大的挫败来源。
这个数字特别值得后端工程师警醒。"几乎对"在 Java 里是最危险的失败模式:语法正确、类型正确、编译通过、单测通过,但事务没生效、并发不安全、索引没命中。它不会报错,它只会在某个凌晨两点报警。
1.4 交付链路的账:吞吐涨了,稳定性掉了
代码写得快,不等于交付得快。Google Cloud 的 DORA 团队连续两年在盯这件事。
2024 年的报告给出了一个让很多人不舒服的估计:AI 采纳率每提升 25%,交付吞吐下降约 1.5%,交付稳定性下降约 7.2%。(DORA 2024 报告) DORA 的解释不是"AI 写的代码差",而是:AI 让同样时间里能产出的代码变多了,变更批量(batch size)随之变大,而大批量从来都是更慢、更容易出事的。
2025 年的报告(约 5000 名技术人员样本)修正了一半结论:AI 采纳对交付吞吐已经转为正向影响,但仍然与交付不稳定性上升相关;90% 的开发者日常使用 AI,80% 认可个人生产力提升,而只有 4% 高度信任 AI 的输出。(DORA 2025 报告要点转引)
一句话:AI 是放大器。 它放大高效团队的交付能力,也放大低效团队的流程漏洞和技术债。DORA 原话是"AI 是一种放大器",这句话我认为是整个 2025 年最该被贴在工位上的判断。
1.5 质量账:不是更脏,是更胖
GitClear 分析了 2020—2024 年间 2.11 亿行代码变更,看到了几个结构性变化:复制粘贴代码占全部变更的比例从 8.3% 升到 12.3%,第一次超过了"移动/重构"类变更;重构类改动从 2021 年的 25% 跌到不足 10%;重复代码块的数量是 AI 普及前的 8 倍左右。(GitClear 2025 AI Code Quality 报告)
更细的一篇 2026 年的生产环境研究(针对某大型组织内的 C++ 代码)发现了另一件事:AI 生成代码的构建失败率中位数约为人类代码的 1.3 倍,但 revert 率反而更低;同时它的计算开销相对基线是 1.31 倍、内存是 1.36 倍,而人类代码分别是 1.25 倍和 1.25 倍。(arXiv:2608.06640)
翻译成人话:AI 写的代码不是更容易被撤销,而是更"胖"——它倾向于把逻辑摊开写,少用已有抽象,多用显式局部计算。在 Java 里这意味着更多的对象创建、更长的调用链、更松的循环。单次请求看不出来,QPS 上去了就开始收账。
1.6 把问题换掉
所以我现在不问"AI 有没有用",我问三个更具体的问题:
| 变量 | 有利区间 | 不利区间 |
|---|---|---|
| 可验证性 | 编译/单测/静态检查能自动判定 | 只有人读完代码才能判断 |
| 上下文可得性 | 规则、约定、样例能写进上下文 | 依赖五年积累的团队默契 |
| 任务粒度 | 一次一个可验收的小单元 | 一次一个跨七层的功能 |
三个变量都在有利区间,你去用;有一个不在,你就得为它补工具;两个以上不在,老老实实自己写。
这三条,就是我整套工作流的设计约束。
2. 为什么 Java 后端是"重灾区"
我做过前端、也写过 Python 脚本,可以负责任地说:AI 在 Java 后端项目里的"翻车率"明显更高。不是模型不行,是这门语言和这套生态的特点把错误藏得更深。
第一,隐式契约太多,错误都在运行时的语义层。
Spring 的事务是 AOP 代理,缓存是代理,权限是拦截器,异步是线程池,序列化是 Jackson 的注解。这些机制共同的特点是:你写的代码和实际执行的代码不是同一段代码。AI 看到的是一段文本,它不知道这段文本会被谁包一层、在哪个线程里跑、事务传播行为是什么。所以它能写出"语法上完美、语义上错误"的东西——这是最贵的一类错误。
第二,上下文跨度太大,一次改动的面积是七层。
一个接口改动,可能要动 Controller、Request/Response DTO、Service、Mapper 接口、Mapper XML、SQL、配置、数据库迁移脚本、单测。这些文件加起来轻松超过模型的舒适上下文。上下文一超,模型就开始"猜"——猜你的包名、猜你的字段、猜你的工具类。猜错的部分它不会告诉你,它会自信地编一个。
第三,编译期类型安全制造了假安全感。
Java 是静态类型,编译通过给人一种"基本对了"的错觉。但真正决定线上行为的东西——事务边界、线程安全、索引、幂等、超时——没有一个能被编译器检查。在 Python 里,一次运行就报错的地方;在 Java 里,可能要等到大促流量进来才报错。
第四,验证成本高,把收益摊平了。
起一个 Spring 上下文十几秒,跑集成测试要 Testcontainers 拉容器,一个 end-to-end 用例几十秒起。AI 生成 200 行只要 8 秒,你验证这 200 行要 20 分钟。净收益 = 生成节省的时间 − 验证增加的时间,而后一项在 Java 里天然偏高。
这四条合起来,得出一个很反直觉的结论:
不要用 AI 写"模块",要用 AI 写"变更"。 模块是七层跨度 + 大量隐式契约;变更是一个可验收的小单元 + 明确边界。
3. 我的工作流:七个工位,一条红线
下面这套东西我在两个团队里跑过,也删掉了很多花架子。留下来的七个工位,每个都对应上面四个结构性问题的某一个。
工位 0:先决定什么不交给它
这一步九成的人会跳过,但它决定了后面所有步骤的上限。
我的分流表长这样:
| 类别 | 具体任务 | 交给谁 |
|---|---|---|
| 放手交给 AI | DTO/VO 转换、样板 CRUD、参数校验注解、日志包装、异常映射、单测骨架、SQL 初稿、正则、迁移脚本、脚本工具、文档与注释 | AI 写,人扫一眼 |
| 人机共写 | 业务分支编排、接口契约设计、缓存策略、分页与排序、报表口径 | AI 出方案,人定稿 |
| 不给 AI 写 | 资金与账务计算、幂等与去重、并发原语、权限判定、DDL 与索引变更、加解密、审计埋点、限流熔断参数 | 人写,AI 只做 review |
红线只有一条:错了要一周才能回滚的东西,不交给 AI 写初稿。
这条红线的依据不是"AI 不可靠",而是"错误代价不对称"。DTO 转换写错了,单元测试立刻红;资金计算写错了,你要面对对账差异、客服工单和财务同事。同样是 10% 的错误率,代价差三个数量级。
工位 1:把隐性知识写成 AI 能读的文件
这是投入产出比最高的一步,也是最容易被误解的一步。
很多人以为 AGENTS.md(或 CLAUDE.md、.cursor/rules)是"教 AI 懂我的项目"。不是。它的真正价值是把团队的隐性约定变成显式规则,让 review 有依据、让新人能上手、让 AI 的输出可以被机械校验。
我的模板控制在 60 行以内(完整版见附录 A),核心就五块:
- 技术栈与版本红线:Spring Boot 3.2 / JDK 21 / MyBatis-Plus,
jakarta.*而不是javax.*。这一条能挡掉迁移项目里最常见的一类低级错误。 - 分层与依赖方向:controller 不写业务、service 不返回 Entity、mapper 不被 controller 直接依赖。
- 事务与异步规则:
@Transactional只标 public 方法、跨服务调用不放事务内、异步必须在事务提交后触发。 - 异常与日志规范:统一
BizException(code, msg)、禁止空 catch、日志脱敏。 - 测试要求:新增 public 方法必须有单测、集成测试用 Testcontainers、不许连开发库。
第 5 条最重要。规则不是写给 AI 看的,是写给你的 CI 看的。 一条规则如果没有对应的检查,它就只是愿望。
工位 2:任务切片
同一个需求,两种问法,结果差一个数量级。
坏问法:
帮我写一个订单取消功能。
这句话里有 12 个未定义项:状态机在哪、幂等键是什么、优惠券怎么退、库存怎么回滚、要不要发消息、日志怎么打、失败怎么补偿……模型会替你猜 12 次,每次猜错都是隐形的。
好问法:
在
OrderService新增cancel(String orderNo, String reason),只改OrderService、OrderMapper、OrderMapper.xml三个文件。约束:
- 幂等,同一
orderNo重复调用返回首次结果;- 仅
PAID、WAIT_SHIP两个状态可取消,其余抛BizException;- 取消成功后回滚优惠券与库存,两处都在同一事务内;
- 不新增表,状态流转复用
order_status_log;- 异常路径要有单测。
先给我改动清单和风险点,不要直接写代码。
差别在哪:
- 文件白名单:把"可能改 20 个文件"收敛成"只能改 3 个",幻觉改动直接消失一半。
- 约束条数控制在 5—7 条:超过 10 条,模型开始丢中间那几条,而且丢得不声不响。
- 状态枚举写死:不给它"合理推断"的空间。
- 最后一句"先给清单":把一次性输出 200 行,变成一次 20 行的对齐。
我现在写提示词有个土办法:先把这段需求用中文写成一封给同事的说明信,再删掉所有客套话。 剩下的一般就是好提示词。
工位 3:计划先行,只审 20 行
让 AI 先输出一份 impact list,人只审这一段。我的模板是五段:
1. 受影响文件(新增/修改/删除,逐个列)
2. 方法级改动(签名 + 一句话说明)
3. 数据与 SQL 变更(表、字段、索引、是否需迁移脚本)
4. 兼容性风险(对外接口、序列化、老数据、并发)
5. 回滚方案(关开关 / 回滚脚本 / 灰度范围)
经验值:审 20 行计划的成本,大约是审 200 行 diff 的十分之一。 而且计划阶段发现的问题,修改成本几乎为零——这时候还没有代码,没有分支,没有 CI 排队。
计划里我最看第 4 段。AI 极其擅长漏掉"兼容性",因为它默认世界是干净的:它不会想到线上还有 300 万条 status 是老枚举值,不会想到有个下游系统在读你的 JSON 字段名。
工位 4:测试先行,把信任变成可执行的
这是整套工作流里唯一能"规模化"的信任来源。
顺序是反的:先让 AI 写测试,你审测试,再让它写实现。
为什么?因为审测试比审实现便宜得多。测试的意图是显式的:输入是什么、期望是什么、边界在哪。你花五分钟能看出"这个用例根本没覆盖取消失败的分支",但你花二十分钟也未必看得出实现里的空指针藏在第 47 行。
具体做法:
- 提示词里明确:“不要 mock 被测类之外的依赖;一个用例只断言一个行为;异常路径和边界值必须覆盖;用
@ParameterizedTest覆盖状态机。” - 集成测试用 Testcontainers,不要连开发库。否则 AI 会"为了让测试过"去改你的数据。
- CI 上加 JaCoCo 的增量覆盖率门禁(新增代码行覆盖率不低于 70%),而不是全量覆盖率。全量覆盖率是可以刷的,增量覆盖率刷不动。
一段能直接用的骨架:
@SpringBootTest
@Testcontainers
class OrderCancelIT {
@Container
static final MySQLContainer<?> MYSQL = new MySQLContainer<>("mysql:8.0")
.withDatabaseName("demo");
@DynamicPropertySource
static void props(DynamicPropertyRegistry r) {
r.add("spring.datasource.url", MYSQL::getJdbcUrl);
r.add("spring.datasource.username", MYSQL::getUsername);
r.add("spring.datasource.password", MYSQL::getPassword);
}
@Autowired OrderService orderService;
@Test
@DisplayName("重复取消:第二次调用返回首次结果,不重复退券")
void cancel_isIdempotent() {
var first = orderService.cancel("NO-1", "用户取消");
var second = orderService.cancel("NO-1", "用户取消");
assertThat(second.couponRolledBack()).isEqualTo(first.couponRolledBack());
assertThat(second.stockRolledBack()).isEqualTo(first.stockRolledBack());
}
@ParameterizedTest
@EnumSource(value = OrderStatus.class, names = {"PAID", "WAIT_SHIP"}, mode = EnumSource.Mode.EXCLUDE)
void cancel_rejectsIllegalStatus(OrderStatus status) {
var no = orderFixture.createWithStatus(status); // 按参数造出对应状态的数据
assertThatThrownBy(() -> orderService.cancel(no, "x"))
.isInstanceOf(BizException.class)
.hasMessageContaining("状态");
}
}
这两个用例的共同点是:它们会真的红。 幂等写错、状态判断漏了,测试立刻失败。这比任何"AI 很可靠"的承诺都实在。
工位 5:双通道 review
生成代码的那个模型,不适合审自己写的代码——它有确认偏误,倾向于认为自己的方案是对的。我的做法是两条通道:
机器通道(一定要有)
- SpotBugs / ErrorProne:空指针、资源泄漏、并发误用;
- ArchUnit:把架构规则变成测试。比如"controller 不得直接依赖 mapper":
@ArchTest
static final ArchRule controller_must_not_touch_mapper =
noClasses().that().resideInAPackage("..controller..")
.should().dependOnClassesThat().resideInAPackage("..mapper..");
- SonarQube 质量门:只看新增代码的重复率、复杂度、覆盖率,不看历史债。
- SQL 变更走
EXPLAIN:任何新增查询必须在 PR 里贴执行计划。这一条能挡住八成 N+1 和索引失效。
模型通道
换一个模型,给它固定清单,要求"只报问题,不改代码"。清单见附录 B,核心 12 项:事务边界与自调用、N+1、索引命中、空指针、集合并发、线程池与上下文传递、异常吞掉、日志敏感信息、幂等、超时与重试、序列化兼容、资源关闭。
这里有个细节:要求它"只报问题、不改代码",输出质量会明显变好。一旦允许它改,它就会倾向于给你一版"看起来更整洁"的重写,而重写会掩盖它没看懂的地方。
工位 6:把 review 结论写回规则
每次 review 抓到的高频问题,变成 AGENTS.md 里的一行。这是唯一让这套流程"越用越省"的机制。
比如那次事故,最后沉淀成两行:
- @Transactional 只能标在 public 方法;同类内部调用必须通过注入的代理或拆到另一个 Bean。
- 涉及事务的方法必须有至少一个集成测试(Testcontainers),禁止只用 mock 单测。
规则库要控制在 60 行以内。超过这个长度,模型会开始随机忽略中间条目,而且你没法从输出看出来它忽略了哪条。每月做一次清理,删掉已经内化成习惯的、留下还在反复犯的。
4. Java 后端的七个高频坑
下面七条,全部来自我实际遇到或 review 到的问题。每条给"AI 会怎么写 / 为什么错 / 怎么堵"。
坑 1:@Transactional 的自调用与 private 方法
AI 常写:
@Service
public class OrderService {
public void cancel(String no) {
doCancel(no); // 自调用,代理不生效
}
@Transactional
private void doCancel(String no) { ... }
}
为什么错: Spring 的事务基于代理。外部调用进代理,代理开事务;this.doCancel() 走的是原始对象,代理压根不知道。private 方法更不可能被代理。
怎么堵: 规则里写死"自调用必须通过注入自身代理(@Lazy 自注入)或拆到另一个 Bean";再加一个集成测试,断言异常时数据确实回滚。只用 mock 的单测永远发现不了这个问题,这是它最阴的地方。
坑 2:异步丢上下文
AI 常写:
@Async
public void sendNotice(Long orderId) {
var user = RequestContextHolder.getRequestAttributes(); // null
}
为什么错: ThreadLocal 不跨线程。@Async 换了线程,RequestContextHolder、SecurityContext、MDC 里的 traceId 全丢。
怎么堵: 统一配一个 TaskDecorator 做上下文透传,并把 traceId 放进 MDC:
@Bean
ThreadPoolTaskExecutor bizExecutor() {
var e = new ThreadPoolTaskExecutor();
e.setTaskDecorator(r -> {
var ctx = MDC.getCopyOfContextMap();
var attrs = RequestContextHolder.getRequestAttributes();
return () -> {
if (ctx != null) MDC.setContextMap(ctx);
RequestContextHolder.setRequestAttributes(attrs);
try { r.run(); } finally {
MDC.clear();
RequestContextHolder.resetRequestAttributes();
}
};
});
return e;
}
坑 3:N+1 与"索引幻觉"
AI 常写:
for (Order o : orders) {
o.setItems(itemMapper.selectByOrderId(o.getId())); // 循环里查库
}
为什么错: 它只看单个方法的正确性,不看调用次数。1000 条订单就是 1001 次查询。更隐蔽的是它会在 where 里加一个没建索引的字段,然后告诉你"已优化"。
怎么堵: 硬规则——任何新增或修改的查询,PR 里必须贴 EXPLAIN 结果;批量场景必须用 IN 或 join 一次取回。这条规则配合 CI 的 SQL 检查,能挡掉大部分性能事故。
坑 4:javax 与 jakarta 的版本幻觉
AI 常写: import javax.servlet.http.HttpServletRequest;
为什么错: 它的训练数据里 Spring Boot 2.x 占比极高。Boot 3 之后全部迁到 jakarta.*,但模型不会主动问你用哪个版本。
怎么堵: 在 AGENTS.md 第一行写死版本,再加一条编译期拦截:自定义一个禁止 javax.servlet 的 Checkstyle 规则,或直接在 CI 里 grep。能编译通过的错,要用规则挡住,不要指望模型记住。
坑 5:Lombok 与 MapStruct 的"二次生成"
AI 常写: 在已经有 @Data 的类上手写 getter/setter;在已经用 MapStruct 做转换的项目里,手写一个 30 行的 BeanUtils.copyProperties 转换方法。
为什么错: 它看不到注解处理器生成的东西,只看到源码里"没有"。于是它补了一份,两份逻辑开始漂移,半年后没人知道该改哪个。
怎么堵: 规则里写"字段访问器由 Lombok 生成,禁止手写;对象转换一律走 MapStruct 的 XxxConverter,禁止 BeanUtils.copyProperties"。再加一条 ArchUnit 规则禁止 BeanUtils。
坑 6:JDK 21 虚拟线程的 pinning
AI 常写:
synchronized (lock) {
var r = httpClient.send(req, BodyHandlers.ofString()); // 阻塞 IO 在 synchronized 里
}
为什么错: JDK 21 的虚拟线程在 synchronized 块里遇到阻塞 IO 会 pin 住载体线程,吞吐直接塌。这是一个"能编译、能跑、压测才炸"的典型问题——正好落在 AI 最不敏感的区间。
怎么堵: 规则写"虚拟线程路径上的临界区使用 ReentrantLock,禁止 synchronized 包裹阻塞调用";压测阶段专门跑一组虚拟线程场景。
坑 7:假重试
AI 常写:
for (int i = 0; i < 3; i++) {
try { return doPay(order); } catch (Exception e) { /* 重试 */ }
}
为什么错: 没有退避、没有幂等键、没有区分可重试异常、没有熔断。对支付这种接口,这是在制造重复扣款。
怎么堵: 重试只能通过统一的 RetryTemplate 或 Resilience4j 配置,必须带指数退避和幂等键;并且支付类接口默认不允许自动重试,只允许"查询后补偿"。这条我是吃过亏才写进规则的。
5. 第二曲线:用 Java 给 AI 造壳
前面讲的都是"用 AI 写代码"。但作为 Java 后端,还有一件更有价值的事:用 Java 写调用 AI 的系统。
这件事的门槛比想象中低,而它的工程量比想象中大。因为一次模型调用,本质上就是一次不可靠的远程调用:会超时、会限流、会返回格式错误、会突然变贵。而超时、重试、降级、限流、幂等、可观测——这些恰好是后端工程师干了十年的活。
一个最小骨架(Spring AI 风格,LangChain4j 同理):
@Configuration
class AiConfig {
@Bean
ChatClient chatClient(ChatModel model) {
return ChatClient.builder(model)
.defaultSystem("你是订单客服助手,只回答订单相关问题,不编造政策。")
.build();
}
}
@Service
class OrderAssistant {
private final ChatClient client;
@TimeLimiter(name = "llm") // 超时
@CircuitBreaker(name = "llm", fallbackMethod = "fallback") // 熔断降级
public String answer(String question) {
return client.prompt()
.user(question)
.call()
.content();
}
String fallback(String q, Throwable t) {
return "当前咨询人数较多,请稍后再试或转人工。";
}
}
三条工程红线,我认为比代码本身重要:
- 不要在业务事务里调模型。 一次调用可能 10 秒,把数据库连接和行锁挂在模型延迟上,是自找的。用 outbox 模式:事务内只写消息,事务外异步调模型。
- 输出必须结构化校验。 让模型返回 JSON,然后用 Jackson + Bean Validation 校验;校验不过就重试一次,再不过走降级。不要让模型输出直接拼 SQL、直接当命令执行。
- 把 token 成本接进监控。 每次调用打点:模型、输入/输出 token、耗时、是否降级。没有这组指标,你会在某个月账单出来时才知道发生了什么。
再补一句:prompt 要版本化。 把 prompt 当作配置而不是字符串常量,进 Git、能回滚、能灰度。上线后效果变了,你要能回答"是模型变了还是 prompt 变了"。
6. 怎么证明自己真的变快了
"感觉变快了"是最不可靠的度量。METR 的实验里,开发者坚信自己快了 20%,实际慢了 19%——这个偏差不是新手的错觉,是资深开发者的错觉。
所以我给自己定了一份四周记录表,只记五个字段:
| 字段 | 怎么记 | 看什么 |
|---|---|---|
| 任务 | 一句话 | 任务类型分布 |
| AI 介入点 | 生成 / 审查 / 测试 / 没介入 | 哪类任务真的用了 |
| 实际耗时 | 从开始到 PR 提交 | 趋势,不看单值 |
| 返工次数 | PR 被要求修改的轮次 | 大于 2 轮说明验收没做好 |
| 合并后 14 天内改动行数 | git log 统计 | 近似 churn,高就是"没想清楚" |
团队层面看三个指标,比看代码量有用得多:
- DORA 四指标:部署频率、变更前置时间、变更失败率、恢复时间。AI 提效如果只体现在"代码行数",这四项不会动。
- PR 中位变更规模(行数)。这个指标如果随着 AI 采纳率一起上升,你就在往 DORA 说的坑里走。
- 两周内返工率(写完后 14 天内被修改的行占比)。这是"AI 生成、人没想清楚"的直接证据。
一个反直觉的提醒:LOC/人日 上升不是好消息。 GitClear 的数据已经说明,代码量增长很可能来自复制粘贴而不是复用。要看的是"durable change"——那些半年后还在、还在被别人调用的代码。
7. 三个月落地路线
如果让我从零再来一次,我会这样排:
第 1 周:只做两件事。
写一份 60 行以内的 AGENTS.md;把编辑器的"自动接受补全"关掉,改成逐条确认。第二件事的心理作用大于实际作用——它强迫你在每一行落地之前想一秒。
第 2—4 周:只在一个模块用,只用两个工位。
选一个你熟的模块(熟到能一眼看出错),只用工位 4(测试先行)和工位 5(双通道 review)。开始记那张五列表。四周后你会知道自己真实的符号是正还是负。
第 2 个月:加计划先行和文件白名单。
把"先给清单再写代码"变成肌肉记忆;把 ArchUnit 和增量覆盖率门禁接进 CI。这个月你会第一次感受到"规则比提示词有用"。
第 3 个月:团队化。
每个人交一条自己踩过的坑进规则库;每月一次 30 分钟复盘,只讨论"这个月 review 抓到的重复问题是什么"。规则库每月删一批——留下还在犯的,删掉已经内化的。
三个月后你应该拿到三样东西:一份会生长的规则库、一组真实的度量数据、以及一套你自己判断"这个活该不该交给 AI"的直觉。第三样最值钱。
8. 结尾:抗拒的从来不是 AI
我关掉 Copilot 的那三个月,表面上是在抗拒一个新工具。现在我知道,我抗拒的是失控感:代码变快了,但我对它为什么正确、为什么安全、出问题怎么回滚,一无所知。
这也解释了为什么"AI 编程"的争论总是吵不出结果。说它好用的人,在拿它做迁移、样板、测试、文档——验收便宜,错了立刻知道;说它没用的人,在拿它写业务核心、并发、事务——验收昂贵,错了要等线上报警。他们说的其实不是同一个东西。
AI 把"写"的边际成本压到了接近零,同时把"判断"的价格抬到了最高。这意味着后端工程师的价值重心在移动:从"能写出这段代码",移动到"能判断这段代码不该这么写"。
所以我现在的工作方式是:让 AI 负责它擅长的部分,我负责它永远不会擅长的部分——定义什么叫做对了。
工具会一直换。这条分工不会。
附录 A:可直接复制的 AGENTS.md 模板
# 项目约定(AGENTS.md)
## 技术栈(不要猜测版本)
- Spring Boot 3.2.x,JDK 21,MyBatis-Plus 3.5.x,MySQL 8.0,Redis 7
- 一律使用 jakarta.*,禁止出现 javax.servlet / javax.validation
- 禁止引入新依赖;确需引入时在 PR 描述中说明理由与替代方案
## 分层与依赖方向
- controller → service → mapper,单向依赖
- controller 不写业务逻辑,不直接依赖 mapper
- service 不返回 Entity,统一返回 DTO;Entity 只在 service 与 mapper 之间流转
- 字段访问器由 Lombok 生成,禁止手写 getter/setter
- 对象转换一律使用 MapStruct 的 XxxConverter,禁止 BeanUtils.copyProperties
## 事务与异步
- @Transactional 只能标在 public 方法上
- 同类内部调用必须通过注入的代理,或拆到另一个 Bean;禁止 this.xxx() 直接调用本类带 @Transactional 的方法
- 跨服务/RPC 调用不得放在事务内
- 异步任务必须在事务提交后触发(TransactionSynchronization.afterCommit 或 outbox)
- 线程池统一使用 bizExecutor,禁止 Executors.newXxx 裸创建
- 虚拟线程路径上的临界区使用 ReentrantLock,禁止 synchronized 包裹阻塞调用
## 异常与日志
- 业务异常统一抛 BizException(code, msg),不抛 RuntimeException 传递业务语义
- 禁止 catch (Exception e) {} 空吞;捕获后必须记录或转换
- log.info 只打关键节点;手机号/身份证/银行卡必须脱敏(MaskUtil)
- 每个请求链路必须带 traceId(MDC)
## 数据与 SQL
- 新增或修改的查询,PR 中必须贴 EXPLAIN 结果
- 批量查询用 IN 或 join,禁止在循环中查库
- 分页必须有上限;禁止无 limit 的 selectList 直接返回前端
- DDL 与索引变更不在此规则范围内,由 DBA 评审
## 重试与幂等
- 重试统一走 RetryTemplate / Resilience4j,必须带指数退避与幂等键
- 支付、扣款、退款类接口禁止自动重试,只允许查询后补偿
- 对外写接口必须幂等;幂等键落库并加唯一约束
## 测试
- 新增 public 方法必须有单测
- 涉及事务、并发、SQL 的改动必须有集成测试(Testcontainers),禁止连接开发库
- 新增代码行覆盖率不低于 70%
附录 B:代码审查提示词模板
你是资深 Java 后端评审人。只做审查,不重写代码,不输出完整实现。
输入:下面的 diff(或文件)。
按以下 12 项逐条检查,每条给出:命中/未命中 + 文件:行号 + 一句话说明 + 风险等级(高/中/低)。
1. 事务边界:@Transactional 是否标在 private/自调用方法上;跨服务调用是否在事务内
2. N+1:是否存在循环内查库;批量场景是否合并为 IN/join
3. 索引:新增查询的 where/order by 字段是否有索引;是否贴了 EXPLAIN
4. 空指针:可能为 null 的返回值是否直接解引用;Optional 是否被滥用为字段类型
5. 集合并发:HashMap/ArrayList 是否在并发场景被共享写
6. 线程与上下文:@Async/线程池是否丢 ThreadLocal、SecurityContext、MDC
7. 异常:是否空 catch;是否吞掉中断异常;异常是否被转成 200 返回
8. 日志:是否打印敏感信息;是否在循环里打 info 日志
9. 幂等:写接口是否有幂等键;重复提交是否会重复扣减
10. 超时与重试:外部调用是否设超时;重试是否有退避与幂等保护
11. 序列化兼容:新增/改名字段是否影响下游;枚举是否新增了未处理的值
12. 资源:流、连接、锁是否在 finally 或 try-with-resources 中释放
最后只输出一张表,不要给修改后的代码。
附录 C:引用清单
- Peng, Kalliamvakou, Cihon, Demirer. The Impact of AI on Developer Productivity: Evidence from GitHub Copilot. arXiv:2302.06590, 2023. https://arxiv.org/abs/2302.06590
- METR. Measuring the Impact of Early-2025 AI on Experienced Open-Source Developer Productivity. 2025. https://metr.org/blog/2025-07-10-early-2025-ai-experienced-os-dev-study/ (论文 arXiv:2507.09089)
- METR. We are Changing our Developer Productivity Experiment Design(2026-02 更新与自我修正). https://metr.org/blog/2026-02-24-uplift-update/
- Google. How is Google using AI for internal code migrations? arXiv:2501.06972. https://arxiv.org/abs/2501.06972
- Google Cloud DORA. 2024 Accelerate State of DevOps Report. https://martinliu.cn/blog/state-of-devops-2024/2024_final_dora_report.pdf
- Google Cloud DORA. 2025 State of AI-assisted Software Development(要点转引自腾讯云开发者社区整理). https://cloud.tencent.com/developer/article/2761337
- Stack Overflow. 2025 Developer Survey. https://survey.stackoverflow.co/2025
- Stack Overflow Blog. Developers remain willing but reluctant to use AI: The 2025 Developer Survey results are here. https://stackoverflow.blog/2025/07/29/developers-remain-willing-but-reluctant-to-use-ai-the-2025-developer-survey-results-are-here/
- GitClear. AI Copilot Code Quality 2025(2.11 亿行代码变更分析). https://gitclear-public.s3.us-west-2.amazonaws.com/GitClear-AI-Copilot-Code-Quality-2025.pdf
- Characterizing the Quality Profile of AI-Generated C++ in Production. arXiv:2608.06640. https://arxiv.org/abs/2608.06640
说明:文中所有百分比与样本量均取自上述公开来源;涉及"我的项目""我的团队"的描述为作者个人经验,不含未公开的业务数据。第 6 节的度量表是模板,不构成对任何具体收益的承诺。
附录 D:发布配置
标题备选(按点击诱因排序)
- 不再抗拒 AI 写代码:一名 Java 后端工程师的 AI 提效工作流(主标题,适用于技术社区与公众号)
- 我把 Copilot 关了三个月,然后靠一次
javax→jakarta迁移彻底改口(故事型,适合信息流) - 55.8% 快和 19% 慢都是真的:Java 后端到底该怎么用 AI 才不亏(争议型,适合问答社区)
摘要(120 字以内)
同一批 AI 工具,GitHub 的实验说快 55.8%,METR 的实验说慢 19%。差别不在模型,在任务形态。这篇按"可验证性 / 上下文 / 粒度"把任务分流,附 7 个 Java 高频坑、可复制的 AGENTS.md 与 review 提示词。
标签
#AI编程 #Java #SpringBoot #研发效能 #代码质量 #工程实践
发布节奏
技术社区首发完整版;24 小时后把七个坑拆成三条短内容导流回长文;一周后补一篇"四周实测数据"复盘,形成系列。系列化是长尾阅读量的主要来源,比单篇爆款更稳。
更多推荐
所有评论(0)