软件维护怎么做?我接手12万行老Java项目3年:重写、渐进重构和冻结维护的账单对比

🔑 关键词:软件维护,遗留系统改造,技术债,Java项目维护,软件维护成本

📖 摘要:一个业余维护老项目的真实记录:12万行Java单体,从JDK7/Spring3.2到JDK17/Spring Boot 3.2,讲清维护步骤、SonarQube阈值、升级命令、重写对比和预算。

软件维护怎么做?我接手12万行老Java项目3年:重写、渐进重构和冻结维护的账单对比

图片

先说结论:软件维护不是“修旧”,而是“让每次变更的成本别涨”。
我 2020 年接手一个 12 万行 Java 单体,JDK 7、Spring 3.2.18、Struts2 2.3.24、MyBatis 3.2.8、MySQL 5.6、Tomcat 7。
日订单 1200 左右,月活 2.3 万,听起来不大,但每次发版要 4 个人守到凌晨 1 点。
第一年我们统计过:每月维护 6.8 人日,42% 花在环境和依赖,31% 在数据修复,18% 在业务咨询,只有 9% 是真正代码 bug。
所以别一上来就喊重写,先看钱花在哪。

先跑基线:我用的 6 个命令和 4 个阈值

图片

我会先在一个干净分支跑基线,不修任何业务代码。
mvn versions:display-dependency-updates 看依赖能升到哪,mvn dependency:tree -Dverbose 抓冲突。
mvn org.owasp:dependency-check-maven:check 跑 CVE,sonar-scanner -Dsonar.projectKey=legacy-crm 出质量门。
jdeps --jdk-internals target/legacy-crm.jar 查 JDK 内部 API,pt-query-digest slow.log 看 MySQL 慢查询。
阈值我定得很土:圈复杂度 >15 的方法标红,重复率 >5% 不接新需求,单测覆盖率 <60% 不发布,慢查询 long_query_time=1 超过 20 条就排期。
这两个月很枯燥,但后面每次升级都靠这份基线判断是不是砸了。

图片

重写 vs 渐进重构 vs 冻结维护:我见过 3 种账单

2019 年我们试过重写:8 个人,计划 9 个月,预算 240 万,结果第 14 个月才上第一个模块。
上线后 3 个月出了 17 次 P2 以上事故,最坑的是老系统里一个“订单拆单”规则没人写文档,重写团队按新逻辑做,财务对账差了 8 万。
后来改成渐进重构:每季度拿 10% 维护预算,先包一层 API,再把 Struts2 action 逐个换成 Spring MVC。
18 个月发了 46 次版,P2 事故 4 次,JDK 从 7 升到 8,再升到 17,Spring 3.2 到 5.3,最后 Spring Boot 2.7.18 过渡,再拆到 3.2.x。
冻结维护也试过 4 个月:只修 P0,结果第 5 个月积压了 63 个变更,数据库里多出 11 张临时表。
我的观点:重写适合“业务规则已经稳定、团队能全职投入”的系统;渐进重构适合还在赚钱、不能停的系统;冻结维护只适合准备下线的系统,否则就是欠债。

图片

升级步骤:我是怎么把 JDK 7 拖到 JDK 17 的

图片

第一步不是改代码,是补回归:API 测试 647 条,Selenium 关键路径 318 条,跑一轮 23 分钟。
第二步锁依赖:把 spring-context、mybatis、mysql-connector-java 版本写进父 pom 的 dependencyManagement,禁止子模块乱写。
第三步小步升:JDK 7 -> 8 先跑 2 周,修 UnsupportedClassVersionError 和日期 API;JDK 8 -> 11 扫 javax.xml.bind;JDK 11 -> 17 重点查反射和 sun.misc.Unsafe。
第四步处理 javax 到 jakarta:Spring Boot 3.2 + Tomcat 10.1 + Hibernate 6.4,javax.servlet 全换 jakarta.servlet,这个别想自动脚本一把梭。
第五步金丝雀:Nginx 按用户 ID 哈希切 5% 流量到新实例,观察 48 小时,看 TP99、错误率、GC 停顿。
我们 JVM 参数从 -Xms256m -Xmx1024m -XX:+UseParallelGC 换成 -Xms512m -Xmx1536m -XX:+UseG1GC,Full GC 从每天 7 次降到 1 次左右。
Docker 镜像也从 1.2GB 降到 380MB,用 eclipse-temurin:17-jre-jammy 多阶段构建,别直接拿 JDK 镜像跑。

维护预算:我现在按“四象限”分钱

图片

我把维护分成四类:止血、保鲜、还债、替换准备。
止血占 25%,只修 P0/P1 和线上数据;保鲜占 30%,升依赖、补证书、换域名、修安全;还债占 30%,拆模块、补测试、清慢查询;替换准备占 15%,写接口文档、做数据映射、试点新服务。
这个比例不是拍脑袋:只要“保鲜”低于 20%,半年内一定被 CVE 或证书过期打脸;“还债”连续两个季度为 0,变更前置时间会从 3 天涨到 11 天。
我们看 DORA 四个指标:部署频率从每月 2 次到每周 3 次,变更失败率从 28% 降到 9%,MTTR 从 5 小时 20 分降到 47 分钟,前置时间从 12 天到 4 天。
工具上,Renovate 每周一 9 点开 PR,Jenkins 每周日 2:00 跑全量回归,SonarQube 质量门卡新代码,不卡老代码,否则没人能动。
数据库用 Flyway,禁止手改生产表;每个变更必须有 V2025_06_01__add_order_index.sql 这种版本号。
如果只能记一句话:软件维护的目标不是让老系统变年轻,而是让替换它的那一天来临时,你不用再读 12 万行没有测试的代码。

🏷️ 标签: