跳到主要内容

AI 编码智能体在遗留代码库上的实践:哪些有效,哪些会适得其反

· 阅读需 12 分钟
Tian Pan
Software Engineer

大多数 AI 编码演示展示的是智能体从零构建一个 Todo 应用,或者干净地实现一个全新的 API。而你的代码库,却是一个有着十五年历史的单体应用:充满未文档化的隐性契约、三个团队都依赖但没人完全搞清楚的废弃依赖,以及一个从单一类起步、如今已蔓延到四十个文件的服务层。演示与现实之间的差距,不仅仅是规模问题——更是结构性问题。在把代码库的"钥匙"交给智能体之前,理解这一点,能让你避开一类既隐蔽又代价高昂的失败。

AI 编码智能体确实能帮助处理遗留系统,但只在特定任务边界内才有效。超出这些边界,它们不是显眼地失败——而是生成外观可信、语法正确、语义却有误的变更,这些变更能通过代码审查,最终在生产环境中暴露出来。

上下文窗口不是正确的心智模型

团队在将 AI 智能体部署到大型代码库时,犯的第一个错误是把上下文窗口当作唯一的限制。这种思路是:"我们有 100 万 token 的上下文窗口,代码库有 200 万 token,只要喂进去相关的部分就行了。"这个算术漏掉了关键一点——模型并不均匀地利用上下文。

关于上下文利用率的研究表明,随着上下文长度增长,LLM 的性能会显著下降,并出现明显的"迷失在中间"效应:模型能可靠地关注上下文窗口开头和结尾的信息,但会忽略埋藏在中间的细节。对于遗留代码库来说,这意味着喂入"相关"文件时,往往会把最关键的上下文——某个深藏三层调用链的工具函数中的隐性约束——恰好埋到模型停止关注的地方。

真正的约束不是"我们能把代码库塞进上下文吗?",而是"我们能为这个具体任务按正确顺序获取正确的上下文吗?"这是两个本质上不同的问题。前者是个硬件问题,多砸 token 就能解决;后者是检索质量问题,需要刻意的工程投入。

对代码库进行检索增强生成(RAG)能部分解决这个问题。没有 RAG,智能体会自由地幻想出从未存在过的内部 API 调用;有了 RAG,它们被约束在检索到的代码模式范围内。CodeRAG-Bench 的研究表明,当提供高质量上下文时,生成质量会大幅提升——但检索层往往无法获取真正有用的上下文,尤其是跨文件的关系。如果模型以高置信度检索到错误的代码片段,就会产生外观正确(因为符合代码风格)的幻想 API。

实际建议:把代码库上的 RAG 视为降低幻想的必要手段,而非已解决的问题。通过测试来显式审计检索质量——查询你真实的内部 API 时,是否能返回定义这些 API 的文件,而不仅仅是调用它们的文件。

任务范围问题

AI 编码智能体在遗留代码上稳定成功的任务,比市场宣传的要窄很多。

高置信度任务类型:

  • 为隔离的模块编写单元测试。智能体看到函数,为正常路径和边界条件生成测试用例,产出捕获现有行为的特征测试(characterization tests)。这是遗留代码上最可靠、最有价值的使用场景。它不需要理解系统的全局不变量——只需理解局部契约。

  • 解释函数的作用。从遗留代码生成文档是较好的应用场景之一,因为它是只读的。智能体即使在文档注释里说错了,也不会破坏生产行为。

  • 从单个文件中提取工具类。小范围的重构——提取目标完全包含在上下文窗口内、没有外部调用者需要追踪的情况——效果很好。智能体可以看到变更的完整影响范围。

  • 在等效模式之间转换。将 React 中基于类的组件转换为基于 Hook 的组件,或将回调风格的函数迁移为 async/await——这些具有机械结构和明确映射关系的任务能成功,因为语义意图本身就由转换来保证。

高风险任务类型:

  • 横切变更(cross-cutting changes)。任何修改共享库、公共工具函数或多个服务使用的接口的操作,都会产生上下文边界问题。智能体只能对它能看到的调用方进行推理。在大型单体应用中,它通常看不到所有调用方。结果是:变更对它分析的子集是正确的,对其余部分则悄然出错。

  • 隐性契约修改。遗留代码充斥着未文档化的假设:"这个方法只从批处理任务调用,所以运行时不需要 null 安全";"这个字段为 0 有独立于 null 的特殊含义"。这些约束只存在于工程师的脑子里,偶尔出现在事故复盘里。智能体根本不知道它们的存在。看起来正确的重构,会悄无声息地破坏这些不变量。

  • 依赖关系图更新。升级共享库版本、修改被广泛使用的工具函数的方法签名、修改数据库表结构——这些变更的完整影响图超出了任何上下文窗口能容纳的范围。智能体修复了眼前的调用方,却悄然破坏了三个它从未见过的下游服务。

AI 生成的遗留系统代码为何以不同方式失败

即使在全新代码库场景下,AI 生成代码的质量指标也令人警醒。有报告显示,AI 生成代码包含的重大问题大约是人工代码的 1.7 倍,安全漏洞和配置错误的比例更高。在遗留系统上,这些失败模式会叠加放大。

核心问题是训练数据的时效性。LLM 在特定时间点索引的代码上进行训练。GitHub Copilot 的训练截止日期意味着它对该日期之后的 API 废弃、CVE 披露或库的变更一无所知。在依赖旧版本库的遗留系统中,智能体生成的代码可能对它训练时的版本是正确的,对你锁定的版本却是错的,或者"好心"升级到了你受约束的依赖版本不支持的最新语法。

第二个叠加失败是模式外推。遗留代码库包含十年前惯用、如今已成反模式的写法。当智能体在检索到的上下文中大量看到这些模式时,它学会了生成更多同类代码。它不会去现代化,而是忠实地延续遗留模式,因为本地证据就是这样说的。有团队报告说,智能体在已经部分迁移到 ORM 的代码库里写出了新的 JDBC 样板代码——因为检索上下文拉出了旧层的文件。

部落知识(tribal knowledge)是第三个缺口。Meta 专门构建了一个由 50 多个专业智能体组成的系统,用于提取和文档化那些仅凭代码本身并不明显的非显性模式——即设计选择。在部署这些智能体之前,工程师估计 40% 的生产调试时间是由于智能体对这些模式的了解不完整而造成的。先文档化部落知识、再部署智能体,显著改善了这一状况。大多数团队跳过了这一步。

安全模式:特征测试优先

AI 辅助遗留工作中最可靠的门控机制,是将测试优先的纪律应用于被修改的代码,而不是被生成的代码。在对任何遗留模块进行智能体辅助重构之前:

  1. 使用智能体生成特征测试,捕获该模块当前可观测的行为——包括边界情况和错误状态。
  2. 让人工工程师审查这些测试,审查的重点不是测试断言的正确性,而是它们所覆盖的行为的完整性。这里正是部落知识发挥作用的地方:工程师为智能体无从知晓的隐性约束补充测试用例。
  3. 在现有代码上运行这些测试,它们应该通过。这个基线就是契约。
  4. 然后再进行重构。这些测试强制保证外部行为得到保留。

这个模式之所以有效,是因为它将两件事分开处理:理解系统(智能体不可靠的地方)和在已理解的边界内转换系统(智能体有用的地方)。特征测试将人工工程师已知的知识正式化。

跳过这一步的团队,始终报告更高的缺陷率。这需要时间,但值得。

上下文工程才是真正的约束

AI 编码智能体在大型遗留系统上的可扩展性瓶颈不是上下文窗口的大小——而是上下文的质量。两个架构选择决定了成败:

范围明确、任务专属的上下文。 与其加载与变更相关的所有内容,不如只加载特定任务必要的内容。一个模块内的重构任务,需要该模块的代码、它的直接调用方和它所使用的类型——而不是整个服务。按文件夹或任务范围划定的上下文文件(类似于按目录划定范围的 CLAUDE.md),优于单一的庞大上下文文件,因为它们编码的是任务相关知识,而不是全系统的背景噪音。

任务前文档生成。 让智能体在修改模块之前先阅读并描述它,效果出人意料地好。生成的描述经过人工审查后,能浮现出隐性假设、识别出智能体本可能忽略的依赖,并强制形成两遍处理流程——第一遍智能体建立对代码的认知模型,第二遍在该模型内进行修改。加入这一步的团队报告跨模块破坏的情况明显减少。

这里的组织类比是新人入职。你不会在新工程师入职第一天就让他对遗留系统做重大变更,而不先让他读相关文档、和团队沟通、理解各种不变量。同样的原则适用于智能体,只不过"与团队沟通"这一步被替换为精心整理的上下文文档——其中编码了团队所知道的一切。

任务类型决策框架

判断是否对特定遗留任务部署智能体的实用启发法:

  • 影响范围在上下文中可见? 如果你能加载变更可能影响的完整文件集并放进上下文窗口,智能体就能对完整影响进行推理。可以推进。
  • 隐性契约已文档化? 如果该模块有特征测试,或者部落知识已通过注释或任务专属上下文被捕获,智能体就有了所需的约束条件。可以在审查下推进。
  • 变更跨越服务边界? 如果变更触及其他服务消费的接口,或修改了共享库的行为,在任何智能体生成的变更合并之前,都需要人工架构审查。这不是智能体审查就足够的情况。
  • 涉及依赖变更? 任何库版本升级、数据库表结构修改或 API 契约更新,都需要人工确认。智能体可以提议变更,工程师来验证影响图。

这个框架能防止最常见的失败模式:智能体生成的变更通过了静态分析、通过了类型检查、通过了现有测试,却仍然破坏了生产行为——因为测试没有覆盖智能体所不知道的不变量。

真正随时间改善的是什么

在遗留系统上使用 AI 智能体进展最快的团队,有一个共同特征:他们将上下文工程视为持续的投入,而不是一次性的配置。每个任务周期都会产出制品——特征测试、从遗留代码生成的文档、在任务专属上下文文件中固化的部落知识——这些制品让下一次智能体运行更加可靠。

这种复利效应,才是真正的价值主张。第一次在文档匮乏的遗留模块上运行智能体,风险较高,需要大量人工监督。第十次运行——在特征测试已编写、文档已生成并验证、隐性契约已被代码化之后——则要安全得多。

错误在于期望智能体在遗留代码上立刻带来生产力提升。生产力提升会在之后到来,是在让代码库对智能体可读的投入完成之后。这种投入本身就有独立价值——它就是文档和测试——但 AI 智能体让这种投入变得更迫切,因为跳过它的代价更高了。

遗留代码库并非为 AI 智能体阅读而设计的。成功的团队,将"对智能体友好"视为一种需要主动培育的显性工程属性,而不是可以理所当然假设的前提。

References:Let's stay in touch and Follow me for more thoughts and updates