作者:vivo IT 技术团队- Liu Heng
面对涉及存量代码变更的需求,挑战不在于编码,而在于如何安全、精准地修改。本文通过构建一个由1个编排器和多个单职责Skill组成的AI协作流水线,将影响分析、规则沉淀、埋点和回归检查等隐性工作流程化,确保在实现功能的同时,对存量代码实现最小侵入式改造。
1分钟看图掌握核心要点👇
一、典型场景:一个看似简单的存量代码变更
某业务上报模块需要新增"图片上传前压缩"功能。需求本身简单,但作为存量修改,真正的复杂性在于:
- **影响范围不确定:**报量中涉及上传图片的场景不止一处,具体要改哪一处?同一个上传组件被多个父组件共用,改了这一处,其他调用方会不会受影响?
- **逻辑分支复杂:**单文件上传和批量上传两条路径,压缩逻辑要插入哪个分支、插到哪一层,会不会有副作用?
- **降级策略未定义:**压缩失败时是折回调用方、静默降级上传原文件,还是提示用户?会不会导致上传功能不可用导致稳定性风险?边界值等于 3MB 或 10MB 时算哪边?
- **效果难以量化:**压缩功能上线后,没有埋点就无法证明它在起作用。压缩阶段耗时和上传阶段省下来的时间对比,是否实现正向收益?需要数据支撑以验证优化效果。
核心矛盾: 改存量代码,最怕的不是"不会改",而是改完不知道哪里会炸。传统的"打开文件直接改"模式,极易遗漏代码之外的隐性工作:影响面没分析、处理规则没定义、埋点没集成、回归用例没覆盖------任何一个环节的遗漏都可能导致线上事故。
二、解决方案:从"散装AI"到"编排流水线"
为解决上述问题,我们不再依赖零散的AI问答,而是构建了一条可复用的AI辅助开发流水线。其核心思想是:将开发过程中那些"每次都要做、但容易忘"的环节,沉淀为独立的、单职责的AI Skill,并通过一个智能编排器按需串联执行。
这就像为存量代码修改制定了一份标准的"外科手术SOP",让AI作为助手,严格遵循流程,确保每一步都精准、可控。
三、架构设计
整个系统由 1个中央编排器 (dev-workflow) 和 多个独立Skill 构成。编排器负责根据需求类型,智能调度并串联Skill;每个Skill则像流水线上的专用工具,只完成一项特定任务。
3.1 流程编排:两条路径,按需组装
dev-workflow(编排器)
│
├── 判断需求类型(增量 / 存量)
│
├── 增量需求 → 流程 A
│ │
│ ├── Phase 1 --- 搭建骨架
│ │ ├── scaffold-generator → 生成模块目录结构
│ │ ├── api-definition → 生成 API 请求函数
│ │ └── i18n-integration → 生成国际化文案
│ │
│ ├── Phase 2 --- 编写业务代码
│ │ ├── [编写或生成业务逻辑]
│ │ └── monitor-integration → 埋点集成(如需要)
│ │
│ └── Phase 3 --- 验证提交
│ ├── regression-cases → 回归用例生成
│ └── cr-self-check → CR 自查
│
└── 存量修改 → 流程 B
│
├── Phase 1 --- 分析评估
│ ├── impact-analysis → 影响面分析
│ ├── processing-principles → 处理原则提取
│ └── minimal-invasion → 最小侵入分析
│
├── Phase 2 --- 编写代码
│ ├── [实施代码修改]
│ ├── monitor-integration → 埋点集成
│ └── i18n-integration → 国际化文案
│
└── Phase 3 --- 验证提交
├── regression-cases → 回归用例生成
└── cr-self-check → CR 自查
编排器并非机械地运行所有Skill,而是根据需求类型(增量、存量),选择不同的Skill组合与执行顺序,形成两条优化流程:
以流程B为例,下面的状态机图展示了完整的动态执行流转------包括 Phase 内部的 Skill 串行顺序、两个并行分支,以及每个 Phase 结束后的关门确认点:
3.2 技能矩阵:每个Skill的职责与产出
每个 Skill 好比流水线上的每台"机器"。多个 Skill,每个只做一件事 。 它们之间通过"产出物"传递数据------上游 Skill 的输出,就是下游 Skill 的输入。
**设计思路:**每个 Skill 都是独立的 Markdown 文件,既能被编排器调度,也能单独使用------例如:只想分析影响面?直接调 impact-analysis;紧急 hotfix 可以跳过 Phase 1,直接进 Phase 2。
3.3 编排机制:三个机制让流水线不僵化
编排器通过三个核心机制确保流水线灵活可控:
- **智能跳过:**不是每个 Skill 都要跑。编排器根据需求特征自动判断,跳过不适用的 Skill。本次实战中 9 个 Skill 执行了 7 个,跳过了 2 个(scaffold-generator、api-definition)。
- **Phase 关门:**每个 Phase 完成后暂停确认,输出摘要并询问是否继续,让人类保留纠偏的权力。
- **独立可拆卸:**每个 Skill 可独立使用,编排器只是"推荐的串联方式",不是"唯一的使用方式"。
三个机制的设计动机、实现细节和实战效果,详见第四、五章。
3.4 数据流转:Skill 间的接力赛模式
整个流程中最值得关注的,是 Skill 之间怎么传递数据。
这不是通过 API 或数据库,而是通过自然语言形式的产出物在对话上下文中流转:
impact-analysis
│ 输出:"影响面极小,修改封闭在 uploadComponent.vue 内部"
▼
processing-principles
│ 输入:上游结论 → 跳过"跨组件兼容性"检查项
│ 输出:6 条编码规则(含阈值、降级策略、埋点要求)
▼
minimal-invasion
│ 输入:6 条规则 → 确定需要"条件守卫 + 前置处理"两种模式
│ 输出:具体的代码插入方案
▼
[代码实施]
│ 输入:插入方案 → 机械化编写代码
├──▶ monitor-integration(规则第 5 条触发)
├──▶ i18n-integration(Toast 文案触发)
▼
regression-cases
│ 输入:变更的文件和函数 → 生成回归清单
▼
cr-self-check
│ 输入:所有修改的文件 → 逐项检查
│ 输出:CR 自查报告
上游 Skill 的结论会直接影响下游 Skill 的行为。 比如:
- impact-analysis 判定"影响面小"→ processing-principles 跳过了"跨组件兼容"和"回滚策略"检查项
- processing-principles 明确了"需要埋点"→ 编排器在 Phase 2 自动激活了 monitor-integration
- processing-principles 没提到"新建模块"→ 编排器跳过了 scaffold-generator
四、实战推演:以"图片压缩"需求跑通流程B
以下以"某业务上报图片压缩"这一典型存量需求,演示流程B如何运行。
开发者输入需求描述后,编排器 dev-workflow 自动分析需求特征。本次"图片上传前压缩"涉及对现有 uploadComponent.vue 组件的修改,编排器判定为存量变更 ,自动路由至流程B,并规划出 Phase 1(分析评估)→ Phase 2(编写代码)→ Phase 3(验证提交)的执行路径。
Agent执行过程
以下按 Phase 逐步拆解,看每个 Skill 具体干了什么、产出了什么。
4.1 Phase 1:分析评估(三个 Skill 严格串行)
Phase 1 的三个 Skill 是有依赖关系的------后者需要前者的输出。所以必须串行执行。
Skill 1: impact-analysis --- 影响面分析
**输入:**需求描述 + 目标文件路径(uploadComponent.vue)
AI 自动扫描了调用链------向上追溯(谁在调用我)、向下追溯(我依赖了谁)、横向扫描(谁和我用同一个 API),最终输出:
结论:影响面极小,修改完全封闭在 uploadComponent.vue 内部。 这一步给了我们动手的信心。
这个结论被传递给下一个 Skill------processing-principles 因此知道不需要考虑"跨组件兼容"的问题。
Agent执行过程
Skill 2: processing-principles --- 处理原则提取
**输入:**需求描述 + impact-analysis 的结论(影响面小,无跨组件变更)
它拿着结构化 Checklist(边界阈值、失败处理、兼容性、并发与顺序、可观测性、回滚策略六大维度)逐项引导确认。中间抛出了两个关键问题:
- "等于 3MB 时算哪边?" → 用户选择:不压缩
- "压缩失败要不要提示用户?" → 用户选择:不提示
最终沉淀出 6 条编码级规则:
亮点:等于 3MB 时不压缩,等于 10MB 时允许上传------这种边界值的决策,如果没提前想清楚,写到一半才发现又得改。
这 6 条规则被传递给下一个 Skill------minimal-invasion 据此决定代码怎么插入。
Agent执行过程
Skill 3: minimal-invasion --- 最小侵入分析
**输入:**目标函数源码 + processing-principles 的 6 条规则
它读取 uploadFileToServer 方法,画出分支结构:
uploadFileToServer(file)
├── if (Array) ──── 批量上传
│ └── forEach → FormData.append → 调用接口
└── else ─────────── 单文件上传
└── FormData.append → 调用接口
然后从 5 种集成策略中选择了两种的组合:
- 条件守卫(规则 2):入口处 10MB 拦截,快速失败
- 前置处理(规则 1/3):FormData.append 之前插入压缩,失败降级返回原文件
输出了具体的代码变更方案:
// 原来
formData.append("file", item.file);
// 改为
const processedFile = await this.processFileWithCompress(item.file);
formData.append("file", processedFile);
只改一个变量名,原有的上传、回调、状态管理全部不动。 这就是"最小侵入"的精髓。
Agent执行过程
**Phase 1 小结:**三个 Skill 像接力赛一样------impact-analysis 递出"影响面",processing-principles 接棒递出"规则",minimal-invasion 再接棒递出"插入方案"。上游的结论直接影响下游的行为:
- impact-analysis 判定"影响面小"
- processing-principles 就跳过了"跨组件兼容"检查项
- processing-principles 明确了"需要埋点"
编排器就在 Phase 2 自动激活 monitor-integration;没提到"新建模块",编排器就跳过了 scaffold-generator。此时编排器暂停,输出摘要并询问是否继续------这是关门机制的第一道确认门。
4.2 Phase 2:编写代码(实施 + 两个辅助 Skill)
拿到 Phase 1 的三份产出,写代码就变得非常机械化。
代码实施:三个文件,一套设计
文件 1:压缩工具函数fileCompress.js
核心设计是 Promise + 回调双通道:
export function compressImage(file, callbacks = {}){
return new Promise((resolve) => {
// ...
new Compressor(file, {
quality: 0.8,
success(result) {
callbacks.onSuccess?.({ duration, originalSize, compressedSize, savedRatio });
resolve(compressedFile); // Promise 通道:返回压缩结果
},
error(err) {
callbacks.onFail?.({ duration, error: err.message });
resolve(file); // 关键:失败也 resolve,不 reject!
}
});
});
}
设计亮点:永远 resolve,永不 reject。 压缩失败时返回原文件,调用方完全不需要 try/catch。这保证了:
- 压缩成功 → 上传压缩后的文件
- 压缩失败 → 上传原文件
- 调用方代码完全一致,零分支
文件 2:集成到 uploadComponent.vue
核心文件 uploadComponent.vue 只改了三处:
原有的 loading 管理、save 触发、错误处理------一行没动。
Agent执行过程
Skill 4: monitor-integration --- 埋点集成
因为处理原则第 5 条要求"埋点监控",编排器自动触发了 monitor-integration Skill。它扫描了 monitorReporter.js 现有的 20+ 个埋点函数,识别出命名模式(report_ 前缀)和公共上报字段(运行环境、用户标识、页面地址),然后在文件末尾追加了 report_imageCompress 函数------风格和已有函数完全一致。
上报字段包括:
status(success/fail)| duration(耗时ms)| originalSize | compressedSize | savedRatio | fileName
**埋点与压缩逻辑的解耦:**压缩工具函数不依赖任何埋点库,通过 callbacks.onSuccess / onFail 暴露钩子:
compressImage(file, {
onSuccess: (data) => report_imageCompress({ status: 'success', ...data }),
onFail: (data) => report_imageCompress({ status: 'fail', ...data })
});
fileCompress.js 是纯工具函数,可在任何模块复用;埋点逻辑留在业务层,由调用方决定是否监控、怎么监控。
Agent执行过程
Skill 5: i18n-integration --- 国际化文案
因为代码中有一处 Toast 提示("文件大小不能超过10MB"),编排器触发了 i18n-integration Skill。它在 zhLang.js中加了一条词条,在 common/index.js 中注册了 key。
Agent执行过程
Phase 2 小结:代码实施阶段的核心特征是机械化------Phase 1 已经定好了"在哪改、怎么改、改多少",这一阶段只是按图施工。三个关键设计决策值得注意:
- fileCompress.js 采用"永远 resolve"模式,压缩失败自动降级,调用方零分支
- uploadComponent.vue 仅改 3 处,原有 loading/save/错误处理一行未动
- 埋点通过 callback 钩子与压缩逻辑解耦,工具函数保持纯粹可复用
编排器根据 Phase 1 的结论自动激活了 monitor-integration(处理原则第 5 条要求)和 i18n-integration(检测到硬编码 Toast),跳过了 scaffold-generator 和 api-definition。此时编排器再次暂停------这是关门机制的第二道确认门。
4.3 Phase 3:验证提交(两个 Skill 可并行)
Skill 6: regression-cases --- 回归用例生成
它分析了本次变更涉及的文件和新增逻辑,生成了 13 条回归用例:
Agent执行过程
Skill 7: cr-self-check --- CR自查
它逐项检查了本次修改的所有文件,区分存量修改的专项检查(分支覆盖、接口兼容、loading 状态、新逻辑失败不阻断原流程)和基础检查,输出:
- 通过项:无 ESLint 错误、无 console.log 残留、所有文案已国际化、所有分支已覆盖、原有接口未变化、新逻辑失败不阻断原流程
- 需关注:监控规则 ID 为占位符(待监控平台申请后替换)
Agent执行过程
Phase 3 小结: 两个验证 Skill 无依赖关系,可以并行执行。regression-cases 从变更文件和新增逻辑中自动推导出 13 条分级用例(P0/P1/P2),覆盖了核心路径、交叉场景和边界条件;cr-self-check 则从代码规范、存量兼容性、埋点完整性三个维度逐项扫描,输出结构化的自查报告。
两个 Skill 的产出互补:回归用例告诉你"该测什么",CR 自查告诉你"代码本身有没有问题"------一个面向测试,一个面向审查,共同构成提交前的最后一道防线。
4.4 执行数据总览
代码变更统计
3 个 Phase、7 个 Skill 执行(跳过 2 个)、6 条处理原则、13 条回归用例、CR 自查全部通过。
流水线产出清单
五、编排设计原则与决策
从架构设计和实战验证中,我们提炼出六条可复用的编排原则。每条原则不仅说明"做什么",更解释"为什么"------没有它会怎样,以及实战中如何体现。
5.1 六条原则
1. 单一职责------做螺丝刀,不做瑞士军刀
- **问题:**如果把多个能力塞进一个"万能 Skill",任何小改动都需要重新调试整个 Skill;不同场景无法按需组合。
- **设计:**每个 Skill 只解决一个问题,独立成 Markdown 文件。编排器负责组合,Skill 只负责执行。
- **实证:**本次实战中,monitor-integration 和 i18n-integration 各自独立触发------如果合并为"辅助集成 Skill",就无法单独跳过某项能力。
2. 接力模式------上游的输出是下游的输入
- **问题:**如果每个 Skill 独立获取信息(如各自重新分析代码),不仅浪费 token,还可能产出矛盾的结论。
- **设计:**Skill 之间通过自然语言"产出物"传递数据,而非 API 或数据库。LLM 天然产出自然语言,无需额外的序列化/反序列化成本。
- **实证:**impact-analysis 输出"影响面极小" → processing-principles 据此跳过"跨组件兼容"检查项 → minimal-invasion 据此选择"前置处理"而非"逻辑替换"。
3. 串行 vs 并行------有依赖的串行,无依赖的并行
- **问题:**如果一律串行,浪费时间;如果一律并行,下游 Skill 拿不到上游结论。
- **设计:**编排器判断 Skill 间是否存在数据依赖。Phase 1 三个分析类 Skill 有严格依赖,必须串行;Phase 3 两个验证类 Skill 无依赖,可以并行。
- **实证:**如果 processing-principles 和 impact-analysis 并行执行,processing-principles 就无法根据影响面结论跳过不适用的检查项,产出会包含大量无关的"跨组件兼容"分析。
4. 智能跳过------不是每个 Skill 都要跑
-
**问题:**如果 7 个 Skill 每次都跑,增量需求也要走完"影响分析 → 处理原则 → 最小侵入"这条存量路径,"走流程"就变成了"走形式"。
-
**设计:**编排器维护一张跳过规则表,根据需求特征和上游产出自动判断:
-
**实证:**本次实战中 9 个 Skill 执行了 7 个,跳过了 scaffold-generator 和 api-definition------编排器检测到没有"新建模块"和"新增接口"的需求特征。
5. 关门机制------每个阶段都有"确认门"
-
**问题:**AI 一口气写完代码,你发现方向就不对,全部要返工。越晚纠偏,成本越高。
-
**设计:**编排器在每个 Phase 完成后暂停,输出摘要并询问是否继续:
✅ Phase 1 完成。摘要:
- 影响面:修改封闭在 uploadComponent.vue 内部
- 处理原则:6 条编码规则
- 插入方案:条件守卫 + 前置处理
是否进入 Phase 2?或者需要调整什么?
-
**实证:**本次实战中,Phase 1 完成后编排器暂停,确认了影响面和插入方案后才进入 Phase 2。如果 Phase 1 的结论有误(如影响面判断遗漏),可以在此时纠正,而不是等 200 行代码写完再推翻。
6. 可拆可组------流水线是"建议",不是"强制"
- **问题:**如果编排器是唯一入口,那只想加个埋点就必须走完整个流程,使用门槛过高。
- **设计:**每个 Skill 可独立调用。编排器是"推荐的串联方式",不是"唯一的使用方式"。
- **实证:**紧急 hotfix 可以跳过 Phase 1 直接进入 Phase 2;其他项目可以复用单个 Skill(如只用 impact-analysis),不需要搬走整个编排器。
5.2 设计决策对比
编排设计的每个选择背后,都有替代方案。以下对比解释了"为什么选这条路":
六、结语
存量代码如同有人居住的老宅,改造之道在于"微创"。本次实践表明,AI的价值不仅在于生成代码,更在于将散落的能力编排成一条严谨、可复用的"开发流水线"。它通过流程的确定性,对抗存量修改中的不确定性风险,让"做得对"从一种运气,转变为一种习惯和必然。
这条流水线并未增加工作的复杂度,而是将那些原本依赖个人经验、容易遗漏的隐性工作显性化、自动化。最终,我们以最小的切口(改动202行代码),安全地嵌入了一个新功能,同时获得了完整的影响分析、清晰的规则定义、可量化的监控以及全面的测试保障------这正是工程化AI协作带来的深层价值。
AI 写代码的能力已经不稀奇了。真正有价值的,是把 AI 的能力编排成一个可复用的流程------让"做得对"变成一种习惯,而不是一种运气。