AI编码代理在大型重构中的实践:规范优先与无监督自动化
2026/8/17 3:57:08 网站建设 项目流程

1. 项目概述:当AI编码助手遇上“无测试、无评审”的巨型重构

最近在社区里看到一个挺有意思的讨论,关于AI编码代理(AI coding agent)在大型、复杂项目中的实际应用边界。恰好,我前段时间亲身经历并主导了一个极具挑战性的案例:在一个超过70万行、189个文件交织的TypeScript代码库中,拆除一个核心的架构不变量(architectural invariant)。更刺激的是,这次重构是在“无测试预言机”(no test oracle)和“无人工代码评审”(no human code review)的约束下完成的。听起来是不是有点疯狂?这恰恰是“规范优先”(Specification-first)方法与现代AI辅助工具结合的一次极限压力测试。

这个项目本质上是一场信任危机。我们有一个庞大的单体应用,其核心业务逻辑被一个历史遗留的、全局性的“用户权限-数据可见性”绑定规则所禁锢。这个规则就像代码里的钢筋水泥,贯穿了几乎每一个服务层、数据访问层和UI组件。随着业务飞速发展,这套规则成了最大的瓶颈,任何新功能的开发都像在螺丝壳里做道场,牵一发而动全身。传统的重构路径——写大量测试、组织多轮评审、小心翼翼地分阶段推进——因时间、人力和历史债务问题变得几乎不可能。于是,我们决定换一种思路:不依赖脆弱的、可能同样有问题的现有测试,也不依赖人力密集的评审,而是将重构的成败,押注在对“目标规范”的精确描述,以及AI代理执行规范的严格一致性上。

这不仅仅是“用Copilot写代码”,而是一次完整的工程范式转变。我们不再问“AI,帮我改这段代码”,而是定义“这是系统最终必须遵守的新规则,请找出所有违反此规则的地方,并以最小化变更、保持语义一致性的方式修复它们”。整个过程,就像给AI一张精确的施工蓝图,然后让它独自进入一座结构复杂但图纸不全的老建筑进行改造,并且要求改造后建筑的所有力学特性必须符合新蓝图,过程中还不能惊动里面的住户(即不引入运行时错误)。接下来,我就详细拆解我们是如何设计这个流程、选择工具链、应对无数坑点,并最终让这个看似不可能的任务平稳落地的。

2. 核心思路:为什么是“规范优先”与“无监督”?

2.1 困境解析:传统重构方法为何失效?

面对这个717k行的TypeScript代码库,我们最初也考虑过常规手段。但很快发现此路不通。

首先,**“无测试预言机”**是致命伤。所谓“测试预言机”(Test Oracle),简单说就是能判断程序输出是否正确的机制。我们的代码库有测试,但多是陈年的、针对具体实现的单元测试,且覆盖率不均。很多测试本身就和我们要拆除的那个旧架构不变量紧密耦合。用它们来验证重构是否正确,就像用一把本身已经弯曲的尺子去测量新加工的零件——毫无可信度。甚至,运行这些测试通过,反而可能意味着重构没有彻底,因为旧逻辑被保留了。

其次,“无人工代码评审”是现实约束。189个文件散布在前端组件、后端服务、通用工具函数、类型定义等各个角落。让团队逐一评审每个变更,需要消耗数周甚至数月的时间,且人眼评审如此分散且语义复杂的变更(例如,将if (user.role === ‘admin’ || data.ownerId === user.id)改为调用一个新的权限服务接口),极易疲劳和出错,效率与可靠性双低。

最后,变更的规模与一致性要求是核心挑战。这个架构不变量像病毒一样渗透在代码的各个角落,但表现形式各异。有的地方是显式的条件判断,有的是隐式的函数参数传递,还有的是通过类型系统间接约束。手动查找和修改,保证所有地方都统一地切换到新模型,并且不破坏任何隐含的数据流或边界条件,几乎是一个不可能完成的任务。

2.2 范式转变:将问题从“如何改代码”转变为“如何定义正确性”

