老Java项目接AI IDE这事,我观望了快两年。原因很实在:那些Demo里AI改的都是玩具工程,而我手上这些项目动辄十几年积累,SQL散落在JSP里,一个工具类背后能牵扯出七八个隐式约定,文档三年前就断更了。直到有个核心模块的维护者突然离职,我要在两周内改一段涉及资金计算的逻辑,才把Cursor真正推进生产环境试了一遍。
试完之后我得承认,之前把问题想偏了。AI IDE的难点从来不是"模型聪不聪明",而是"项目的上下文能不能喂得进去"。老Java项目恰恰是上下文最复杂、最不友好的一类。这篇文章把我从零推进老项目AI化这几个月攒下的完整方案整理出来——包括迁移前的准备、规则配置、实操姿势、踩坑清单和团队落地方法,适合正在纠结"要不要把老项目接进AI工作流"的团队参考。
1. 老Java项目接AI IDE:先认清四个现实
1.1 老项目真正的问题不是"老",而是"历史包袱的结构性失忆"
把Java老项目和新技术栈Demo放在AI IDE面前对比,差距就像让一个实习生直接去改没人维护的遗留系统。你对着一个十年历史的后台管理系统,摆在AI面前的到底是什么样的代码库?
第一,依赖关系是蜘蛛网式的。一个订单模块间接依赖可能达到几百个jar,版本冲突是常态,运行期靠反射、SPI、动态代理兜底。AI如果只看单文件,根本理解不了某个Bean是怎么被装配出来的。它可能给你一个"看起来正确"的修改建议,但那个类实际是通过Spring的@DependsOn在初始化阶段被另一个模块装配的,改了构造方法签名,启动直接报错。
第二,隐含约定远多于显式约定。老项目的编码规范往往存在于老员工脑子里,不在文档里——比如"新增字段必须有默认值,否则老数据反序列化会炸""Controller里不允许直接return null,必须包一层Result"。这些约定AI不可能自己猜出来,你不告诉它,它就按通用最佳实践来,然后随手就给你造出一个生产事故。
第三,"死代码"和"活代码"混在一起。很多老项目改着改着,一些功能看着没用其实还活着:定时任务、MQ消费、回调接口、反射调用。AI最容易犯的错就是把"看起来没被调用"的公共方法判断成死代码然后建议删除,这是老项目迁移里最危险的误判之一。我见过不止一次AI自信地建议删掉一个"没有引用的方法",而那个方法恰恰是被@Scheduled注解驱动的定时任务入口。
第四,构建方式不标准。老项目的Maven/Gradle配置往往魔改严重,有自定义parent、奇怪的profile、内网私服。AI默认的构建知识在这套配置面前经常失效。你让它修一个编译错误,它可能直接建议你升级某个公共模块的版本——然后全项目二十多个模块一起编译失败。
这四个特征决定了:老项目接入AI,第一步不是"让它写代码",而是"让它先读懂你们的潜规则"。跳过这一步直接让AI上手改,等着的就是一堆看似合理实则破坏性的Diff。
1.2 Cursor不是"带AI补全的IDE",而是"结对编程的另一个人"
很多人把Cursor看成传统IDE加了个AI补全插件,这低估了它的本质。我自己的理解是,Cursor更像一个"高度可定制的结对程序员":它有记忆(索引和rules)、有工具(终端和MCP)、有上下文窗口(对话和@引用)。你用得好不好,取决于你怎么训练它,而不取决于它本身有多聪明。
这个定位带来两个心态转变。
第一个转变:你要开始写"给AI看的文档"了。过去写文档是为了后人维护,现在写文档更是为了让AI在生成代码时遵循同样的约定。.cursor/rules文件就是"给AI看的项目README",它的价值不亚于一份好的架构设计文档。我后面会用一个专门的章节讲这个文件的组织方式。
第二个转变:代码评审的对象变了。以前Review的是代码本身,现在Review的是"AI生成的Diff + 你给AI的指令质量"。如果AI生成了一段烂代码,大概率是你的上下文没给够、提示词太模糊,或者索引没建好。养成这个归因习惯,AI工作流才能越用越顺,而不是越用越暴躁。
提示:不要一上来就追求"让AI从0写一个模块"。老项目的正确打开方式是"让AI从1到1.1"——理解现有实现、做小步重构、补测试、改注释。步子越小,越不容易翻车。
2. 迁移前的地基工程:让AI能读懂一个十年老仓库
2.1 目录结构和构建依赖的梳理:先给AI画一张"地图"
Cursor的索引能力再强,也架不住一个满是迷宫的项目。我在做迁移准备时,第一件事是确认三样东西。
构建入口是否唯一。如果项目里既有Maven又有Gradle,或者有多套build文件共存,AI在分析时很容易精分。我见过一个项目,根目录有pom.xml,某个子模块里莫名冒出一个build.gradle,AI分析时一会儿按Maven的逻辑猜,一会儿按Gradle的逻辑猜,给出的建议两头不着。先统一用一套,至少在rules里明确"本项目以pom.xml为准,忽略其他构建文件"。
多模块关系是否清晰。老项目常见"一个仓库N个模块",模块间依赖靠本地install或者私服。建议在rules里写清楚模块间的依赖方向,比如"domain层不允许依赖infrastructure层""controller不得直接操作DAO"这类规矩。AI有了这条约束,生成跨模块代码时就不容易随手封一个跨层调用。
外部服务依赖名单。老项目经常藏着配置中心、缓存、MQ、定时任务这些外部依赖。AI分析代码时如果不了解它们的存在,很容易对"这个方法的副作用"产生误判——比如一个看似"纯计算"的方法里其实埋了一个Redis读操作,AI可能建议你把它提取成静态工具类,结果破坏了缓存读取逻辑。
这三样不需要你写得精美,能说明白就行。我把它们整理成了一页纸的PROJECT_OVERVIEW.md,放在仓库根目录,并且在rules中让AI"遇到不确定的架构问题先读这个文件"。实测下来,这比在每条对话里反复强调"我们这个项目是xxx架构"有效得多。
2.2 代码索引与"让AI记住项目"的正确姿势
Cursor底层的代码索引会自动扫描项目目录,但老项目的目录里往往混着一堆不该进索引的东西:target/、node_modules/、uploads/、third_party/。我第一次没配置忽略规则,结果AI经常拿第三方源码里的风格回答问题,还自以为很对。比如项目里自己封装的DateUtil不用,偏偏参考某个开源库里八九年前就被废弃的写法。
建议在首次进入项目时就设置好忽略列表:
- 构建产物目录(
target/、build/、dist/) - 生成代码目录(如
generated-sources、proto生成的Java文件) - 第三方依赖的源码包(如果Maven/Gradle拉下来的源码被索引了,会严重干扰检索)
- 大型测试资源(
src/test/resources里的大数据文件、图片、抽样数据)
索引构建完成后,再配合"代码库问答"来验证AI对你项目的理解程度——直接问它"XXX模块的订单状态流转是怎么实现的",看它能不能准确说出核心类的位置、职责和主要调用关系。这一步的验收标准很朴素:AI能准确说出项目里某个核心类的位置、职责、主要调用关系,而不是给你一段放之四海而皆准的泛泛解释。
2.3 JDK版本、构建工具版本与语言方言的对齐
这是老项目迁移到AI工作流时最容易被忽略的技术债,但对Java老项目来说几乎是决定性的。
Java老项目往往停在JDK 8或11,代码里全是Date、SimpleDateFormat、StringBuffer,还有一堆团队自研的私有API。AI的训练数据里有大量新语法,你如果不做任何约束,它会默认给你生成var、List.of()、Optional.orElseThrow()这些新写法——在老项目里要么编译不过,要么依赖不合规,要么和现有代码风格格格不入。
我在rules里直接写下"项目只使用Java 8语法,禁止var、禁止List.of、禁止Record,时间日期一律使用项目封装的DateUtil",效果立竿见影。
这背后的逻辑是:AI生成代码的风格,是由你显式或隐式地"教"出来的。你给它看的项目代码全是老风格,它自然会偏向老风格;但如果你在对话里给它的示例太"新派",它也会在新老之间来回摇摆。所以请把"语言语法边界"当成硬性纪律写进rules里,最好再附上一个"本项目推荐写法 vs 禁止写法"的对照表——别嫌啰嗦,这个对照表能避免90%的返工。
3. 最关键的一步:为Cursor写一份"项目说明书"
3.1 rules文件放什么:从架构约束到"潜规则"的显式化
在Cursor里,"项目的说明书"主要就是.cursor/rules目录下的文件。我把它当成"新入职员工的培训手册加公司红线"的合体。具体我会放这几类内容。
项目概况:技术栈、模块结构、构建命令、入口类位置。这是AI认识项目的第一印象,要保证准确。
架构约束:分层规则、依赖方向、禁止的循环依赖、核心业务逻辑必须写在service层。这些是"硬约束",AI违反它们的代价通常很大。
编码规范(老项目特色版):强制Java版本、命名风格、日志规范、异常处理方式、返回值约定。尤其要写清楚"Result包装""统一异常处理"这类项目特有的约定。
领域潜规则:金额计算禁止直接用浮点、状态机变更必须走统一方法、敏感字段脱敏规则。这些是"业务红线",AI越界的后果往往要上线了才能发现。
AI行为约束:禁止删除看起来未调用的公共方法(可能被反射、定时任务、MQ消费调用)、禁止把SQL重写为MyBatis-Plus链式调用(老项目内统一使用XML)、修改公共方法时同步搜索调用方等。
我把这些rules按"01-architecture.md、02-coding-standards.md、03-domain-rules.md"的方式分文件组织。原因是:rules文件一旦超过三个,AI在不同任务里读取的优先级会出现差异,分文件并按优先级命名,能让它先读最关键的架构约束,再读具体的编码规范。
3.2 用示例驱动,而不只是规则驱动
纯文字规则对AI的约束力其实有限。我自己的体感是,"示例"的约束力远大于"描述"。比如"时间格式统一"这条规则,如果只写"请使用统一的时间格式",AI还是会自由发挥;但如果你在rules里贴一段现有代码,效果完全不同:
// 推荐:项目统一使用 DateUtil.format(date, "yyyy-MM-dd HH:mm:ss") // 禁止:LocalDateTime.now().toString()、单独 new SimpleDateFormat()AI看到正反例以后,很少再跑偏。同样道理,返回值包装、异常处理、日志打点这些规则,我都配了一小段正反例。这比任何措辞严谨的"不得"都管用——因为AI不是靠读"禁令"理解的,它是靠"模仿模式"理解的。
另外提醒一句:rules文件本身也要纳入版本管理。我把它放在仓库的.cursor/rules目录下,跟随代码提交,这样每个成员clone下来拿到的是同一套"项目素养",不会出现"你这台机器上的AI懂规则,我那台机器上的AI是个野孩子"这种分裂。
3.3 MCP服务接入:要不要给AI接内部工具?
Cursor支持MCP,可以让AI调用外部工具获取额外上下文。对老Java项目来说,最大的价值在于:接上代码搜索/调用链查询工具,让AI能查询"这个接口有哪些实现类"或"谁调用了这个私服方法";接上日志查询工具,让AI能分析线上日志来定位问题;接上数据库字典,让AI生成代码时能查表结构。
我的建议是:先跑通基础工作流,再考虑MCP。MCP的价值是锦上添花,但如果项目本身的索引和rules还没理清楚,接上再多的工具,AI也会因为"对自己的判断过于自信"而出错。而且团队引入MCP是有维护成本的——你得保证服务稳定性、权限控制、数据安全。初期不接MCP,靠着"代码库问答+全文检索+人工补充上下文"已经能覆盖80%的场景,剩下那20%等基础扎实了再补。
4. 迁移期的一天:老代码上AI的实际工作流
4.1 场景一:理解一段没人敢动的核心逻辑
我接手过一个资金计算模块,方法体三百行,全是if嵌套和状态位判断,注释几乎为零。以前的常规操作是:逐行读、画调用链、找调用方、再对照配置表猜含义,整套下来一下午就没了。
现在的流程换成三步。
第一步,在对话里选中这段代码,让AI"用中文逐段解释这段逻辑,找出可疑的状态分支和隐藏的副作用"。第二步,追问"这段代码在什么情况下会走到这个分支?它依赖哪些外部配置?"——AI会结合检索到的配置类、枚举、调用方来做推断。第三步,让AI把解释写成结构化文档,直接沉淀成方法头的Javadoc。
这个流程最大的收益不是"AI替代了阅读",而是"AI把阅读结果结构化输出,大大降低了我进入上下文的时间"。我只需要验证AI的解释是否合理,在关键判断处再回去看一眼源码。三个小时能压到四十分钟,而且产出物(文档注释)是可以留存的。
有一个点必须提醒:AI的解释不等于事实。尤其在资金、权限、状态机这些核心领域,AI"自信地胡说"的概率并不算低。我的做法是:让AI在不确定的地方明确标注"此处存疑,建议人工确认",并要求它给出依据(引用具体类名/行号)。这样我复查时有方向,不会被它的语气带偏。
4.2 场景二:给老代码补单元测试
老项目的核心痛点之一是测试覆盖率低,而补测试恰恰是AI最顺手的事情之一。我这里有一套可复制的Prompt模板:
请为以下方法生成单元测试,要求: 1. 使用JUnit 4 + Mockito(项目现有测试框架),不要引入新依赖 2. 覆盖正常路径、边界值、异常路径,至少5个用例 3. 对外部依赖(Redis、数据库、HTTP)使用Mockito打桩 4. 测试数据使用项目已有的工厂类/测试工具类,不要硬造 5. 测试命名遵循 Given_When_Then 风格,保持可读性这里最容易踩的坑是:AI生成的测试引用了不存在的测试工具类,或者Mock了不该Mock的私有方法。我的检查顺序是:先看它引用的类是否都存在,再跑一遍测试看是否真的能过,最后抽查两三个用例的断言是否真正覆盖了业务逻辑——而不是"为了覆盖而覆盖"的低质量断言,比如断言一个空方法的返回值为null,这种用例等于没写。
补充一个实战小技巧:先让AI生成测试数据工厂,再让它写测试用例。老项目的测试数据构造往往又长又绕,如果AI每次都在用例里内联构造数据,测试会很臃肿。先让它抽出测试目录下的数据构造工具类,后面的用例都基于这个工具类生成,整体代码质量会提升一个档次。
4.3 场景三:重构一段"坏味道"代码
老项目里最常见的重构诉求,是把几百行的长方法拆成职责清晰的子方法,或者把重复度极高的if-else替换成策略模式。AI在这类任务上能力很强,但前提是你把边界画清楚。
我会在对话框里这样写:
这是一个订单价格计算的核心方法,重构目标: 1. 把优惠计算、运费计算、税费计算拆成独立私有方法 2. 保持完全相同的外部行为,不要改变任何计算结果 3. 每个子方法不超过30行,加Javadoc说明输入输出 4. 重构完成后,请给出一个"行为等价性"自查清单(哪些测试用例可以验证重构前后一致)这里最重要的不是让它"重构",而是**"保持行为完全不变"**。重构之后我会用现有的测试跑一遍;没有测试的话,先按上一小节的方式补几个关键用例再重构。顺序必须是"先补测试、再重构、最后看Diff",否则重构前后的行为差异会变成一笔糊涂账。
还有一个我后来改掉的习惯:不要让AI一次重构太多文件。一次只重构一个方法或一个类,Review完确认没问题再继续。AI一次改多个文件时,文件间的连贯性往往会出问题——比如公共方法签名改了,某个调用方没同步改,或者一处常量改了,另一处还是旧的硬编码。小步快跑,在老项目里永远是真理。
5. 老项目迁移的坑与解题思路(实测汇总)
5.1 坑一:AI改坏了共享依赖的版本
有次我让AI修复一个编译错误,它直接在pom.xml里把某个公共模块的版本号从1.2.3升到了1.4.0,理由是"1.4.0修复了该问题"。但那个公共模块是另一个组在维护的私有仓库,1.4.0的接口变了,全项目二十多个模块全部编译失败,几个人一起查了大半天才定位到是这个"善意的升级"惹的祸。
从那以后,我在rules里加了一条硬性规定:禁止修改pom.xml/gradle文件中的版本号,除非人工明确指示,并且把pom.xml列入"AI只读文件清单"。这个教训让我意识到:老项目里构建文件是最脆弱的资产,宁可让AI绕远路,也不要让它动版本。AI看到编译错误时,第一反应是"升级到最新版",但这个直觉在大型老项目里几乎是灾难级的。
5.2 坑二:AI生成的代码自带"新项目味",融不进老风格
前面提过这个问题,但值得再展开。老项目有自己的一套习惯:ServiceImpl里会有一堆历史遗留的防御性判断、日志打点的固定格式、甚至变量命名的缩写风格。AI天然偏好在生成代码时"合理化一切"——把防御逻辑删掉、用更现代的表达重写、给变量起一个"更准确"的名字。
结果就是:功能是对的,但Merge Request的Diff大得惊人,Review的人看着一头雾水:"这个变量名原来叫userCnt,你给改成userCount干什么?这不影响功能,但你的Diff里全这种噪声。"
解决方案我在实践中有三个层次:
- 写进rules:禁止重命名公共方法/变量,除非有编译错误或明确需求;
- 约束Diff大小:每次任务时明确"尽量最小化Diff,只修改目标代码块,不要顺手格式化其他行";
- 在对话里说明风格:在任务描述里直接贴一段"本项目这段代码的风格是xxx,请严格模仿"——AI看到具体样式后,对齐速度快到惊人。
5.3 坑三:AI的"能编译"和你的"能编译"不是一回事
Cursor的AI有时会用"它认为的新API"生成代码,本地编译却报错。最典型的场景是:Spring Boot版本过低,不支持某些新注解;或者JDK 8下用了String.repeat()这类只有新版本才有的方法。
这类问题的本质是:AI在做代码生成时,对"你们的项目环境"的了解不一定及时。尤其是依赖版本升级后索引没更新、rules里没写JDK版本边界、IDE的语言服务没加载完,AI就会拿一套"通用环境"的知识去生成代码。
我的应对策略是:
- 每次新建会话时,先确认"当前项目JDK版本、Spring Boot版本、关键依赖版本";
- 生成代码后,立刻让AI自查:"这段代码用到了哪些Java标准库API?它们在你说的JDK 8下是否可用?";
- 把编译和单测当成最后一道防线,千万别信AI的"我给你写好了"。
另外,如果条件允许,在本地搭一套和CI一致的环境给AI工作流用——同版本的JDK、Maven wrapper固定版本——能少踩一半的坑。我们团队后来统一用.sdkmanrc固定JDK版本,AI的生成正确率肉眼可见地稳定了。
5.4 坑四:上下文爆炸,AI越聊越"失忆"
老项目的单次任务往往需要同时看十几个文件:Controller、Service、Mapper XML、配置类、领域枚举。如果每条消息都把代码贴进对话,上下文很快就爆了,AI开始忽略之前的约束,重复犯已经纠正过的错误。
我的解决思路是"少贴代码,多指路"。在对话里尽量用"项目路径 + 类名 + 问题点"的方式引用代码,让Cursor自己通过索引去检索,而不是把整段代码粘贴进去。比如:
项目里 com.example.order.service.impl.OrderServiceImpl 的第120行附近, checkShippingAddress 方法里有个空指针隐患, 请结合 com.example.order.domain.ShippingAddress 的实现来分析。这样既省上下文,又能让AI学会"自主找代码"。省下来的上下文额度,可以让AI更从容地处理多文件联动修改。
补充一个小技巧:任务比较大时,先让AI"输出一个执行计划",确认计划没问题再执行,比让它闷头改完再review要稳得多。AI的"计划能力"和"执行能力"在同一个上下文窗口里是互相挤占的,先计划后执行,等于给它分层用脑子——第一次对话专门做规划,第二次对话只做执行。
6. 团队落地:从一个人用到全组都会用
6.1 先让AI做"读代码"工具,再谈"写代码"
我在团队里推AI工作流时的策略是"低门槛切入":第一个月,不要求任何人用AI改业务代码,只要求三件事——用AI理解不熟悉的模块、用AI生成Javadoc和注释、用AI写单元测试。这三件事风险极低、收益明显,成员很快能感受到"AI确实省时间"。
等大家习惯了"AI是我结对的对象"这个心态,再逐步开放到"让AI做小步重构、生成接口实现"。这个循序渐进的过程,本质上是在培养团队对AI输出的判断力——如果一上来就允许AI大改特改,很容易出现"改完就出问题"然后全组反弹,最后AI工具被丢进垃圾桶。
老项目的逻辑是:信任靠小事积累,崩塌只在一瞬间。第一印象如果是"AI把线上搞挂了",这个坑后面很难再填回来。
6.2 建立"AI辅助变更"的代码评审标准
AI改过的代码,评审标准和人工写的代码应该略有不同。我们团队最终沉淀下来的几条评审红线:
- 行为等价性:对于重构类变更,必须要求AI和旧代码在同样输入下输出一致,最好有测试或对比验证;
- Diff最小化:不接受"顺手优化"——顺手改了别人的命名、顺手调整了缩进、顺手把
if改成switch,这些都视为噪声; - 可回滚性:AI的变更必须能被单独revert,不要把AI的改动和人工的改动混在同一个commit里,否则出了问题很难拆;
- 涉密与安全:AI生成的代码不得包含硬编码密钥、不得过度授权,涉及敏感数据的代码必须人工重点审查。
这些标准并不是"不信任AI",而是把AI当成一个"能力很强但还不懂项目潜规则的新成员",给它配同样的管控流程。过了这层评审,AI的产出才能真正稳定合并进主干。
6.3 沉淀团队的Prompt与规则资产
我把团队里好用的Prompt、rules片段、踩坑案例统一收集到一个内部文档里,形成了"AI协作规范"。这个东西一开始只是我一个人在维护,后来慢慢变成了新人入职必读的一部分——因为它沉淀的不仅是"怎么用AI",更是"我们项目有哪些潜规则"。
整个文档的核心不是"背标准答案",而是学会描述问题。团队里最会用AI的人,往往不是代码写得最好的人,而是最会"把业务规则和项目上下文讲清楚"的人。所以我在文档里专门写了一个章节,叫"如何给AI一个好任务",列了五个要素:任务背景、目标、约束、验收标准、相关文件路径。这五个要素缺一个,AI的输出质量就会掉一档。
等到团队里每个人都能在五分钟内写出一条"好任务",AI工作流才算真正落地。我自己电脑里至今留着一个清单,记录我总结出来的有效姿势和踩过的坑——老项目不敢太激进,每次只改一个方法或一个类,先跑测试再提Merge Request,遇到"这个坑AI解决不了"的case,宁可花半小时把上下文备齐再试一次,也不要自己动手把活干了然后开骂AI。
最后分享一个我个人的体会:老项目接AI最成功的标志,不是"成员每天在群里秀AI改了多少代码",而是某天你发现新人入职后,第一周就能靠AI把一片旧代码讲得头头是道——那一刻你就知道,这套工作流大概是真的融进项目了。