ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AI改写75%代码工作?Java工程师工作流重构实战

AI改写75%代码工作?Java工程师工作流重构实战

最近和一帮做Java的朋友聊了个话题,大家的感受高度一致:手边的AI代码助手已经从“偶尔尝尝鲜”变成了“每天工作流里离不开的一个角色”。与此同时,网上到处流传着一个说法——AI将改写75%的代码工作。第一次看到这个数字时,我心里咯噔一下,但真把自己手头的工作拆开看,才发现它说的不是“75%的人要失业”,而是“75%的代码工作方式会变”。这个转变,已经在不知不觉中完成了大半。

今天这篇东西,我不打算谈什么宏大趋势,只想从一个还在写Java、还在被线上问题折腾的工程师视角,把我看到的、踩过的、验证过的东西摊开来说。你会看到AI具体改写了哪些工作,哪些环节依然非人不可,以及现在这个节点上,一个Java工程师到底应该把精力花在哪。

1. 先别慌:“75%被改写”到底在改写什么

1.1 被改写的是任务结构,不是岗位数量

很多人一看到“改写75%代码工作”就开始焦虑,其实这个数字更应该被理解为:在日常开发中,大约有七成左右的工作内容是模式化、样板化、可预测的。这些内容恰好是大语言模型最擅长处理的领域。

你回想一下自己的一天:建实体类、写DTO、配Mapper、写Service层的CRUD、补单元测试、调SQL、写接口文档、给上个世纪的代码补注释。这些工作占了你多少时间?我粗略统计过自己过去半年的工时,这类“确定性产出”大概占了65%到70%。AI真正改写的是这部分——它把“亲手写”变成了“描述、生成、审查、修正”。

这不是岗位消失的逻辑,而是任务重分配的逻辑。就像当初IDE出现时,没人因为不用手写编译命令而失业,反而腾出了手去做更有价值的事。现在AI做的事情,本质上和IDE当年做的事情一样,只不过粒度更粗、智能程度更高。

1.2 一张表看清Java开发任务的可替代性分层

我把Java日常开发任务做了个分级,你可以对照自己的实际工作看看:

分层典型任务被AI改写的程度原因
高替代层CRUD接口、实体/Mapper/配置类、单元测试、常规SQL、注释文档70%~85%模式固定、上下文明确、有大量历史样例
中替代层业务规则实现、异常处理、三方系统对接、参数校验30%~50%需要业务理解,但AI能给出可用初稿
低替代层架构设计、性能调优、分布式方案、跨团队协调、需求抽象5%~15%依赖经验判断和复杂取舍,AI只能辅助

注意,高替代层恰恰是很多初级和中级工程师的主要工作对象。这解释了为什么有些人觉得AI是神器,有些人觉得AI是“高级复制粘贴”——关键不在于AI,而在于你被分配到的任务处于哪一层。

我在一个老项目上做过一次实验:把一个订单模块的9个接口全部用AI从零生成,从实体到Mapper XML再到Service实现,整个过程只花了一个下午。如果按我五年前的效率,至少需要两天。但让人真正冒冷汗的不是效率差,而是AI生成的代码里有三个隐含问题,如果不经过人审直接上生产,迟早出事。这个后面细说。

2. AI重构Java工作流的五个核心变化

2.1 CRUD从“手写”变成“口述”

先说最直观的变化。以前写一个新的REST接口,从Controller到Service再到Mapper,一套流程走下来,手快也要四十分钟,手慢一点再调调格式,一个小时就没了。而且写出来的东西千篇一律:入参校验、调用Service、捕获异常、返回统一包装。

现在的模式完全不同。我只需要把需求描述清楚,AI直接生成可编译的代码。举个例子,我最近在一个模拟项目X里新增一个订单创建接口,提示词大概是这样的:

项目使用Spring Boot 3.2、Java 17、MyBatis-Plus。创建一个REST接口: POST /api/orders,入参包含userId、productId、quantity、remark。 要求:校验quantity必须大于0且不大于100,userId和productId不能为空, 使用统一返回体Result<T>,异常由全局异常处理器兜底, Service层用@Transactional,创建时自动设置createdTime和orderStatus=PENDING。

AI生成出来的Controller和Service初稿,基本能直接用。我只需要做两件事:一是检查它有没有正确处理null入参,二是确认事务边界符合业务预期。整个流程从一小时缩减到十五分钟,里面至少有十分钟花在审查而不是编写上。

2.2 单元测试从“磨洋工”变成“顺手就写”

