老系统现代化先搭兼容阶梯
Copilot 真正有用的地方,是把环境、入口、测试和边界一层层拉出来
老系统现代化先搭兼容阶梯
我第一次打开一个二十年前的 Java 项目,最先看到的不是代码味道,而是年代味道。build.xml 还在,pom.xml 不在;目录结构是老式的,测试入口也是老式的,连“能不能重建一个可运行产物”都得重新确认。这个阶段最容易犯的错,是盯着类和方法开刀,忽略真正卡住现代化的东西其实是环境和入口。
旧系统现代化最先要做的,是把兼容阶梯搭出来。阶梯的底座通常只有一件事:让旧系统在旧规则里稳定跑起来,最好还能做成一个时间胶囊。Docker、老 JDK、老构建工具、旧测试命令,这些东西看上去土,作用却很硬。只要这个底座没立住,后面每一次改动都会混进新的噪声:到底是代码坏了,还是 JVM、镜像、架构、插件坏了,根本分不清。
接着才轮到选桥。桥的目标很具体:一头连着旧源码,一头连着新机器。这个项目最后落到 Java 8 和 Gradle 7.6,刚好卡住两端的约束:Java 17 不再愿意编译 Java 1.5 的源码,Java 6 又跑不进 ARM64 的原生环境。桥搭错了,现代化会直接卡死在工具链上;桥搭对了,后面的迁移才有可能一段一段往前挪。
这里最有用的是让大模型做整理和翻译。它可以帮着把 Ant 的逻辑翻成 Gradle,把目录映射回旧布局,把那些重复而机械的配置先搬出来。比如把源码目录明确指到老路径,再补一个自定义任务去跑旧式 main() 测试:
java {
sourceCompatibility = JavaVersion.VERSION_1_5
targetCompatibility = JavaVersion.VERSION_1_5
}
sourceSets {
main {
java {
srcDirs = ['java']
}
}
}
tasks.register('runLegacyTest', JavaExec) {
mainClass.set(project.findProperty('mainClass'))
classpath = sourceSets.main.runtimeClasspath
}
这段配置的意义在于把旧世界的规则显式化。旧项目里最麻烦的东西,往往不是缺少能力,而是能力藏在习惯里。测试是靠 main() 入口跑的,源码是按老目录摆的,构建是假设 Ant 时代的路径写的。Copilot 在这里能省时间,但省掉的是翻译时间,不是判断时间。
判断时间不能外包。Gradle 8 看起来更新,Java 17 看起来更现代,直接往上跳也看起来更干净,可这些选项未必能同时满足旧源码和旧测试的约束。现代化最怕的,就是把工具升级当成进步,把命令跑通当成理解系统。一个项目只要还没把运行基线钉死,绿色测试就不算胜利,只能算暂时没撞上暴露面。
我更信任的做法,是先把问题拆成四层:能不能跑、能不能编、能不能测、能不能改。第一层是复现现场,第二层是确定桥,第三层是判断旧测试到底保住了什么,第四层才轮到真正的重构。Copilot 在前两层特别好用,它适合追着日志和目录结构做体力活,适合把零碎线索拼成一张可读的地图;到了第四层,模型只该负责搬运和提示,拍板还得靠人。
旧系统现代化里最值钱的结果,是把一堆“应该能跑”的东西,变成“确实能跑、也知道为什么能跑”。一旦这条兼容阶梯立住,后面的拆解才有节奏,改动也才有边界。Copilot 这时像个跑前线的助手,拿着手电筒照年代、照入口、照依赖,真正决定方向的,还是对旧系统历史的判断。