从人工同步到自动闭环:跨 Java/.NET 代码转换工具的工程化实践

背景

GcExcel 是一款基于 Java 平台的服务端表格组件,支持批量创建、编辑、打印以及导入/导出 Excel 文件,能够在高性能处理的同时保持较高的 Excel 兼容性。

产品同时发布在 Java 和 .NET 两个平台上。相应地,项目组需要长期维护两套实现:一套 Java 代码,一套 C# 代码。常规开发流程通常如下:

把一种语言的改动同步到另一种语言,对开发者来说一直是一件很头疼的工作,既费时,也费力。

一方面,改动的规模通常并不小。一个中等规模的功能更新,平均需要修改 2000 行以上的代码;即使是改动规模较小的 bug 修复,也仍然需要额外投入时间,将实现同步到另一套代码中。

另一方面,长时间逐行对照代码进行同步,本身就是一项非常消耗精力的工作。这类体力活做得多了,很容易出现操作变形,例如笔误、语义不等价、实现细节遗漏等;这类问题并不少见。如果这些问题能够被单元测试或后续自动化测试拦截,代价还只是多花一些排查时间;但如果未被及时发现,最终就可能演变为用户侧问题。

一直以来,这项工作主要依赖人工完成。由于 Java 和 .NET 之间的代码互转本身属于较为小众的需求,市面上也缺乏成熟、可直接复用的开源方案。如果完全自己造轮子,不仅难度高,效果也很难预估。直到 LLM 井喷式发展,这件事才真正迎来转机。

最早的起点:对话式 AI 只能解决局部问题

最开始可用的 AI 主要是对话式工具。当时,一些代码补全类和对话式 AI 工具已经能够协助处理一些局部工作,比如转换一些代码片段,或者处理行数较少的文件。即便能力还比较有限,也已经和早期"刀耕火种"的方式有了明显区别。一个很直接的感受就是:用起来确实爽,而且效率和正确率都有了明显提升。

这个阶段的重点主要还是调提示词,尽量提高转换的准确率。但对我来说,更重要的收获其实是建立了信心:AI 的确有能力把"转代码"这件事做好。这也为后面的多轮优化打下了基础。

从 Prompt 到 Skill:AI 开始具备承接完整任务的能力

真正的转折点,出现在具备代码仓库读取、命令执行和任务承接能力的 AI 开发工具开始可用之后。

一方面,不再需要手动把代码片段贴给 AI;这类工具可以在 CLI 环境中自动读取代码、执行 Git 命令,并识别需要转换的范围。另一方面,基于 CLI 的这些能力,也为自动化带来了可能。最初的版本,就是把之前总结好的提示词封装成一个 Slash Command,用起来就更顺手了,只需要打开 CLI 并执行:

Plain 复制代码
/code-converter <commit 信息>

到这里,AI 的角色已经不再只是"帮忙转换代码片段",而是开始具备了承接整段转换流程的能力。

但我自己用着顺手还不够,关键是要让团队也能顺手用起来。代码转换是每位开发者都会遇到的事情,在验证出一定效果之后,我开始尝试推广。问题也随之出现:由于 Slash Command 没有纳入代码仓库管理,如何让大家始终使用同一份提示词,成了一个实际的落地障碍。

工具形态探索:如何普及代码转换工具

在这个阶段,主要探索的是:怎么让所有人都能顺畅地把这套流程用起来。

最早的一版自动化方案是这样的:

这版方案主要依赖 Git 的 pre-push hook,通过执行一些脚本来启动 CLI,完成自动化流程。

但这个方案在推广时并不理想。首先,它需要额外配置 hook,而且 hook 一旦设置,所有 push 都会触发。在某些场景下,比如只是做原型设计时,其实并不希望执行代码转换,开发者如果想跳过,还需要额外指定 Git 参数,使用成本较高。其次,我们项目共有 6 个仓库,逐个配置也很麻烦。再者,skill 文件需要放在代码仓库中,如果有更新,还得再同步到用户目录并执行额外操作,维护起来也不方便。

简而言之,这个流程不够顺手,因此最终并没有真正推广开。

为了解决这些问题,让工具真正可用,第二版引入了一个可手动操作的 UI,流程如下:

这一版大幅降低了使用门槛:双击 bat 即可启动;skill 直接放在工具仓库里,由工具统一管理;使用者只需要关注 UI 上要填写的参数即可。

真正的瓶颈:大任务为什么开始不稳定

到了这个阶段,工具在功能上已经基本可用,团队里的其他同事也开始把它真正用到代码转换场景中。随着使用次数增加,真正的问题才开始集中暴露出来。

小任务的效果通常不错,但一旦进入大批量代码转换,稳定性就会明显下降。结合实际使用情况回看,核心瓶颈主要有三个。

第一个问题,是上下文增长过快。

举个很典型的例子,某些存量代码里,C# 的属性定义可能是这样的:

C# 复制代码
public CalcType CalcType { get; set; }

Java 侧可能是这样的:

Java 复制代码
public CalcType CalcType = CalcType.Single;

从抽象规则上看,在 Java 侧,更合理的对应形式通常应该是 getter/setter,命名也应该转成小驼峰。但现实问题在于,存量代码本身未必完全规范。某些已有 Java 实现可能已经偏离了"标准映射",比如直接保留为公开字段,甚至还带有特殊命名方式。

这就引出了第一个难点:为了确保转换准确,AI 往往需要多次到目标仓库里查代码。