既然验证和评审的旧路走不通,我们就必须开辟新路。我们的新范式核心是:将人类智慧前置到“规范定义”阶段,将重复、机械、高一致性的代码发现与修改工作交给AI代理。

  1. 精确的规范(Specification)就是最高指令:我们不再给AI看代码片段和模糊的指令。相反,我们花费了大量时间,用结构化的方式定义“目标状态”的规范。这包括:

    • 新架构的接口契约:新的权限服务API是什么样子?它的函数签名、输入输出类型、错误处理方式。
    • 旧模式到新模式的映射规则:每一种旧的权限检查模式(如直接角色判断、属性比较、特定函数调用),对应应该调用新API的哪个方法,参数如何转换。
    • 代码变更的约束条件:例如,必须保持函数的纯性(如果原来是纯函数)、不能改变异步/同步性质、必须处理可能的null/undefined、必须导入正确的依赖模块等。
    • 禁止的模式:明确列出重构后绝对不能再出现的代码模式(如直接访问user.role进行业务逻辑判断)。
  2. AI代理作为规范的执行引擎:我们将上述规范,结合整个代码库的上下文(通过工程化的方式提供给AI),交给AI编码代理。它的任务不再是“理解”业务逻辑,而是“匹配”和“转换”。它像是一个拥有极高模式匹配和代码生成能力,且绝对服从规范的机器人,遍历所有文件,应用我们定义的转换规则。

  3. 一致性作为内置属性:由于所有变更都源于同一套规范,只要规范本身是正确且完备的,那么生成的变更集天然就是一致的。这从根本上解决了人工修改可能出现的疏漏和不一致问题。

这个思路的关键在于,它把最困难、最需要创造性和业务理解的部分(定义“什么是正确的”)留给了人;而把最繁琐、最需要耐心和精确度的部分(在百万行代码中执行“正确”的变换)交给了AI。人的角色从“操作工”和“评审员”,转变为了“架构师”和“规范制定者”。

3. 技术选型与工具链搭建

工欲善其事,必先利其器。要实施这个“规范优先”的重构,我们需要的不是单个工具,而是一套相互配合的工具链。

3.1 AI编码代理的选择:为何不是简单的ChatGPT或Copilot?

市面上有很多AI编码工具,但它们的定位不同。

  • GitHub Copilot / Tabnine:更像是“智能补全”,在单文件、局部上下文下表现优异,但缺乏跨文件、系统性分析和执行复杂规范的能力。
  • ChatGPT (Web界面或基础API):虽然能处理长文本和复杂指令,但难以直接对接代码库、进行精确的文件操作,且对话式交互不适合自动化批处理任务。
  • 专门的AI编码代理(如Cursor的Agent模式、Claude Code、以及一些开源框架):这类工具的设计初衷就是接受高级任务指令,自主规划、浏览代码、编写和修改文件。它们通常具备:
    • 工作区感知:能读取项目中的多个文件,理解项目结构。
    • 自主规划与执行:能将“拆除架构不变量”这样的大任务,分解为“寻找模式A -> 在文件X中修改 -> 寻找模式B -> ...”等一系列具体步骤。
    • 工具使用能力:可以调用代码解析器(如TypeScript编译器API)、静态分析工具、甚至运行简单的脚本来验证假设。

我们最终选择了一个支持长时间运行、具备工作区访问权限、并能通过自定义指令(Custom Instructions)进行强约束的AI代理平台。关键在于,我们能将前面定义的详细规范,以机器可读(同时AI也能理解)的方式,写入代理的“系统指令”或初始上下文,使其成为代理所有行为的根本准则。

3.2 静态分析作为“导航仪”:TypeScript编译器API

让AI代理直接在海量文件中盲目搜索是不现实的。我们需要一个“导航仪”来缩小范围、提供线索。TypeScript编译器API是我们的不二之选。

