作者:vivo IT 技术团队- Liu Heng
目录
01. 典型场景:一个看似简单的存量代码变更
02. 解决方案:从"散装AI"到"编排流水线"
03. 架构设计
04. 实战推演:以"图片压缩"需求跑通流程B
05. 编排设计原则与决策
06. 结语
面对涉及存量代码变更的需求,挑战不在于编码,而在于如何安全、精准地修改。本文通过构建一个由1个编排器和多个单职责Skill组成的AI协作流水线,将影响分析、规则沉淀、埋点和回归检查等隐性工作流程化,确保在实现功能的同时,对存量代码实现最小侵入式改造。
1分钟看图掌握核心要点👇
01
典型场景:
一个看似简单的存量代码变更
某业务上报模块需要新增“图片上传前压缩”功能。需求本身简单,但作为存量修改,真正的复杂性在于:
影响范围不确定:报量中涉及上传图片的场景不止一处,具体要改哪一处?同一个上传组件被多个父组件共用,改了这一处,其他调用方会不会受影响?
逻辑分支复杂:单文件上传和批量上传两条路径,压缩逻辑要插入哪个分支、插到哪一层,会不会有副作用?
降级策略未定义:压缩失败时是折回调用方、静默降级上传原文件,还是提示用户?会不会导致上传功能不可用导致稳定性风险?边界值等于 3MB 或 10MB 时算哪边?
效果难以量化:压缩功能上线后,没有埋点就无法证明它在起作用。压缩阶段耗时和上传阶段省下来的时间对比,是否实现正向收益?需要数据支撑以验证优化效果。
核心矛盾:改存量代码,最怕的不是“不会改”,而是改完不知道哪里会炸。传统的“打开文件直接改”模式,极易遗漏代码之外的隐性工作:影响面没分析、处理规则没定义、埋点没集成、回归用例没覆盖——任何一个环节的遗漏都可能导致线上事故。
02
解决方案:
从"散装AI"到"编排流水线"
为解决上述问题,我们不再依赖零散的AI问答,而是构建了一条可复用的AI辅助开发流水线。其核心思想是:将开发过程中那些"每次都要做、但容易忘"的环节,沉淀为独立的、单职责的AI Skill,并通过一个智能编排器按需串联执行。
这就像为存量代码修改制定了一份标准的"外科手术SOP",让AI作为助手,严格遵循流程,确保每一步都精准、可控。
03
架构设计
整个系统由 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组合与执行顺序,形成两条优化流程:
流程类型 | 典型场景 | Phase 1 核心任务 | Phase 2 核心任务 | 关键区别 |
流程A 增量变更 | 新增页面 | 搭建骨架: - 生成目录 - API - 国际化文案 | - 编写业务逻辑 - 埋点 | 跳过分析类Skill |
流程B 存量变更 | 修改通用组件 | 风险分析: - 影响面 - 处理原则 - 最小侵入点 | - 按方案实施修改 - 埋点与国际化 | 最大风险是引入Bug Phase1全力用于风控 |
以流程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
04
实战推演:
以"图片压缩"需求跑通流程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),最终输出:
关联文件 | 关联方式 | 影响评估 |
parentForm.vue | 父组件调用 | props/emit 不变,无影响 |
readonlyPreview.vue | 父组件(只读模式) | 不触发上传,无影响 |
business.uploadFile API | 上传接口 | 入参仍为 FormData,无影响 |
结论:影响面极小,修改完全封闭在 uploadComponent.vue 内部。 这一步给了我们动手的信心。
这个结论被传递给下一个 Skill——processing-principles 因此知道不需要考虑"跨组件兼容"的问题。
Agent执行过程
Skill 2: processing-principles — 处理原则提取
输入:需求描述 + impact-analysis 的结论(影响面小,无跨组件变更)
它拿着结构化 Checklist(边界阈值、失败处理、兼容性、并发与顺序、可观测性、回滚策略六大维度)逐项引导确认。中间抛出了两个关键问题:
"等于 3MB 时算哪边?" → 用户选择:不压缩
"压缩失败要不要提示用户?" → 用户选择:不提示
最终沉淀出 6 条编码级规则:
# | 原则 | 规则 |
1 | 触发阈值 | >3MB 触发压缩,≤ 3MB 直接上传 |
2 | 大小上限 | >10MB 拒绝上传,Toast 提示 |
3 | 失败降级 | 压缩异常 → 静默上传原文件 |
4 | 用户感知 | 压缩失败不提示,对用户完全透明 |
5 | 埋点监控 | 成功/失败状态 + 压缩耗时 |
6 | 接口兼容 | 不改变 props/emit/API 接口 |
亮点:等于 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 只改了三处:
改动位置 | 改动内容 | 改动量 |
文件头部 | 新增 2 行 import | +2 行 |
uploadFileToServer 入口 | 新增 10MB 守卫 | +8 行 |
FormData 构建前 | 原 item.file → processedFile | 改 2 处 |
methods 中 | 新增 processFileWithCompress | +35 行 |
原有的 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 条回归用例:
优先级 | 示例场景 | 预期 |
P0 | 上传11MB 图片 | Toast 提示"文件大小不能超过10MB" |
P0 | 上传 5MB 图片 | 自动压缩后上传,埋点记录 success |
P0 | 上传 1MB 图片 | 不压缩,直接上传 |
P1 | 批量上传混合大小 | 各自按规则处理 |
P1 | 只读模式预览 | 正常预览(行为不变) |
P2 | 上传正好 3MB | 不压缩 |
P2 | 上传正好 10MB | 允许上传 |
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 执行数据总览
代码变更统计
文件 | 操作 | 变更行数 |
src/utils/fileCompress.js | 新增 | +119 行 |
src/utils/monitorReporter.js | 修改 | +21 行 |
src/views/business/components/uploadComponent.vue | 修改 | +60 行 |
src/lang/libraryFile/zhLang.js | 修改 | +1 行 |
src/lang/common/index.js | 修改 | +1 行 |
合计 | 5 个文件 | +202 行 |
3 个 Phase、7 个 Skill 执行(跳过 2 个)、6 条处理原则、13 条回归用例、CR 自查全部通过。
流水线产出清单
Phase | Skill | 产出 |
1 | impact-analysis | 影响面清单(3 个关联文件,均无影响) |
1 | processing-principles | 6 条编码级处理原则 |
1 | minimal-invasion | 插入方案(条件守卫 + 前置处理) |
2 | monitor-integration | 埋点函数 report_imageCompress |
2 | i18n-integration | 中文词条 + 公共模块注册 |
3 | regression-cases | 13 条分级回归用例 |
3 | cr-self-check | CR 自查报告(通过) |
05
编排设计原则与决策
从架构设计和实战验证中,我们提炼出六条可复用的编排原则。每条原则不仅说明“做什么”,更解释“为什么”——没有它会怎样,以及实战中如何体现。
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 每次都跑,增量需求也要走完“影响分析 → 处理原则 → 最小侵入”这条存量路径,“走流程”就变成了“走形式”。
设计:编排器维护一张跳过规则表,根据需求特征和上游产出自动判断:
SKill | 跳过条件 |
scaffold-generator | 不新建模块时跳过 |
api-definition | 不新增接口时跳过 |
impact-analysis | 纯增量需求时跳过 |
processing-principles | 逻辑简单无歧义时跳过 |
minimal-invasion | 纯增量需求时跳过 |
i18n-integration | 无用户可见文案变更时跳过 |
monitor-integration | 用户明确不需要埋点时跳过 |
regression-cases | 不涉及代码修改时跳过 |
cr-self-check | 不涉及代码修改时跳过 |
实证:本次实战中 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 设计决策对比
编排设计的每个选择背后,都有替代方案。以下对比解释了“为什么选这条路”:
设计决策 | 本文选择 | 替代方案 | 不选替代方案的原因 |
Skill 粒度 | 7 个单职责 Skill | 1 个大提示词 | 大提示词不可复用、难调试、无法单独跳过某项能力 |
数据传递 | 自然语言产出物 | 结构化 API / JSON | LLM 天然产出自然语言,API 对接需额外序列化成本;且自然语言更灵活,能传递“影响面极小”这类模糊结论 |
流程控制 | 编排器推荐 + 关门确认 | 全自动执行 | 存量修改风险高,人必须保留纠偏权;全自动执行无法在 Phase 1 发现方向错误时及时纠偏 |
Phase 划分 | 3 阶段(分析→实施→验证) | 不分阶段 | 不分阶段则无法做阶段性质量把控,也失去了关门机制的锚点 |
06
结语
存量代码如同有人居住的老宅,改造之道在于"微创"。本次实践表明,AI的价值不仅在于生成代码,更在于将散落的能力编排成一条严谨、可复用的"开发流水线"。它通过流程的确定性,对抗存量修改中的不确定性风险,让"做得对"从一种运气,转变为一种习惯和必然。
这条流水线并未增加工作的复杂度,而是将那些原本依赖个人经验、容易遗漏的隐性工作显性化、自动化。最终,我们以最小的切口(改动202行代码),安全地嵌入了一个新功能,同时获得了完整的影响分析、清晰的规则定义、可量化的监控以及全面的测试保障——这正是工程化AI协作带来的深层价值。
AI 写代码的能力已经不稀奇了。真正有价值的,是把 AI 的能力编排成一个可复用的流程——让"做得对"变成一种习惯,而不是一种运气。
END
猜你喜欢
从混乱到秩序:我如何搭建一套「规范驱动」的 AI 协作开发体系