老项目 × AI:从濒死到手术台,我做对了什么?
接手一个十年老单体项目,打开代码,四千行的
OrderService、不敢升的 JDK 1.8、被魔改的底层包、写在存储过程里的定时逻辑——那一刻我明白,这不是技术债务,这是技术遗产。
而我要做的,是带着 AI 这把新式工具,把它从“濒死”拉回“体面地活下去”。
写在前面:这不是一篇爽文
如果你也接过这样的项目——打开 IDE 要等半分钟,点进一个类看到滚动条细得像根针,问同事“这段干嘛的”只会收到一句“别动就行”——那我相信,你看到标题时的心情,和我当时一样复杂。
一边是摇摇欲坠的老古董,一边是号称“解放生产力”的 AI 全家桶(Vibecoding、Agent、Copilot)。我天真地以为,把脏活累活扔给 AI,我只要躺平做决策就好。
结果呢?AI 确实很勤快,但它勤快得像个刚毕业的实习生——敢删代码、敢升依赖、敢拆结构,就是不敢问“为什么”。
三周血泪,四次炸服,七个被驳回的 PR 之后,我终于摸到了一条让老项目和 AI 和平共处的路。这篇文章不吹概念,只讲实操——我踩过的坑、定下的规矩、以及最后如何让那个四千行的OrderService体面地活了下来。
Define:一个让所有架构师头皮发麻的场景
故事的开头很标准——Leader 丢给我一个任务:去救火,稳住一个十年老单体项目。
我打开代码,映入眼帘的是:
- 一个叫
OrderService的类,四千多行,最长的那个方法翻三屏找不到结尾。 - 依赖版本停留在 Spring 4.x,JDK 还是 1.8,没人敢升,因为升一次崩一次。
pom.xml里躺着一个被魔改过的底层包,注释写着“某业务特殊需求,改源码”——至于改了哪里,没人说得清。- 数据库字段
status=2,文档写“已审核”,但线上有个定时任务每天凌晨三点会把它刷成“已删除”——而这套逻辑写在存储过程里,没人敢动。
我问同事:“这段逻辑为什么这么写?”
同事答:“我也不知道,以前就这样,别动就行。”
那一刻我懂了:这不是一个技术问题,这是一个考古问题。而我手上最先进的工具,只有一台电脑和一个刚上线不久的 AI 助手。
Design:我以为的“AI 解放方案”
当时的想法很直接:项目太复杂,人看不过来,那就让 AI 帮我读、帮我写、帮我改。工具链齐全,理论上一周就能出效果。
我设计了一套理想工作流:
- 把
OrderService.calcPrice()那个几百行的方法贴给 AI,让它解释业务逻辑。 - 让它生成对应的单元测试,把这个方法的行为固化下来。
- 再让它帮我重构——把散落的校验逻辑抽成私有方法,把重复的折扣计算统一收口。
- 最后,基于改动生成 Mermaid 调用关系图,告诉我哪些地方会受影响。
听起来很完美,对吧?AI 负责干脏活累活,我负责做决策和 Review。
但实际跑起来之后,我才发现——AI 在它看得懂的地方有多聪明,在它看不全的地方就有多鲁莽。
Dilemma:Agent 进老项目,等于往雷区扔扫地机器人
AI 确实勤快。但它只看到了我贴给它的那几百行代码,完全看不到全局。于是灾难接二连三:
第一个坑:擅自“优化”依赖,线上炸了半小时
Agent 看到redis.clients:jedis版本号是 2.9.0,贴心地给我升到 4.3.0,理由是“修复了已知漏洞”。但它不知道我们生产环境的 Redis 版本是 4.0,更不知道项目里有十几处用到了JedisPoolConfig的旧 API。一部署,NoSuchMethodError铺满日志,线上直接不可用。
第二个坑:自作聪明删“无用代码”,价格算错calcPrice()里有一段看起来毫无意义的Thread.sleep(50),Agent 判定为“性能垃圾”,当场删除。但那行代码是三年前为了等主从同步而加的操作——虽然写法不规范,但十年来没人敢动。删除之后,主库刚写入的订单数据在从库查不到,价格全算错,财务第二天追着我骂。
第三个坑:疯狂重复造轮子,代码量膨胀 10%
上午我让 AI 写了个手机号脱敏工具类PhoneMaskUtil。下午让它改另一个模块的用户信息展示,它又写了个UserInfoMaskHelper,功能几乎一模一样。短短一周,项目里多了二十多个工具类,代码量膨胀了近 10%。
第四个坑:修一个 Bug,生出三个新 Bug
我让 AI 修一个空指针异常,它加了个判空,返回空列表。本地测试通过,上线后下游计算平均值直接除以零,又炸了。AI 只管“当下这个方法的返回值不空”,压根不会去扫调用方怎么消费这个返回值。
最崩溃的是第五个坑:修复死循环,变成死循环
我反馈 → AI 改 → 引入新问题 → 我再反馈 → 它再改。同一个类的同一个方法,来来回回提了七个 PR,全是 AI 写的。我每天的工作变成了点“驳回”和“重试”,像个高级测试员,而不是架构师。
那一刻我彻底清醒:让 AI 在老项目里自由奔跑,它不是手术刀,是挖掘机。
Deliver:血泪里总结的四条铁律
被折腾了三周后,我终于摸索出一套能打的方案。核心思想就一句话:
让 AI 当实习生干局部脏活,所有架构决策必须捏在人类手里。
第一条:先画红绿灯,再让 AI 进门
我在项目根目录建了一个.ai-rules.md,每次对话开场第一句就是“先读这个文件”。里面三块内容:
- 🔴红灯禁区:升级依赖、删方法删字段、删配置文件、改核心公共类、动数据库。AI 敢碰一次,直接驳回。
- 🟡黄灯确认区:新增工具类、改 Controller 入参出参、改事务行为。AI 可以提方案,但必须我点确认才能合入。
- 🟢绿灯自由区:方法内部算法优化、if-else 简化、加日志、写注释、生成单元测试。
规则定死之后,AI 的犯错率直线下降,至少它不敢再碰pom.xml了。
第二条:测试先行,没有安全网不准动手术
老项目最大的痛点是“没有测试”。所以我强制要求:改任何代码之前,先用 AI 生成特征测试——让 AI 根据当前方法的行为逆向生成单元测试,跑通之后,这些测试就变成“护城河”。改造完成后,跑一遍测试,全绿才能提 PR。这一步把“改崩了都不知道”的恐慌,彻底掐死在本地。所有测试都用 Mockito 隔离外部依赖,套件能在 30 秒内跑完,开发中随时可以快速验证。
第三条:拆分任务,一次只改 20 行
绝不允许对 AI 说“重构整个订单模块”。只能指定具体的方法和行号范围,例如:“把OrderServiceImpl.createOrder()第 50 到 70 行的参数校验逻辑抽成一个私有方法,保持原有功能不变。”每次改完立即跑测试,立即提交。一个模块拆成四五十个小任务,累是累了点,但安全。
第四条:人类 PR 必过三关
AI 提交的 PR,必须经过三道检查才能合入:
- 依赖检查:
pom.xml或build.gradle有变化?直接驳回,理由就一句:“你没权限碰这个。” - 删除检查:原有代码一行不许删,仅允许删除 AI 新增的调试代码。AI 觉得“没用”的代码,我宁可留着也不让它删。
- 日志检查:有没有把
System.out.println带进生产代码?AI 经常干这事,见一个打回一个。
额外收获:让 AI 反向考古
AI 不适合改老代码,但特别适合读老代码。我把那段神秘的Thread.sleep(50)丢给 AI,让它分析上下文,它还真猜出来了——“疑似等待主从同步”。虽然不敢 100% 确认,但至少给后续排查提供了方向。我还让它给整个订单模块生成了调用关系图,清楚看到calcPrice()被哪些上游依赖,彻底解决了“改一处不知炸哪里”的恐慌。
结尾:AI 是手术刀,还是挖掘机,取决于你怎么用
老项目不是一个可以“一步到位”重构的东西。它是十年里无数个“先上线再说”的妥协累积出来的产物,里面每一行看似愚蠢的代码背后,可能都藏着一段血泪教训。
Vibecoding 和 Agent 给了我们强大的生成能力,但生成能力越强,越需要有边界。我的经验总结成一句话就是:
人指路,AI 干活,测试保底,人把关。
按照这个流程,我那个四千行的OrderService用了一个月拆成了七个类,每个类五百行以内,单元测试覆盖率从 0 提升到了 67%。过程中 AI 辅助生成了初稿,但核心逻辑部分人工重写了约 40%——没有任何一行代码未经 Review 就直接上线。
你问我现在还敢不敢让 AI 改老项目?
敢,但戴好手套、穿好防护服。
老项目还能再活十年。只不过这一次,是带着 AI 这把手术刀,体面地活下去。
而那个曾经问“这段逻辑为什么这么写”的我,如今会笑着回答自己:“我不知道没关系,但我知道怎么让 AI 帮我去查、去试、去验证,同时不炸掉整个系统。”
这,才是架构师该干的事。