单元测试是Java工程师心里永远的痛。理论上大家都知道要写、要覆盖、要保证质量,但业务压力一来,测试永远是第一个被牺牲的。我见过太多项目,测试覆盖率长期在30%以下,甚至有的服务类根本没有测试。

AI出现后,这个局面彻底变了。以前写一个Service方法要配套四个测试用例,从mock依赖到准备数据再到断言结果,怎么也得半小时。现在只需要把方法签名和业务规则丢给AI,它就能生成一套覆盖正常流程、边界条件和异常路径的测试代码。

我自己的习惯是:写完一个方法后,让AI生成测试类,然后我补两条它漏掉的边界用例,再跑一遍确认全绿。这个流程的成本从半小时降到了五分钟。测试的意愿度提高,直接带来的是重构时的安全感——AI时代,代码变更频率会更高,没有测试兜底,谁都不敢动老代码。

2.3 代码审查从“人肉扫雷”变成“人机共检”

我以前做过一个不算严谨的统计:我经手的代码评审里,大概有六成的问题集中在空指针隐患、资源未关闭、异常被吞掉、校验缺失这几类。这几类问题有一个共同特点:它们都符合“明显可识别的模式”,而AI识别模式的能力恰恰非常强。

现在的评审流程变成了这样:代码写完先让AI过一遍,它会指出潜在的空指针、未关闭的IO流、缺少的边界校验,甚至能根据上下文提示事务问题。然后我再基于业务规则做二次审查。AI负责“扫雷”,我负责“判断”。这让我能把自己的注意力集中在那些真正需要人脑判断的地方,而不是把时间耗在逐行找低级错误上。

不过这里要泼一盆冷水:AI做代码审查存在一个致命弱点——它倾向于在“代码自洽”的范围内发现问题,它很难察觉“代码和业务预期不一致”这类问题。换句话说,代码里写错了业务规则,但只要代码内部逻辑自洽,AI大概率发现不了。所以人机共检的“人”这一环,不仅不能省,反而更关键。

2.4 遗留代码从“不敢动”变成“可对话”

Java生态里存量老旧系统实在太多,很多跑得好好的老系统,用的是十几年前的技术栈。这类代码有共同特征:没有注释、命名混乱、方法体五百行起步、牵一发动全身。以前接手这种模块,光读懂代码就要一两周,理解业务和代码之间的映射关系,更是全靠人工脑补。

AI在这里的价值是让我可以“和代码对话”。我会直接丢给AI一个老方法,让它解释这段逻辑在干什么、有哪些副作用、依赖了哪些外部状态。它给你一份图文并茂的解读,比让一个老同事从零开讲要快得多。

更重要的是,AI能辅助做迁移和重构。我最近在一个遗留项目里把一套基于自研ORM的查询迁移到MyBatis-Plus,AI帮我完成了绝大多数SQL语义的等价改写。这个活儿如果靠人来做,枯燥且易错,AI干这个再合适不过。但注意,迁移后的对比测试必须自己补,AI在语义边界上偶尔会翻车。

2.5 结对节奏:写一段,停一步,审一下

很多人刚接触AI编程时容易走两个极端。要么完全不信AI的产出,只是把它当成补全工具;要么毫无保留地信任AI,一键生成后直接往代码库里塞。这两个极端我都见过,也都翻过车。前者的问题是效率没提起来,除了自动补全,AI的价值基本没吃到;后者的麻烦更大,因为AI生成的代码表面光鲜,埋在里面的坑往往要等联调或者上线后才爆发。

我现在的节奏是“写一段,停一步,审一下”:让AI生成一小块功能代码,自己审查这一块,确认理解了每一行之后再进入下一块。这比“生成一个大文件然后整体调试”的效率高得多。因为如果一次性生成几百行,出问题时你根本不知道AI在哪个环节的假设和你不一致,排查成本反而比手写还高。

3. 实操实录:AI辅助落地一个订单超时关闭模块

3.1 场景设定与提示词设计

口说无凭,我拿一个真实的开发场景把全过程走一遍。假设现在要在一个订单系统里新增一个“订单超时自动关闭”的定时任务,业务规则是这样的:订单创建后30分钟内未支付,自动把状态改为已关闭,每次任务只处理一批,避免扫表压力过大。

传统写法我要做这些事:建一个定时任务类、写Mapper查询、写状态变更SQL、处理事务边界、考虑多实例部署会不会重复执行、补测试。整体耗时大概两到三小时。

现在我给AI的提示词是这样的:

Java 17 + Spring Boot 3.2 + MyBatis-Plus。 写一个订单超时关闭的定时任务类。 表t_order字段:id、user_id、status(PENDING=1待支付,CLOSED=5已关闭)、created_time、pay_time。 规则:每5分钟扫描一次,查出status=PENDING且created_time < 当前时间减30分钟的订单, 每次最多处理200条,把status批量更新为CLOSED,记录close_time。 要求:使用@Scheduled,分批处理避免大事务,不要在事务内做远程调用, 全部用MyBatis-Plus的Wrapper实现,不要写自定义XML。

AI生成的初稿长这样:

@Component public class OrderTimeoutTask { private final OrderMapper orderMapper; public OrderTimeoutTask(OrderMapper orderMapper) { this.orderMapper = orderMapper; } @Scheduled(cron = "0 */5 * * * ?") public void closeTimeoutOrders() { LocalDateTime deadline = LocalDateTime.now().minusMinutes(30); List<Order> timeoutOrders = orderMapper.selectList( new LambdaQueryWrapper<Order>() .eq(Order::getStatus, OrderStatus.PENDING) .lt(Order::getCreatedTime, deadline) .last("LIMIT 200") ); if (timeoutOrders.isEmpty()) { return; } for (Order order : timeoutOrders) { order.setStatus(OrderStatus.CLOSED); orderMapper.updateById(order); } } }

3.2 生成结果的四个审查点

这个初稿能跑,但直接上生产肯定会出事。我来演示一下人类审查到底要看什么。

第一个问题:多实例部署会重复执行。定时任务如果部署在两台机器上,每台机器都会扫描同一批订单,产生重复更新。虽然updateById幂等,但浪费查询资源,而且可能在日志里制造大量噪音。要么引入分布式锁,要么用任务调度平台分配执行节点。这里我选择了最简单的方案:给任务加一把基于数据库的分布式锁,或者干脆拆到一个单独的执行实例里。AI不会知道你的部署架构,这正是需要人来判断的地方。

第二个问题:循环updateById是挨个更新,200条就有200次数据库交互。可以接受,但不是最优。更好的方式是汇总ID列表,用一条批量更新SQL。MyBatis-Plus里可以用updateBatchById,或者直接用自定义SQL。我让AI改成批量更新,它给出的修正版本如下:

public void closeTimeoutOrders() { List<Order> timeoutOrders = findTimeoutOrders(); if (timeoutOrders.isEmpty()) { return; } List<Long> ids = timeoutOrders.stream().map(Order::getId).toList(); LambdaUpdateWrapper<Order> updateWrapper = new LambdaUpdateWrapper<>(); updateWrapper.in(Order::getId, ids) .set(Order::getStatus, OrderStatus.CLOSED) .set(Order::getCloseTime, LocalDateTime.now()); orderMapper.update(null, updateWrapper); }

第三个问题:事务边界。初稿在方法上加了@Transactional,这在这个场景里其实是个隐患。定时任务加事务,意味着如果批量更新中有一条失败,整个批次都会回滚。但扫描出的200条订单彼此独立,一条失败不应该影响其他199条。合理的方案是不要一个超大事务,而是把批量拆分,每个小批独立事务。AI默认的“方法级事务”在这里是错的。

第四个问题:时间边界。created_time < 当前时间减30分钟,这个条件表面正确,但仔细想想,如果支付渠道回调有延迟,用户在第29分钟发起了支付,恰好在第31分钟才回调成功,而这个定时任务已经把订单关闭了怎么办?这已经超出“代码生成”的范畴,属于业务规则设计。最终我们加了一个保护逻辑:状态为CLOSED的订单,如果后续收到支付成功回调,需要有补偿机制把它翻回已支付。这个设计AI给不了你,它只会顺着你的描述把代码写出来。

3.3 效率对比:手写、盲信AI、AI加人审

我拿同类需求做了个对照,用我们自己团队两个周末的时间做了个不那么严谨的横评:

方式从零开发耗时代码缺陷数上线后可维护性
纯手写2.5~3小时3个左右,集中在边界条件中规中矩
AI生成直接提交40分钟8个以上,其中2个属于生产级隐患较差,没人知道AI为什么这么写
AI生成+人审修正1.5小时1~2个,且可发现较好,审查过程让工程师理解全貌

盲信AI那一列最吓人。速度快,但代价全在后面。生成时省下的时间,在联调和线上故障排查阶段连本带利还回去了。而“AI加人审”虽然只比盲信多了五十分钟,但代码质量和可维护性完全不在一个档次上。