我们编写了一个Node.js脚本,使用ts-morph(一个对TypeScript编译器API的友好封装)来分析整个项目:

  1. 模式识别:通过查询抽象语法树(AST),快速定位所有可能包含旧架构不变量代码的文件和具体位置。例如,查找所有包含特定属性名(如user.role,data.isPublic)的二元表达式、函数调用或类型引用。
  2. 影响分析:分析找到的代码节点,确定其所属的函数、类、模块,以及它的数据流,帮助判断修改的边界和影响。
  3. 生成“任务清单”:将分析结果输出为一个结构化的JSON文件,其中列出了每个需要审查的代码位置、其上下文、以及根据规范推测出的建议修改方式。这个文件成为了AI代理的主要工作输入。
// 示例:使用 ts-morph 查找特定模式的代码 import { Project, SyntaxKind } from ‘ts-morph’; const project = new Project({ tsConfigFilePath: ‘tsconfig.json’ }); const sourceFiles = project.getSourceFiles(); const violations = []; for (const file of sourceFiles) { const ifStatements = file.getDescendantsOfKind(SyntaxKind.IfStatement); for (const ifStmt of ifStatements) { const conditionText = ifStmt.getExpression().getText(); // 简单的模式匹配:查找包含旧权限逻辑的条件 if (conditionText.includes(‘user.role’) && conditionText.includes(‘admin’)) { violations.push({ filePath: file.getFilePath(), line: ifStmt.getStartLineNumber(), codeSnippet: ifStmt.getText(), suggestedAction: ‘replace_with_permission_service_check’ }); } } } // 输出 violations 到 tasks.json

注意:静态分析脚本的规则需要精心设计,宁可“误报”(多找一些),不可“漏报”。AI代理在后续阶段可以过滤误报,但漏报意味着遗留的架构债务。

3.3 验证与安全网:尽管“无测试预言机”,但并非“无验证”

我们虽然不依赖现有测试作为预言机,但必须建立新的、轻量级的验证机制,作为重构的安全网。

  1. 类型检查作为第一道防线:TypeScript的核心优势在此凸显。任何不符合类型规范的修改,都会在编译阶段(或通过tsc --noEmit)立即暴露。我们确保AI代理的每一次修改后,都运行一次全项目的类型检查。这是成本最低、反馈最快的正确性校验。
  2. 关键路径的集成测试快照:我们挑选了5-10个代表核心用户旅程的端到端集成测试(或API测试),虽然它们可能也包含旧逻辑,但我们关注的是重构后这些测试的行为是否发生变化。我们运行这些测试,并对比重构前后的输出(对非确定性输出做标准化处理)。任何差异都需要人工介入审查,判断是预期的行为更新还是意外的回归。
  3. 自定义的“规范一致性”检查脚本:我们编写了另一套脚本,专门用于扫描重构后的代码,检查是否还有“禁用模式”的残留。这相当于用代码来检查代码是否符合规范,是闭环的关键一步。

这套工具链形成了“分析 -> 规划 -> 执行 -> 验证”的自动化循环。静态分析提供目标,AI代理执行修改,类型检查和自定义脚本提供即时反馈。

4. 实操流程:拆解189个文件的系统性变更

有了清晰的思路和工具,接下来就是实战。整个过程是高度迭代和自动化的。

4.1 阶段一:规范制定与“黄金样本”创建

