你身边有没有这种情况:AI工具装了一堆,网页版对话、IDE插件、命令行工具、本地大模型,各管各的,真到要对存量代码动手的时候,却帮不上忙——要么答非所问,要么改完的代码不敢合入,要么关键模块一碰就崩。
这套“什么都会但什么都不精”的用法,就是“散装 AI”。它的问题不在模型能力,而在组织方式:没有统一的技能封装,没有流程编排,每个AI都在单打独斗。这篇想聊的,是我最近一直实践的一种思路——用 SKILL 对存量代码做“微创手术”。把AI能力拆装成一个个可复用的技能单元,再用 workflow 编排成一套完整的手术方案,让每个改动都足够小、足够可验证。适合正在维护老项目的技术团队,也适合一个人扛一条业务线的全栈开发者。
1. 存量代码为什么需要“微创手术”,而不是重写
1.1 存量代码的真实困境
我在不少项目里见过同一种病灶:一个核心函数七八百行,嵌套四五层 if-else,全局变量到处共享,文档约等于零。写这个模块的人可能早就离职了,新接手的人全靠 Ctrl+F 猜逻辑。业务方还在不停提需求,没人敢动这块代码,因为一改就炸。
这种代码叫存量代码。它最大的特点不是“写得很烂”,而是“价值很高但状态很脆弱”。它承载着线上真实业务,出过一次事故就够团队紧张一阵。直接重写?成本太高。业务不等人,需求还在排期,重构窗口通常只有一两周。而且重写风险极大,旧逻辑里的边界条件、隐式依赖、异常分支,很多已经没人说得清了。
这时候真正需要的是“微创手术”:在不改变系统整体结构的前提下,选定一个最小切口,把最危险的那段逻辑拆出来、理顺、替换掉。目标不是“代码变漂亮”,而是“行为不变,结构变安全”。
1.2 “散装 AI”的三次翻车现场
我最早尝试用常规AI对话去处理这种存量改造,失败过很多次,现在回头看,问题非常清楚。
第一次翻车,是让AI帮忙梳理一个支付回调函数的调用链。我贴进去代码片段,AI分析得头头是道,但追问两句就露馅了——它只看到了我贴的那一段,根本不知道别的文件里还有谁在调这个函数。上下文窗口有限,隐式依赖它看不见,最后给出来的影响面报告,我核对完发现漏了整整两个上游调用方。
第二次,是让AI重构一个函数的内部逻辑。它确实改出来了,但把整个函数从头到尾重写了一遍,风格完全变了,连变量命名习惯都换了。diff一出来几百行,我根本没法评审。业务逻辑倒是“等价”的,可这种改动谁敢合进去?
第三次更离谱,AI建议我引用一个工具类,说“项目里已经有了”。实际一查,那个工具类只在某个测试环境的分支里存在。这个错误如果没发现,上线必炸。
这些翻车有个共性:单次对话型AI只适合处理“局部、单文件、一次性”的问题。存量改造是“跨文件、多步骤、需要验证”的复杂工程,散装AI根本接不住。
1.3 “微创手术”的完整解题思路
后来我转变了思路,不再指望一个AI会话搞定一切,而是做三件事:
第一,把AI能力封装成 SKILL。一个SKILL就是一个“工种说明书”,比如“函数影响面分析”“代码安全抽离”“新旧逻辑对比”。每个SKILL只做一件事,输出格式固定,完成标准明确。
第二,用 workflow 把SKILL串起来。就像手术室里有主刀、有麻醉、有器械护士,各有分工,按流程推进。一个SKILL输出结果,交给下一个SKILL作为输入,中间设置人工确认节点,关键步骤停下来复核。
第三,严格控制每次改动的“切口”大小。一次PR只允许改一个文件、几百行以内,所有相关改动必须能在十分钟内评审完。方法论上就是:先切小口,再上夹子,最后缝合,每一步都能回退。
这套思路的核心是:不让AI“决定”系统走向,而是让AI“执行”你设定好的路径。你定方案,AI出力气。
2. SKILL:把 AI 从“临时工”变成“专科医生”
2.1 到底什么是 SKILL
简单说,SKILL就是给大模型写的一份“工种说明书”。它告诉模型:你是什么角色、在什么情况下触发、按什么步骤执行、输出什么格式、做到什么程度算合格。
SKILL、Agent、Workflow,这三个词经常混着出现,我用自己的理解区分一下:
| 概念 | 类比 | 核心特点 | 典型用法 |
|---|---|---|---|
| SKILL | 专科医生 | 单一能力,输入输出明确,可复用 | 函数影响面分析、代码抽取、trace对比 |
| Workflow | 手术流程表 | 定义步骤、节点、分支、人工确认点 | 存量改造流程、发布流程、审查流程 |
| Agent | 带目标的执行团队 | 有自主规划能力,能调SKILL,按目标决策 | 自动修复任务、长链路分析任务 |
实际使用中三者常常嵌套:Agent负责理解目标,Workflow负责流程控制,SKILL负责具体执行。你做存量改造,核心单位其实是 SKILL + 人工确认点,Agent反而是可选项。
2.2 一个 SKILL 的四个核心组成部分
我写过十几个SKILL,踩过不少坑之后总结出来,一份优秀的SKILL定义至少包含五个字段:
- name:技能名,简短,其他流程调用时靠它定位
- description:什么时候用这个SKILL,写清楚触发边界,避免“万金油”
- steps:给模型的执行步骤,必须具体可操作
- done criteria:完成标准,没有这一项,AI会给你一堆“我觉得差不多了”的结论
- output:输出格式,报告型输出最好固定模板
新手最容易犯的错误,是description写得太泛,比如“分析代码”。这句话等于没说。AI触发之后不知道该往哪使劲。我后来改成“当用户要求评估某个函数的改造影响时,触发本SKILL,输出调用图、风险点和推荐切口”,效果立刻不一样。原因很简单:模型在模糊指令下会主动“发挥”,而你根本不需要它发挥,只需要它执行。
2.3 一个最小可用的 SKILL 示例
下面是我最常用的“函数影响面分析”SKILL,你可以直接拿去改。我用的是 YAML 格式,大部分支持SKILL的工具都能兼容。
name: skill-fn-impact-analysis description: > 当用户需要对存量代码中的某个函数做改造前评估时,触发本技能。 适用范围:重构、接口拆分、逻辑迁移前的调用链分析。 triggers: - 包含“影响面”“调用链”“能不能改”“分析这个函数”等意图 - 输入内容包含函数名或文件路径 steps: 1. 定位目标函数所在的文件,解析文件AST 2. 生成目标函数的函数调用图(正向:它调用了谁;反向:谁调用了它) 3. 标记外部依赖(数据库、MQ、Redis、第三方接口) 4. 按影响程度输出改造风险等级:高/中/低 5. 推荐最小改造切口和需要人工复核的点 done_criteria: - 已输出调用图 - 已输出风险等级列表 - 已列出至少3个需要人工复核的关注点 output: | ## 影响面报告 ### 函数调用图 ... ### 风险等级列表 - [高/中/低] 风险项描述 ### 推荐改造切口 ...看到这个结构你可能已经发现,它和普通prompt最大的区别是多了“输出格式”和“完成标准”。有了这两项,AI就不会给你一个开放性答案,而是交一份格式固定、便于人工复核的报告。生产环境里,这份报告可以直接进文档系统存档。
2.4 把 SKILL 变成团队资产
现在很多开源工具链里都有人分享现成的SKILL仓库,比如针对前后端重构、代码扫描、测试生成的技能包。你可以直接拿过来改,不需要从零发明。但我有两点建议。
不要直接照抄。别人写的SKILL,触发条件和输出格式都是按他的项目来的。你抄过来,先检查description里有没有“针对XX框架”“适用于XX场景”等限定词,不符合你们项目现状就改掉。
SKILL需要定期维护。代码规范变了、技术栈升级了、工具链换了,SKILL里的步骤也得跟着改。我每个季度会把高频使用的SKILL过一遍,像维护测试用例一样维护SKILL。这样才能保证AI“医生”手里的手术手册,永远和真实手术室一致。
3. 编排:让多个SKILL有条不紊地对存量代码下手
3.1 编排的核心:一次大改造拆成多个“小手术”
SKILL解决的是“单个动作”的问题,但存量改造从来不是单动作,而是完整流程:评估影响面 → 抽离逻辑 → 双写对比 → 人工确认 → 切换主逻辑 → 清理旧代码。每个环节都必须有序执行,前一步的输出是后一步的输入。
我习惯把这个流程画成一张编排图,但不是在白板上画完就完,而是把它落成一份机器可执行的 workflow。选型上我通常用 JSON 或 YAML 格式的编排描述文件,里面定义每个节点的类型、依赖关系、输入输出和人工确认要求。
3.2 实战编排:旧模块微服务化改造的workflow样例
下面是我在做一个远古支付模块改造时实际用过的编排结构,做了隐私处理,但流程是完整的。
{ "workflow": "legacy_payment_refactor", "nodes": [ { "id": "n1", "skill": "skill-fn-impact-analysis", "input": {"target": "PaymentService.confirm()", "source": "src/payment/PaymentService.java"}, "next": ["n2"] }, { "id": "n2", "type": "human_confirm", "purpose": "确认影响面分析结果,核对调用图是否遗漏上游依赖", "next": ["n3"] }, { "id": "n3", "skill": "skill-code-extraction", "input": {"source": "src/payment/PaymentService.java", "extract_target": "channel_dispatcher"}, "next": ["n4"] }, { "id": "n4", "skill": "skill-dual-run-compare", "input": {"legacy": "PaymentService.confirm()", "refactored": "PaymentDispatcher.dispatch()"}, "next": ["n5"] }, { "id": "n5", "type": "human_confirm", "purpose": "对比双写日志,确认新旧逻辑在真实流量下行为一致", "next": ["n6"] }, { "id": "n6", "skill": "skill-cutover-and-cleanup", "input": {"switch": "PaymentService.confirm()", "retain_rollback": "true"}, "next": [] } ] }这个workflow跑起来大概是这样的。
n1节点先执行影响面分析SKILL,产出调用图和风险清单。完成后,流程停在n2人工确认节点——不是自动往下冲,必须由人点了“确认无误”,才进入n3抽离逻辑。
n3抽离完成之后不会立刻切流量,而是进入n4双写对比。新旧逻辑同时跑,把所有不一致的日志记下来。n5又是人工确认,等确认一致了,n6才做最终切换,并且保留回滚开关。
这套流程最核心的设计是两道人工确认节点。为什么必须停?因为存量改造最大的风险不是“AI改错了”,而是“AI没改错但你不知道它改没改错”。机器自己写的代码,必须有真实流量验证,而这个验证结果必须由懂得业务的人来看。
3.3 人工确认点怎么设计
人工确认点不是越多越好,每停一次,改造周期就拉长一天。我目前习惯只设两三个关键卡点。
第一,影响面确认。AI说“这个函数只有3个调用方”,你得核实一下。环节出错了后面全错。
第二,双写对比确认。这是最重要的一道卡口。新旧逻辑跑了几天,不一致日志有没有收敛到零,必须人眼看过。
第三,切换后的监控确认。上线后观察一段时间,确认无异常才能清理旧代码。有些关键模块我甚至要求保留旧逻辑一个月再删。
人工确认节点上,我会额外加两个字段:expected_output(期望看到的结果),rollback_action(确认失败时如何回退)。比如n2节点,期望看到的是“调用图无遗漏、风险清单完整”,只要有一条不满足,流程直接打回n1重新分析。
3.4 怎么判断手术是否成功
编排最后需不需要有验收标准?太需要了。没有验收标准的手术,切完之后没法缝合。我关心四个指标:
- 行为一致性指标:双写对比期间,新旧逻辑输出不一致的次数,必须为0
- 性能指标:改造后P50/P99延迟不劣化,甚至更好
- 回归测试通过率:全量回归测试通过率100%,不允许红改绿
- 单Pr体量控制:每次合入的diff控制在300行以内,超过就拆
这四个指标备齐,才算一个合格的“微创手术”。在编排文件里,我会用metadata字段记录这些指标,在各节点执行完后自动汇总到一份验收报告里。
4. 实战记录:用SKILL编排对订单模块做“微创手术”
4.1 目标模块背景
讲一个我最近做完的真实案例。项目是一个老订单服务,里面有一个订单确认函数,650行,支付渠道6种,每种渠道都有自己的回调处理参数、异常分支、重试逻辑。全部堆在一个函数里,if-else嵌套最深的地方有5层。
这个模块之前出过两次线上事故,都因为有人改动某个渠道的回调逻辑时,影响到了其他渠道。最严重的一次,某渠道回调超时异常处理判断错了层级,结果所有订单都走到统一异常分支里,大量正常回调被误判失败。
团队一直想把这块重构掉,但不敢动。接到这个任务之后,我没有一上来就写代码,而是先把改造流程跑成了前面说的那个workflow。
4.2 术前准备:建索引、出报告、埋探针
术前准备我做了三件事。
第一件,跑“影响面分析SKILL”,让它输出完整调用图。结果显示这个函数不仅有6个渠道回调入口,还被上游一个定时任务和下游两个通知服务调用。这个结论,团队里没人能凭经验一次性说全。报告出来之后,我根据报告手动确认了每个调用方,确认无遗漏,才进入下一步。
第二件,埋探针。在函数入口、每个渠道分支出口、每个异常分支,都加了一条日志,记录业务单号、渠道编码、耗时。这一步是必须的,没有探针,后面双写对比的数据无从谈起。我把探针代码作为第一个独立PR合入了。
第三件,观察基线。探针上线后跑了3天,收集了约2万条真实流量日志,把每个渠道的路径分布、平均耗时、错误率都统计出来。这是后面判断改造有没有改出问题的对照基线。没有基线的重构,等于闭眼开车。
4.3 手术执行:三步拆解
整个拆解过程分三步,每步都是独立PR,每步合入后系统都可运行。
第一步,抽离配置。把6个支付渠道的参数、回调URL、超时时间、重试次数,全部抽到一个渠道配置表。原函数里这些参数全是魔法数字,散落在各个 if 分支里。抽出来后,原函数代码量少了大约100行。这一步纯搬移,不改变任何行为,理论上零风险。
第二步,引入“渠道处理器注册表”。这是核心步骤。我没有让AI重构整个函数,而是给它定义了一个SKILL,要求它把“渠道分发逻辑”从大函数里原样搬到一个map里,每个渠道一个处理器,接口统一。下面是当时生成的简化结构:
# 渠道处理器注册表(改造后) PAYMENT_CHANNEL_HANDLERS = { "alipay": AlipayHandler(), "wechat": WechatHandler(), "unionpay": UnionpayHandler(), "card": CardHandler(), "balance": BalanceHandler(), "coupon": CouponHandler(), } def dispatch_channel(channel: str, order: Order): handler = PAYMENT_CHANNEL_HANDLERS.get(channel) if not handler: raise UnknownChannelError(channel) return handler.process(order)注意,这个注册表只做分发,不改变每个渠道内部的业务步骤。断言的目的是让AI不能自由发挥重写每个handler。这个SKILL里我明确加了约束:“只允许搬移现有分支代码,禁止重写业务逻辑,禁止改动异常抛出类型”。
第三步,双写对比。我把旧函数和新注册表同时部署上线,每个订单都走两条路径,只比较结果,不对外返回新路径的结果。每天导出不一致日志,连续跑了5天。
这5天里真抓到两个问题。一个是某渠道处理器在搬移过程中,少复制了一个异常分支的回滚操作。另一个是旧代码里有一个全局计数器变量,新代码没有同步更新,导致两者统计数字不一致。这些问题如果在常规重构里,很可能要上线后一段时间才暴露。双写对比让它们在测试阶段就现了形。
4.4 术后复盘:数据与经验
改造完成后,我复盘了一下效果。
主函数从650行减到不到200行,剩下的主要是入口编排和调用外部服务。每个渠道的处理器独立成类,单文件最长80行。全量回归测试通过,双写对比不一致日志归零,p99耗时不升反降,少了大约12%——因为原来每次处理所有渠道共用同一份全局锁,现在独立调度可以并行了。
经验上最重要的是两条。
第一,人工确认不能省。双写对比第3天出现不一致日志时,如果流程是自动往下走的,这个bug就会直接溜进生产环境。停下来看结果,是这套流程最大的价值。
第二,SKILL里的“禁止重写”约束极其关键。第一次跑SKILL时,我没加这条约束,AI真的把某个渠道的回调逻辑重写了,还在注释里写“优化了异步处理”。我花了一个小时才对比出来差异。后来我在所有涉及存量代码搬运的SKILL里,都用黑体字强调一条铁律:不准顺手“优化”,只准原样搬移。除非你在编排里单独安排一个“优化SKILL”,否则任何隐式的小改动都可能导致线上故障。
5. 避坑指南与排查技巧实录
5.1 高频问题速查表
这套玩法的坑,我在项目里基本都踩过一遍。下面这张表整理了我遇到的典型问题、可能原因和排查方向,你可以直接存着。
| 问题现象 | 可能原因 | 排查方向 |
|---|---|---|
| SKILL没触发,AI答非所问 | description写得太泛,或trigger条件太窄 | 检查description是否包含明确的触发意图词,补一句“当用户提到XX时使用” |
| 输出格式不统一,每次都不一样 | SKILL缺少output字段的强约束 | 把输出格式写死成模板,要求AI严格按模板输出 |
| AI改完的代码风格突变 | SKILL里没写“最小变更原则” | 在steps里明确追加“禁止重写未指定代码” “保持原有命名风格” |
| 影响面分析漏上游调用方 | 分析SKILL只看了单文件 | 升级SKILL:要求必须解析全局符号索引,或者正反两个方向都找调用关系 |
| 双写对比不一致日志太多 | 探针日志未加采样,或对比逻辑本身有误 | 先确认探针埋点位置正确,再逐步缩小对比范围到核心路径 |
| 编排流程卡死,不推进 | 人工确认节点超时机制缺失 | 给每个human_confirm节点配置超时提醒和默认回退路径 |
| 合入后出现新bug | 双写对比时间窗口太短,没有覆盖完整业务周期 | 拉长对比窗口,至少覆盖一个完整业务周期,比如日报类业务跑满一周 |
5.2 我踩过的坑:三个反面案例
第一个坑,是SKILL没写完成标准。最早的版本,我给影响面分析SKILL只写了“输出分析报告”,没有done_criteria。结果AI跑完后输出一个“分析完成”的结论,没有任何可执行内容。我去看日志,发现它自己认为“已经回答完毕了”。加上 “必须输出调用图+风险清单” 这条完成标准后,问题立刻消失。
第二个坑,是过期索引导致分析错误。有一次存量改造,我依赖一份调用关系索引文件,但那索引是两个月前生成的,中途有别的团队重命名了一个服务,索引里全乱了。分析SKILL给出的调用图少了一个重要依赖,差点出事。复盘后发现,SKILL的steps里没有加“索引时效校验”这一步,后来我改为每次跑分析前强制重新生成索引。
第三个坑,是编排节点之间没有输入输出校验。n3抽离SKILL的结果,直接作为n4双写SKILL的输入,但中间没有字段校验,结果某次抽离后输出字段名变了,n4直接跑不了。后来我给每个编排节点加了一层“输出schema检查”,这活儿可以用一个小脚本,也可以让n4的第一步先做输入自检。别指望SKILL之间天然兼容,机器之间也要契约。
5.3 什么情况不适合用 SKILL 编排
不吹这套方案万能。有些场景,SKILL编排并帮不了你。
存量代码压根跑不起来、连遵循业务逻辑的完整回归环境都没有。这种情况下,双写对比、探针埋点都无从谈起,AI再强也只能“盲改”。先把构建、测试环境修好,再来谈智能化改造。
架构级重构,比如要从单体直接切到微服务、或要换核心存储。这种结构性变化影响力太大,“微创手术”的切口根本不可能控制得这么小。该做的系统设计、数据迁移规划,都得单独推进。
团队没有能力配合做人工确认。这套流程很依赖“有人能看懂AI输出的影响面报告并做判断”。如果团队里没人对这块代码足够熟悉,那SKILL的介入反而会增加风险。毕竟,手术方案能操作的前提是主刀医生对这个器官够了解。
5.4 我的日常使用建议
现在我接手任何一个存量改造项目,都不会先去碰代码,而是先花小半天把两样东西定下来:三个SKILL(影响面分析、代码抽离、双写对比)和一张编排图。SKILL可以复用,编排图可以根据项目改节点。
然后严格按流程走:配置和探针作为第一批合入,抽离和双写对比作为第二批,切流和清理作为第三批。每批之间有人工确认。这个节奏会让团队里所有人心里都踏实,因为每一步都是可验证的、能回退的。
最后说一个小技巧。双写对比那段时间,我会每天固定一个时间,用一套脚本把不一致日志自动汇总成邮件发出来。如果连续3天累计不一致次数为0,再进入切换阶段。这个“连续3天为0”的门槛,比只看一天数据稳得多。存量改造的隐患,很多是低频异常,一天不漏不代表一周期不漏,时间窗口拉足,准没错。