这个要求本身很合理,但它会迅速推高上下文成本。一旦开始大量读取代码仓库,AI 的上下文就会快速膨胀。根据我的实际观察,任务规模一旦上到 1000 行左右,就很容易出现上下文耗尽;即使没有彻底耗尽,稳定性也往往会明显下降。

第二个问题是,大 diff 会让整个转换过程变得不流畅,甚至失控。

理想情况下,AI 会尝试自己拆分任务、逐步完成。但由于前面提到的"必须结合存量代码"这一点,即使任务被拆开,执行成本仍然很高。更糟的是,AI 有时会偏离当前目标,不再专注于完成转换,而是"另起炉灶",试图再生成一个工具来解决问题。到了这个阶段,最大的风险已经不只是单次转换失败,而是整个流程开始失去稳定性和可预期性。

第三个问题是,AI 也会犯错。

从测试结果看,能够顺利完成的代码转换任务,正确率已经很高,基本能达到 95% 以上,但仍然无法完全避免零散错误。

增强工具

为了解决这些问题,我重新设计了一版流程:

新流程主要有以下几个亮点:

MCP Server

原来那种"直接读取源码来判断如何转换"的方式,是一个很大的瓶颈。为了减少"读代码"带来的不确定性,我设计了一个 MCP Server:它会从 Jenkins 获取产品最新的非混淆包,再通过反射和遍历的方式,构建出一份完整的类型信息集合。

这份集合包含目标产物中的所有类型,以及每个类型下的方法、字段、属性等结构化信息。这样一来,AI 就不再需要直接查找源码,而是通过 MCP 查询目标平台实际暴露的 API 信息。

当然,中间也有一些失败的尝试。之前我试过用 knowledge.md 记录差异,但效果并不好。第一,项目太大,某次转换过程中记录下来的差异,下次未必真的能复用;第二,每次转换前还要额外读取这份文档,本身也会增加上下文负担。

大任务拆分

大任务拆分其实经历了两次设计。第一次也是做成一个 skill,交给 AI 自己完成,但 AI 还是会像之前那样,觉得"需要先写个工具"再来解决问题。

既然它这么爱写工具,那我就干脆让它真正写一个工具。后来我把"任务拆解"这一步固化成了专门的程序逻辑,让它稳定地负责拆分任务,避免 AI 在执行时临场发挥。这里本质上是在给 AI 减负:凡是能够固化成代码的部分,最好就不要继续依赖 AI 即时决策。

转代码和仓库隔离,并行执行转换任务

这一部分也经历了多轮优化。第一次优化后,所有小任务仍然是串行执行的。之所以不并行,核心原因是多个任务会同时修改同一个仓库,从而产生冲突。

第二次优化后,我把"转换"和"写回仓库"这两件事解耦了。每个任务先独立完成转换,结果先保存为中间结果,并不直接写入目标仓库;待所有转换任务结束之后,再统一把结果写回仓库。

这一设计的主要目标是提升执行效率。并行执行任务的最大风险在于多个 CLI 工具同时修改同一个文件会产生冲突,因此,此处的设计思路是将仓库完全隔离。代码转换 skill 仅生成中间结果,不直接写回代码仓库,只要不落到目标仓库里,就不会发生写冲突。这样既提高了速度,也保证了稳定性。

最终修复编译错误保底,尽量形成自动闭环

即使前面的转换质量已经很高,AI 产出的代码仍然难免留下少量零散错误。这一步的目的,就是尽量减少人的参与,把人工介入压缩到最后的结果审核阶段。转换完成之后,再让 AI 统一检查并修复一轮编译错误,最终产出尽可能收敛到"可直接 review 的代码变更"。

这也是整套工具从"让 AI 帮忙写代码"演进为"跨语言代码同步系统"的关键一步。

效果:从人工翻译到结果校验

和原来的手工转换方式相比,这套工具带来的变化是比较明显的。

以一次 2k+ 的代码变更为例,过去完成一次完整转换,通常需要 1 到 2 天。这里既包括代码转换本身的时间,也包括后续排查和修复错误的时间。现在,这部分工作已经基本可以收敛为两个阶段:第一阶段,是小于 1 小时的自动代码转换;第二阶段,是 1 到 2 小时的人工 code review。

转换过程本身已经基本可以在后台完成,不再依赖人工全程盯着处理。人的主要工作,也从过去的"手工翻译和排错",变成了"结果校验和最终把关"。

可以看到,这个工具不仅提高了转换精度、质量和速度,也把开发者从繁重的体力活儿中解放出来,可以把精力投入到更重要的问题上。

总结:AI 落地不是堆 Prompt,而是做系统

回头看,这个工具的演进路径其实很清楚。

最开始,我们先解决"能不能运行"的问题;然后解决"怎么更方便地转换代码"的问题;接着解决"大任务为什么转换不稳"的问题;最后再把上下文获取、任务拆解、并行执行、结果写回、错误修复这些关键环节逐步系统化。

在这个过程中,真正起决定性作用的,并不是单纯把提示词写得更复杂,而是把那些 AI 不擅长、但流程又强依赖的部分工程化。比如任务拆解、API 查询、结果合并、编译修复,这些工作如果全部压在 AI 身上,流程就很容易失控;而一旦由系统来承担这些职责,AI 才能在自己更擅长的局部转换上稳定发挥作用。

到了这个阶段,这套工具已经不再只是"让 AI 帮忙转代码",而是在逐步演进成一套相对完整的跨语言代码同步流程。

如果要用一句话总结这次实践,比较贴切的表述大概是:

AI 负责代码转换,系统负责约束流程。一个工具要真正进入生产环境,核心不是聪明,而是稳定、可控、可预期。