这是最耗费心力的阶段,也是决定成败的阶段。我们组织了核心架构师和资深开发,花了几天时间做这件事:

  1. 枚举旧模式:通过代码抽样和静态分析,我们归纳出旧架构不变量在代码中的8种主要表现形式(Pattern)。例如:角色直接判断模式属性比较模式特定工具函数调用模式等。
  2. 定义新契约:设计并敲定了新的权限服务接口(PermissionService)。它提供如canView(dataId: string, user: User): Promise<boolean>这样的方法,将业务逻辑从分散的条件判断收拢到服务内部。
  3. 编写转换规则:为上述8种旧模式,逐一编写精确的转换规则。这不仅仅是“把A换成B”,而是要考虑上下文。
    • 规则示例
      • 旧模式if (user.role === ‘admin’ || user.role === ‘editor’) { … }
      • 转换规则
        1. 确保当前文件已导入PermissionService(如未导入,则添加import { permissionService } from ‘@/services/permission’;)。
        2. 将条件替换为if (await permissionService.canAccess(‘someResource’, user)) { … }
        3. 注意:原条件可能包含更复杂的逻辑(如与&&结合),需要将整个权限相关子表达式识别并替换,同时保留其他业务逻辑条件。
        4. 如果原代码在非异步上下文中,需要考虑是否将外层函数改为async,或使用立即执行的异步函数。
  4. 创建“黄金样本”:我们手动挑选了几个具有代表性的文件,人工应用这些转换规则进行修改。这些修改后的文件,以及详细的修改说明,成为了后续AI代理学习的“标准答案”和验证其输出的“参考模板”。

4.2 阶段二:任务分解与AI代理引导

我们将静态分析生成的“任务清单”导入到一个项目管理工具(简单点可以用Markdown文件)中。然后,我们不是让AI代理一次性处理所有189个文件,而是采用“分而治之”的策略:

  1. 按模块/目录分组:将文件按功能模块或目录分组,每次交给AI代理一个小组(例如10-15个文件)。这降低了单次任务的复杂度,也便于定位问题。
  2. 提供丰富的上下文:给AI代理的指令中,除了具体的转换规则,还包括:
    • “黄金样本”文件的链接或内容。
    • 当前模块的简要说明。
    • tsconfig.json的路径和项目根目录。
    • 严格的指令:“只修改与任务清单中描述的模式相关的代码。对于不理解的代码或边缘情况,保持原样并记录下来。每次修改后,运行npm run type-check确保没有类型错误。”
  3. 启动代理,监控执行:启动AI代理,让它开始处理第一个任务组。我们会在旁观察其“思考过程”(一些代理会输出推理步骤),看它是如何定位代码、应用规则的。初期需要较多的人工干预和指令调优。

4.3 阶段三:变更应用与自动化验证

AI代理会对每个文件提出具体的修改建议(通常是生成一个diff)。我们的流程不会让它直接写入文件,而是:

  1. 生成变更集(Patch/Diff):AI代理输出标准格式的diff。
  2. 自动化验证流水线
    • 步骤1:应用Patch:通过脚本将diff应用到工作副本。
    • 步骤2:运行类型检查:立即执行tsc --noEmitnpm run type-check。如果失败,则此次修改被驳回,记录原因,工作副本回滚。
    • 步骤3:运行规范一致性检查:运行我们自定义的脚本,检查修改后的代码是否完全符合新规范,且没有禁用模式的残留。
    • 步骤4:运行关键集成测试:对修改所影响的模块,运行相关的集成测试,对比快照。
  3. 人工抽查与确认:对于通过所有自动化验证的变更,我们设置了一个抽样率(例如20%),由开发人员随机抽查diff,重点查看复杂逻辑的修改是否正确。这个抽查不是为了找语法错误,而是为了发现自动化验证无法捕捉的语义逻辑偏差。例如,AI是否错误地理解了某个条件判断的业务含义。

通过这个流程,大部分简单、模式清晰的修改都能自动通过并合并。只有少数复杂案例会进入人工处理队列。我们将人工处理这些案例的经验,反过来补充到“转换规则”或“黄金样本”中,形成正向循环,让AI代理越用越聪明。

5. 踩坑实录与核心经验

这个过程绝非一帆风顺,我们踩了无数的坑,也积累了许多宝贵的经验。

5.1 常见问题与排查技巧