3.4 提示词迭代:把AI“调教”成你需要的样子

第一次生成的代码不满意太正常了。真正有用的技巧是“迭代式对话”,而不是反复开新会话。

比如我给AI的第一次提示词没有说明“多实例部署”,它生成的代码也就没有分布式锁。这时候我不会直接把完整答案重写一遍,而是追加一条:项目是多实例部署,定时任务可能重复执行,请给出分布式锁的引入方案。AI会把锁的逻辑补上。

再比如它生成的批量更新用了last("LIMIT 200"),但如果表里同时有多个批次积压,每轮扫描永远处理同一批。正确的做法是每次扫描要跳过已经扫描过的批次,或者用主键范围分片。这种偏业务口径的细节,我通常会在第二轮提示里补充。

给AI反馈时,最好的做法是给出“代码+解释”的组合:这里要改成什么样,为什么这么改。AI会把你的偏好记住,同一个会话里后面的生成结果会越来越贴合你的风格。

4. Java工程师需要更新的四项基本功

4.1 会提问:写清楚提示词的五个要素

如果你只能从这篇文章里带走一个技能,那是“把需求翻译成提示词”的能力。这不是什么玄学,它本质上是一种精确表达的能力,和当年写需求文档的底层能力一模一样。我现在一份合格的提示词至少要包含五个要素:

  • 技术上下文:Java版本、框架版本、ORM方案、项目结构。
  • 业务规则:状态枚举、时间边界、金额精度。
  • 约束条件:事务边界、性能要求、并发安全。
  • 输入输出示例:给一个输入样例和期望输出,AI的理解会准确得多。
  • 明确禁区:不要用XML、不要在循环里查库、不要用过时API。

举个例子,你写“帮我写一个关闭订单的定时任务”,和写“用Spring Boot 3.2 + MyBatis-Plus写一个订单超时关闭任务,扫描PENDING超过30分钟的订单,批量更新为CLOSED,每次200条,分布式部署要考虑幂等”,得到的结果完全不同。差的提示词给不了AI足够的约束,它只能在猜你的意图,猜错是很正常的。

4.2 会验证:Trust but Verify

AI生成的代码看起来太像“正确答案”了。它有完整的注释、规范的格式、合理的命名,错误因此更容易被隐藏。人对“看着很专业”的内容天然有信任倾向,这是最危险的地方。

我的验证流程是四步:第一步编译,第二步跑AI生成的单测,第三步自己补两条边界用例,第四步在本地用真实数据小范围跑一遍。这套流程下来,AI代码里大部分问题都会暴露。我见过太多同事第一步编译过了就直接提交,最后被别人审查时被问得哑口无言。

有一个被反复验证的经验:AI生成的代码里,最容易出问题的地方是“边界条件的处理”。比如page+1这种边界、空集合、null入参、超长字符串、时间为空。AI写的是“正常路径”,你需要补的是“不正常路径”。你的价值不在生成,而在验证。

4.3 会选型:方案决策的价值在上升

当代码生成变成低成本的事情,工程师的稀缺性就转移到了方案选型上。同一个需求,AI能给你三套方案:同步接口、异步消息、定时轮询。它不会也不应该替你做选择,因为它不知道你的团队规模、运维能力、流量模型和成本预算。

我在实际工作中越来越明显地感受到:能否在十分钟内判断“这个功能该用数据库事务还是消息队列”“这个查询该走缓存还是走索引”“这个模块该拆微服务还是留在单体里”,才是真正拉开差距的地方。这些判断AI给不了你,至少现在给不了。

换句话说,AI把“实现”的成本压得很低,但把“决策”的价值抬得很高。架构能力、权衡能力、对业务长期演进的理解,这些东西的回报率,在这两年里被显著放大了。

4.4 会表达:把业务翻译成可执行的规格

写代码的能力贬值,但“把模糊业务需求翻译成精确规格”的能力在升值。因为AI的输入是文本,你对业务理解的偏差会直接转化为你给AI的下游语句。业务说不清楚,你提示词写得再花哨都没用。

我现在带新人,要求他们在让AI生成代码之前,先写一小段“可执行规格”,至少包含:输入是什么、输出是什么、异常时怎么办、并发下怎么处理、性能要求是什么。写得清楚的人,用AI的效率是写不清的人的三倍以上。这不是AI的问题,是沟通抽象层的问题。

