老项目做 vibe coding 改造,别先开写:先把边界、契约和验收跑通
摘要
老项目改造最容易被 vibe coding 带偏:看到一个现象就让 AI 写一个补丁,短期很快,长期却容易把历史行为、隐藏约束和回归风险搅在一起。本文不讨论某个具体兼容项目,而是整理一套更通用的方法:先做边界发现,再把旧行为写成契约,用可逆变更控制风险,最后用证据链和确定性规则验收。
这篇适合谁看
这篇主要写给正在用 AI 辅助改造老项目的开发者、技术负责人和工具链建设者。
你面对的项目可能是一个多年运行的后台页面,也可能是一套无人敢大改的业务流程、一个缺少测试的前端工程,或者一段依赖历史环境的脚本。它们的共同点不是技术栈旧,而是:很多行为已经变成了业务约定,却没有被写进文档和测试。
所以这篇不会展开某个具体适配细节,而是回答一个更通用的问题:
当 AI 能很快给出代码时,我们怎样避免把老项目改得更不可控?我的结论是:老项目 vibe coding 不能以“写代码”为起点,而要以四个交付物为起点。
边界地图 -> 行为契约 -> 可逆变更记录 -> 证据验收清单代码只是这四件事之后的结果。
一开始最容易误判:老项目不是代码仓库,而是历史行为集合
新项目里,需求通常可以从产品说明、接口文档、设计稿和测试用例里推出来。
老项目不一样。很多真正重要的规则藏在这些地方:
- 某个字段为什么能为空;
- 某个按钮为什么要先弹确认;
- 某个列表为什么默认只查最近一段时间;
- 某个失败提示为什么不能换文案;
- 某个导入流程为什么允许部分成功;
- 某个页面为什么必须保留一个看似多余的隐藏字段;
- 某个旧接口为什么返回字符串,而不是标准 JSON;
- 某个用户习惯为什么已经依赖了一个“历史缺陷”。
AI 不知道这些背景。它看到的是当前代码、当前错误、当前提示词,所以它很容易给出“局部合理”的改法。
例如你告诉 AI:
这个保存按钮点了没有反应,请帮我修复。AI 可能会补事件绑定、改提交逻辑、重构表单校验、增加 loading 状态。每一段代码单独看都可能合理,但它没有回答三个更关键的问题:
- 保存按钮原来在什么条件下允许点击?
- 失败时应该保持旧提示,还是可以换成新提示?
- 这个页面有没有被其他入口复用?
老项目改造的风险不在于 AI 写错一行代码,而在于我们没有先定义“什么叫没改坏”。
通用方法:先产出四个交付物
我现在做老项目改造,会先要求自己把问题压成四个小文档。它们不需要复杂,但必须能被复核。
| 交付物 | 解决什么问题 | 最小内容 |
|---|---|---|
| 边界地图 | 防止不知道影响范围就开改 | 入口、页面族、接口、数据对象、用户角色、外部依赖 |
| 行为契约 | 防止把历史行为当成可随意优化的代码 | 触发条件、输入、输出、副作用、异常分支、不得改变项 |
| 可逆变更记录 | 防止补丁越改越散、难以回退 | 变更点、原因、开关、回滚方式、关联证据 |
| 证据验收清单 | 防止 AI 或人工凭感觉判断成功 | 观测点、命令、截图、日志、断言、人工确认项 |
这四个交付物的作用,是把“边写边试”变成“有边界地探索”。
vibe coding 仍然可以很快,但快的是生成候选方案,而不是跳过判断。
第一步:边界发现,不要让 AI 在未知范围里补丁扩散
老项目里,一个现象经常横跨多个边界。
比如一个“提交失败”可能涉及:
- 页面按钮状态;
- 前端校验规则;
- 表单字段序列化;
- 老接口参数名;
- 后端兼容分支;
- 权限或角色判断;
- 缓存和历史数据;
- 用户原有操作习惯。
如果只盯着报错点,很容易把补丁写在最方便的位置,却不是最应该的位置。
我会先做一张边界地图:
| 边界 | 要问的问题 | 记录示例 |
|---|---|---|
| 入口边界 | 用户从哪里进入?是否有多个入口? | list -> edit、dashboard -> quickEdit |
| 页面边界 | 同一组件是否被多个页面复用? | edit.html、copy.html共用表单 |
| 数据边界 | 哪些字段是展示字段,哪些字段会提交? | displayName展示,entityId提交 |
| 接口边界 | 接口是否容忍旧参数、空值、字符串编码? | status可空,type只能是旧枚举 |
| 权限边界 | 不同角色看到的按钮和流程是否不同? | 普通用户只读,维护用户可编辑 |
| 环境边界 | 行为是否依赖浏览器、插件、缓存或配置? | 本地正常,测试环境缓存旧资源 |
| 习惯边界 | 用户是否依赖某个历史表现? | 失败后保留已输入内容 |
边界地图不追求完整设计,它只回答一个问题:
这次修改最远可能影响到哪里?如果这个问题答不出来,先不要让 AI 扩写代码。
第二步:把旧行为写成行为契约
老项目改造里,“优化”是一个危险词。
很多看起来可以优化的地方,可能早就被用户、数据、接口或脚本依赖了。我们要先把旧行为写成契约,明确哪些可以改,哪些不能改。
一个行为契约可以很轻量:
contract:legacy-form-savescope:page_family:/example/order/*entry:edit-formtrigger:action:click save buttonpreconditions:-required fields have values-user has edit permissioninput:visible_fields:-displayName-amounthidden_fields:-entityId-versionexpected_behavior:success:-submit legacy parameter names unchanged-show success message-keep redirect target unchangedfailure:-keep user input on page-show original validation category-do not retry automaticallymust_not_change:-submitted field names-empty-value compatibility-permission branch-failure message categoryevidence:-request payload snapshot-response status-page state after success-page state after failure这个契约不需要一次写完。它可以随着排查逐步补齐。
但只要写出来,AI 的任务就变了:它不再是“请帮我修一下”,而是“请在不破坏这些契约的前提下,给出最小变更”。
提示词会变得更具体:
请根据下面的行为契约分析修复方案。 要求: 1. 不改变 must_not_change 中的任何行为; 2. 优先给出最小改动; 3. 标出每个改动需要哪条证据验证; 4. 如果证据不足,先列出需要补充的观测点,不要直接写代码。这比直接贴一段报错让 AI 猜,要稳定得多。
第三步:变更必须可逆,不能只看当前页面修好了没有
老项目里最怕“补丁堆叠”。
今天为一个页面加一个兜底,明天为另一个入口加一个特判,后天再补一段兼容逻辑。单看每次都不大,合在一起就会变成没人敢删的逻辑。
所以我建议每个改动都写进可逆变更记录:
| 变更 | 为什么改 | 限制范围 | 如何关闭 | 验收证据 |
|---|---|---|---|---|
| 保留旧参数名 | 接口仍按旧字段读取 | 仅/example/order/* | 配置开关legacyParamMode=false | 请求 payload 对比 |
| 增加空值兼容 | 历史数据存在空枚举 | 仅编辑页保存 | 删除兼容分支或关闭开关 | 空值样本保存成功 |
| 延迟初始化组件 | 旧脚本加载顺序不稳定 | 仅指定页面族 | 恢复同步初始化 | 初始化日志和页面截图 |
可逆不一定等于每个改动都要做功能开关。它至少要回答:
- 这个改动在哪里生效;
- 为什么不能全局生效;
- 如果误伤,怎么快速退回;
- 哪条证据证明它确实需要存在。
如果一个改动说不清这些内容,它就不适合在老项目里扩散。
第四步:AI 负责推理和生成,PASS/FAIL 由证据规则决定
vibe coding 最大的价值,是把探索速度拉起来。
你可以让 AI 帮你:
- 从日志里找异常分支;
- 对比新旧请求参数;
- 推断可能断点;
- 生成最小补丁;
- 补一组回归用例;
- 把现场现象整理成检查清单。
但 AI 不应该替你决定“通过”。
老项目验收至少要分成三层:
| 层级 | AI 可以做什么 | 不能交给 AI 的判断 |
|---|---|---|
| 证据收集 | 整理日志、截图、请求、响应、页面状态 | 不能仅凭描述判断现场已通过 |
| 规则匹配 | 对照契约指出缺失证据和风险 | 不能把推测当成验收结论 |
| PASS/FAIL | 生成检查表、解释失败原因 | 最终结果必须由断言、命令、截图或人工确认项落地 |
一个更稳的验收格式是:
检查项:保存失败后保留用户输入 证据来源:浏览器截图 + 表单字段快照 通过规则:失败提示出现后,displayName、amount、entityId 保持原值 AI 角色:对比快照并指出差异 最终结论:由测试记录人勾选 PASS/FAIL这里 AI 可以辅助观察,但不能绕过规则。
什么时候应该停止写代码,先补观测能力
老项目改造中,有几种情况不适合继续让 AI 写补丁。
| 信号 | 继续写代码的风险 | 应该先补什么 |
|---|---|---|
| 失败现象不稳定 | 每次修复都可能修到假问题 | 版本标记、环境标记、复现步骤 |
| 无法确认旧行为 | 可能把历史契约改没 | 旧版本截图、请求样本、用户确认项 |
| 不知道改动是否生效 | 会把部署问题误判成代码问题 | 构建版本、加载日志、资源校验 |
| 同类问题反复出现 | 会形成散落特判 | 问题分类表、共享适配层、回归矩阵 |
| 修好 A 后破坏 B | 说明边界没有圈住 | 影响范围表、相邻场景测试 |
这些时候,最有效的下一步往往不是继续补代码,而是增加观测点:
- 页面显示当前构建版本;
- 关键流程输出结构化日志;
- 请求和响应可以脱敏导出;
- 重要字段有前后快照;
- 回归清单能标记已验证、待复验、禁止重复修;
- 每个补丁能对应到一个契约和一条证据。
观测能力看起来不像功能,但它能减少大量假成功和假失败。
一个通用的老项目改造流程
下面这套流程适合多数老项目 vibe coding 改造。
1. 记录现象 - 用户动作 - 当前结果 - 期望结果 - 是否稳定复现 2. 画边界 - 入口 - 页面或模块 - 数据对象 - 接口 - 权限 - 环境 3. 写契约 - 触发条件 - 输入输出 - 副作用 - 异常分支 - 不得改变项 4. 让 AI 分析 - 先找断点 - 再给最小改动 - 标出风险 - 不足证据先补证据 5. 小步修改 - 限定范围 - 保留回滚方式 - 记录变更原因 6. 证据验收 - 正向路径 - 失败路径 - 相邻路径 - 旧行为保持 7. 沉淀规则 - 更新契约 - 更新清单 - 更新回归矩阵这套流程的重点不是多写文档,而是让每次 AI 生成的代码都有约束、有边界、有验收。
一个可直接复用的提示词
下面这段可以在老项目改造时直接使用。
你现在是老项目改造审查员。 请不要先写代码,先基于我提供的现象、代码片段、日志和截图,完成以下分析: 1. 这次问题涉及哪些边界? - 入口边界 - 页面或模块边界 - 数据边界 - 接口边界 - 权限边界 - 环境边界 - 用户习惯边界 2. 旧行为契约是什么? - 触发条件 - 输入 - 输出 - 副作用 - 异常分支 - 不得改变项 3. 当前证据能支持哪个判断? - 已确认事实 - 合理推断 - 仍缺失的证据 4. 如果要修改,请给出最小变更方案: - 改动点 - 限制范围 - 回滚方式 - 可能误伤的相邻场景 5. 最后输出验收清单: - 正向路径 - 失败路径 - 相邻路径 - 旧行为保持 - PASS/FAIL 由什么证据决定 如果证据不足,请先列出需要补充的观测点,不要直接生成补丁。这段提示词的核心,是把 AI 从“代码生成器”拉回到“证据和约束的协作者”。
最后的检查清单
每次老项目改造前,我会用下面这份清单做自查:
- 是否知道这次修改的最远影响范围;
- 是否记录了入口、页面、数据、接口、权限、环境边界;
- 是否把旧行为写成了可复核契约;
- 是否明确了哪些行为不能改;
- 是否让 AI 先分析证据,而不是直接写补丁;
- 是否把 AI 的推断和已确认事实分开;
- 是否让每个改动都有范围、原因和回滚方式;
- 是否至少验证了正向路径、失败路径和相邻路径;
- 是否确认加载的是新版本,而不是旧资源或缓存;
- 是否有一条确定性规则决定 PASS/FAIL;
- 是否把新的经验沉淀回契约、清单或回归矩阵。
老项目做 vibe coding,不是不能快。
真正的问题是:如果没有边界、契约和证据,AI 会把“写得快”变成“返工也快”。
把这三件事补上以后,AI 才更像一个可靠的改造助手,而不是一个不断制造局部补丁的自动补全工具。