问题现象可能原因排查与解决思路
AI代理的修改导致类型错误(TS错误)1. 未正确导入新模块或类型。
2. 异步/同步上下文处理不当(如非async函数中使用了await)。
3. 替换代码后,变量作用域或类型推断发生变化。
1.指令强化:在给AI的指令中明确要求“修改前检查并确保必要的导入语句存在”。
2.上下文提供:将项目中常用的导入语句模式作为示例提供给AI。
3.验证前置:在AI输出diff后,在独立环境应用并运行类型检查,通过后再进入主流程。
修改后代码功能异常(逻辑错误)1. AI错误理解了旧代码的业务逻辑,替换了不该替换的部分。
2. 转换规则对于某个边缘情况定义不完整。
3. 权限判断与其他业务条件耦合过紧,AI未能正确拆分。
1.提高模式匹配精度:优化静态分析脚本,提供更精确的代码上下文(如前后的函数名、变量名)给AI。
2.“黄金样本”覆盖边缘案例:将人工处理的复杂案例加入“黄金样本”,供AI学习。
3.人工抽查重点区域:对业务核心、逻辑复杂的文件(如订单处理、支付流程)提高人工抽查比例至100%。
AI代理陷入循环或执行无关操作1. 初始指令过于宽泛或存在歧义。
2. AI的“思考”过程出现偏差,开始解决一个不存在的问题。
1.指令具体化、原子化:将大任务拆分成更小、更具体的子任务。例如,不是“重构权限模块”,而是“在src/services/order.ts文件中,将第45-60行基于user.role的判断,替换为调用permissionService.canViewOrder”。
2.设置操作边界:明确告诉AI“只修改指定行号的代码”,“不要创建新文件”,“不要修改package.json”。
3.及时中断与调整:一旦发现AI行为异常,立即停止当前会话,分析其推理日志,调整指令后重新开始。
静态分析产生大量误报/漏报1. 分析规则太宽泛或太严格。
2. 代码中存在大量别名、间接引用或动态属性访问。
1.迭代优化分析脚本:这是一个持续的过程。用AI修改的实际结果来反哺分析脚本,调整AST查询模式。
2.结合多种分析手段:除了语法匹配,可以简单尝试数据流分析,追踪关键变量的来源和去向,减少误报。
3.接受不完美:设定一个可接受的误报率(如15%),AI代理和后续验证流程可以处理掉这些误报。

5.2 核心心得与避坑指南

  1. 规范的质量决定天花板,工具决定效率:花在精确定义规范上的时间,会在后续节省数十倍的人工审查和调试时间。模糊的指令只会导致混乱的结果。务必把规范写成机器可解析、无歧义的描述。
  2. AI代理是“超级实习生”,不是“资深架构师”:不要指望AI能理解你模糊的业务意图。你必须像指导一个极其聪明但缺乏业务背景的实习生一样,给出一步步明确、可操作、带示例的指令。它的价值在于不厌其烦、高度一致地执行清晰规则。
  3. 验证网必须比执行网更密:在“无测试、无评审”的设定下,自动化验证就是生命线。类型检查、规范一致性检查、关键路径测试,这三层网必须紧密。任何一层的疏漏都可能导致缺陷潜入生产环境。
  4. 保持版本控制的可追溯性:每一个由AI代理生成的变更集,都必须以独立的提交(或Pull Request)形式进入版本库,并且提交信息必须标准化,包含任务ID、修改模式、关联的规范版本等信息。这为回滚、审计和问题追踪提供了基础。
  5. 人的角色是升级了,而非被替代了:工程师从繁琐的代码查找和修改中解放出来,投入到更有价值的工作中:设计更合理的架构、制定更精确的规范、处理AI无法解决的复杂边缘案例、以及进行高层次的语义审查。这次经历让我深刻体会到,AI不是取代开发者,而是将开发者的工作重心推向价值链的更高端。

这次“规范优先”的重构之旅,就像一场精心策划的外科手术。AI代理是那把锋利、稳定、不知疲倦的手术刀,而开发者团队则是制定手术方案、监控生命体征、处理突发状况的主治医师。最终,我们成功地在可接受的风险和时间内,拆除了那个困扰团队多年的架构肿瘤,为系统的未来演进扫清了障碍。这不仅仅是关于AI编码工具的应用,更是一次关于软件工程方法论的深刻实践。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询