这一点在跨团队协作时体现得尤为明显。产品经理说“用户付款后订单要关闭”,这里至少有三层歧义:什么时候算付款成功?关闭是逻辑关闭还是数据删除?关闭后能不能恢复?如果你只是把这个句子原样转述给AI,那AI就会用它的“常识”帮你做决定,而AI的常识并不是你的业务规则。

5. 常见翻车现场与排查实战

5.1 幻觉API:AI推荐的库方法根本不存在

AI最经典的翻车方式,是给你一个看起来合理但实际不存在的API。我在一个项目里让AI写一个Redis缓存工具类,它直接用了一个redisTemplate.batchDelete(keys)方法,看起来后缀一致、语义合理,但实际上在项目使用的版本里根本没有这个方法。编译直接报错,新手同事第一反应是去查资料,最后发现是这个方法压根是编出来的。

排查这类问题有一条捷径:不要盲目搜索整个互联网,把报错信息原样扔回给AI,明确告诉它“这个方法在你的语境里不存在,请根据当前项目里的RedisTemplate实际API重写”。通常情况下它能自我修正。如果修正不了,就让它先列出当前版本RedisTemplate提供了哪些delete相关方法,再选择正确的方案。

5.2 老版本习惯:Java 8写法被硬塞进Java 17项目

AI的训练数据里包含了大量历史代码,这导致它在没有明确约束时,会默认使用比较保守的写法。比如在一个Java 17项目里,它可能会生成new String(request.getBytes("ISO-8859-1"), "UTF-8")这类远古编码转换代码,也可能会推荐用CountDownLatch而不是CompletableFuture。

解决这个问题,最好的办法是在提示词里直接声明技术版本,并且在审查时注意识别“看着眼熟但其实是老套写法”的代码。另一个小技巧:让AI在生成代码时“为每一处使用了Java 17特性的地方写注释说明”,这能逼它使用新写法。实测下来很管用,能显著减少代码风格和新项目脱节的问题。

5.3 性能隐患:编译通过不代表线上能扛

AI生成的代码,性能问题是最隐蔽的一类,因为它能编译、能跑、功能看起来也对,只是会在某个数据量级上突然崩给你看。最常见的是N+1查询。我让AI写一个批量查询用户订单数量的功能,它在循环里逐个查询数据库,100个用户就是100条SQL,性能可想而知。

这类问题靠人审不容易发现,因为代码分散在几层,每一层看都挺正常。我的办法是:在提示词里明确要求“不允许在循环内访问数据库或远程服务”,然后审查时再顺着数据流走一遍。如果项目里接入了链路追踪或慢SQL日志,建议顺手开起来,AI生成的代码上线后多盯几天SQL日志,问题会自己浮出来。

5.4 安全红线:SQL拼接和密钥硬编码

AI在生成SQL时,偶尔会写出${status}这种直接拼接的MyBatis写法,特别是当你让它“写得简单一点”的时候,它就把#{}换成${}了。这种代码一旦上线,等于把SQL注入的窗口给用户打开。另外,AI生成配置文件时,可能在代码里塞一个假的数据库密码或第三方密钥,如果没被发现,就会被当成真配置带进生产。

经验做法是:AI生成的代码和配置,必须做一次安全专项检查。至少检查三个点:所有SQL参数是不是都用参数绑定、有没有密钥和Token出现在代码里、有没有绕过权限校验的接口入口。我一度以为这些是最基本的东西,直到在代码里看到AI生成的硬编码密钥,才意识到这件事必须写进规范。

6. 我的几条实操体会

AI进来这两年,我自己的技能树构成发生了明显变化。写代码的比例在降低,但需求分析、方案设计、代码审查、生产排查的比例在上升。过去那个“写完功能就完事”的时代确实回不去了,每一行代码前面的思考、后面的验证,变得更加重要。

给还在Java这条路上的同行几个小建议。第一,把AI当成一个“水平忽高忽低的结对程序员”,它能在几分钟内给你一份初稿,但你不能省掉自己那十分钟的审查。第二,提示词写的不是指令,是你对业务的理解,业务理解越透彻,AI给出的产出就越靠谱。第三,趁现在多练一下“从零看清一个系统的能力”——一个人能看清整个系统的结构和瓶颈时,AI只是他手里的一个效率工具,而不会成为他职业道路上的不确定性。

最后分享一个我最近养成的小习惯:每让AI生成一段代码之后,我会要求它把这段代码里它做的关键假设列出来。这个动作帮我发现了大量隐含的业务歧义。有时候AI列出的假设,比业务需求本身写得还清楚。这大概是这个阶段最好的工作模式了。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进