我让 AI 一天把 7600 行 Java 换成 Kotlin 上了线,最值钱的却不是它快

cover

哈喽,我是飞飞。

先说个我自己干的、听着有点离谱的事。

上周有一天,我让 AI 把一个老项目里 7600 行 Java,整库重写成了 Kotlin,顺带把构建工具从 Maven 换成了 Gradle。早上十点多动的手,下午六点多收工,当天晚上走 CI 直接上了生产。

九十来个接口,行为一个没变。

这项目是我 2022 年写的记账后端,最早是 Maven、Java 8、Spring Boot 2.7。搁过去,这种换血是按「周」算的高风险体力活。多少人手里攥着这么个老项目,明知道该升级,就是不敢动。

干这事的没什么黑科技,靠的是 Claude Code 的 dynamic workflow,加上一套我逼着自己守的纪律。

但这回真正让我后劲很大的,不在「AI 写得多快」这件事上。这场迁移到底快在哪,其实挺反直觉的,根本不在打字这一下。

为什么老项目迁移,传统做法那么慢

先想个问题:把 7600 行 Java 翻成 Kotlin,到底难在哪?

逐行翻译这件事,其实不慢。难的是翻完之后,你怎么证明这一库代码跟原来行为一模一样。

接口返回的结构有没有变?错误码还对不对?少传一个字段,老代码默认给 0,新代码会不会直接抛 500?这些东西编译器不报错,跑起来也「看着正常」,可线上的客户端就是会崩。

我去翻了下行业里对 Spring Boot 2 升 3 的说法,官方和一堆老手的口径高度一致:javax 换 jakarta 那一步是「最繁琐、最容易出错」的,大代码库靠人手改「不可持续、人为出错风险极高」,是那种「明知道要做、又一直往后拖」的活。我这回是直接 2 升 4,比这还狠一档。

所以传统迁移拖到好几周,时间根本没花在写代码上,全砸在逐个接口对齐行为、对齐依赖、补一摞回归测试上。

说白了,迁移最贵的不是打字,是证明你没改坏。

想明白这一层,你就知道 AI 的劲儿该往哪使了。

我给这场迁移设了几道「验证门」

我没让 AI 一把梭。整场迁移拆成一串小步,每步中间卡一道「门」,过不了就不许往下走。

有几道门我觉得特别值得抄。

依赖集逐一对齐。换 Gradle 的头一步,源码我一行没动,只要求新构建产出的包,跟原来 Maven 那版依赖一个不差。105 个依赖对 105 个,连被 BOM 偷偷抬上去的版本号都用 strictly 锁死。构建工具换了,吃进去的东西得分毫不差。

端点清单逐字节比对。把项目里 88 个路由全导成一份清单,迁移前后必须一模一样,连每个 Controller 注册的 Bean 名都不许变。客户端那头感知不到任何动静,才算真透明。

最骚的是这道:删旧代码之前,先让它招供。项目里有个 529 行的祖传 XSS 过滤器,谁也不敢保证 Kotlin 重写后那堆正则的行为还一致。我让 AI 先拿原来的 Java 版跑一批输入,抓下 19 条「黄金输出」,钉成断言。

这样新版本任何一点行为漂移,测试立马变红。光靠「能编译、老测试也过」,这种坑根本兜不住。

最后还有真机冒烟。用真的 MySQL 和 Redis,把打好的包从头到尾跑一遍。就这一道,真抓出了两个藏得很深的 bug。

对了,把这一大坨机械翻译干快的,正是 dynamic workflow 本身。清扫基础设施那批,我让它跑成六个小工作流,每批是一条流水线:一个 agent 把 Java 翻成 Kotlin,另一组 agent 跟在后头专挑行为漂移。能并行的全并行,墙上那个钟基本只跟最慢那条链走,而不用等所有活一件件排着干完。

AI 替我抓出几个连编译器都看不见的坑

这部分,实话讲是我最服气的。

Java 翻 Kotlin,最阴的地方不在语法。真正咬人的是那些「看着一样、其实不一样」的语义差。我挑三个真在这次迁移里被抓出来的,你感受下有多细。

一个是字段凭空消失。我有个 Int 字段叫 isLiability,Kotlin 生成的取值方法是 isLiability(),结果 Jackson 一看不是布尔类型的 isXxx,直接当它不存在,这字段在接口返回里就没了。老的 Java 版叫 getIsLiability,字段稳稳在。

这种事编译器一声不吭,跑测试不挂、本地点一下也正常,等上了线,客户端那边解析少了个字段才发作。

一个是少传字段直接 500。Kotlin 的 data class 被 Jackson 当构造器用,客户端少传一个原始类型字段,比如排序用的 sort,直接报错;老代码是漏传就给个默认 0,相安无事。

还有个更绝的,入口类被悄悄改了名。Kotlin 把顶层的 main 函数编译成了一个带 Kt 后缀的类,jar 包的启动类名跟着变了,差点没起来。

这些坑,每一个都能让线上出故障,每一个编译器都查不出来。把它们一条条钉成测试,是这次 AI 帮我做的最值钱的事,比它把代码写出来值钱多了。

想抄走的话,这套打法长这样

把这次的经验拆开,其实是一套谁都能复用的迁移打法。

五段式迁移流程

先定死「什么不许变」。动手前就把接口契约、错误码、依赖集、配置文件这些不变量写成硬约束。我这回甚至明文规定,yaml 配置一个字都不许动。

让 AI 先出文档,别上来就写码。先一份设计 spec,再一份逐任务计划,每个任务都写清楚「做完怎么验证」。三场迁移我攒了差不多 4900 行这种 spec 和计划。

拆成带门的小步,一步一提交,随时能退。机械的、能并行的翻译活,扇出去给好几个 subagent 同时干;有先后依赖、要动脑判断的,串起来用门一道道卡住。

让评审唱反调。我专门让一组 agent 干挑刺的活,要求它默认怀疑、主动找茬,别回头顺着我刚写的代码夸。「看着对其实错了」就是这么防住的。

顺序本身也是风控。我是先升框架、再换构建、最后换语言,一次只动一个变量。真要框架、构建、语言一起换,出了问题你都不知道该赖谁。

快,是因为「证明没改坏」变便宜了

绕回开头那句。

AI 让这场迁移从「按周算」变成「按天算」,我现在越想越清楚,快的根本不在打字那一下。

真正被它压下来的,是迁移里最贵的两件事:大批量的机械翻译,能并行扇出去同时做;那些验证和校验,能提前变成一道道自动跑的门。后面这件,才是传统迁移拖上好几周的真正原因。

换个说法,AI 没帮你跳过迁移里的任何一个难点。它做的是另一件事:让你终于负担得起,把每个难点都老老实实做对。

最后得补一句免责:上面 AI 这边的数字,全是我项目真实 git 记录里抠出来的;传统那边的「几周」,是按行业经验估的。所以我说「保守十几倍」,是个数量级的感觉,不是拿秒表掐出来的精确值。这点咱得诚实。

说到底,你手里大概也有那么一个明知道该升级、又一直不敢碰的老项目。下次动它之前,先别急着让 AI 开写,先把「什么绝对不许变」列清楚,再给每一步配一道能自动跑的验证门。

想问问你:你手里那个最不敢动的老项目,是什么技术栈的?真正卡住你的,是改起来麻烦,还是不敢保证改完不出事?评论区聊聊。


参考资料

  • [[springboot-kotlin-migration-claude-code]]

相关洞察

  • [[ai迁移真正的杠杆是把验证变便宜]]