AI 编程工作流:从需求拆解到上线回滚

我用 AI 写代码四年,踩过的最大坑不是「模型不会写」,而是「AI 说写完了,就以为它真写完了」。前两年我反复在一个循环里转:AI 生成一段代码 → 看一眼能跑 → 提 PR → 测试环境报错 → 修一下 → 上线生产 → 凌晨收到告警 → 紧急回滚 → 怪 AI 不靠谱。问题从来不在模型,而在我把它当成了「写代码的人」,而它实际是「按指令改文件的人」。该上测试的地方没上,该评审的地方没评,该回滚的地方没准备预案。

这篇文章把这条工作流完整拆开:任务卡拆解 → 读仓库与小步实现 → 真实测试与评审 → 提交/CI/生产三态分离 → 上线后观测与回滚。你照着走一遍,至少能把「AI 写完就上线」的故障率压掉八成。

配套阅读:AI 编程入门实战(从零到第一个能跑的脚本)、AI 编程应用场景(生成、补全、评审、CI/CD 各自用法)、Cursor/Trae/Copilot 对比(工具选型)、Prompt 调试方法(AI 答非所问时怎么定位)、AI 写作工作流(内容侧的同类 SOP)、AI 绘画工作流(图片交付的同类 SOP)、Cursor 进阶技巧(编辑器侧高效用法)、Cursor 入门(基础配置)、Prompt 工程基础(如何写好指令)、AI 编程工具推荐(工具横向对比)、AI 生产力技巧(整体提效清单)、ChatGPT vs Claude(编程模型横向对比)。


交付单位不是「生成的代码」,是「可验证的改动」

把 AI 写代码想成「AI 替我实现了某个功能」,从一开始就跑偏了。AI 真正能稳定交付的,是一个可验证的改动:能跑测试、能通过 lint、能编译通过、能看 diff、能独立回滚。功能、需求、上线是这一连串改动累积起来的结果,而不是单个改动的目标。

这一节先定几件交付前必须想清楚的事。

交付单位 = 一次可回滚的 commit

不要把「实现整个登录重构」当成一个改动。要拆成:

  1. 抽出 user repository 接口(独立 commit,独立测试)
  2. 替换一个调用点为接口实现(独立 commit,独立测试)
  3. 接入新的 token 校验(独立 commit,独立测试)
  4. 跑全量回归 + 上线 + 监控(合并上线)

每一个 commit 都是「可验证的改动」:能跑自己的测试、能独立回滚、能告诉评审人改了什么。AI 在这种粒度下最稳,因为它只需要回答「这个文件、这处逻辑、这个测试」三件事,而不是「帮我重写登录模块」。

AI 不是写代码的人,是按指令改文件的人

AI 没有「我完成了」的判断力,它只有「我生成了 diff」的执行力。你不问,它不说;你不测,它不验证;你不评审,它就当通过。这一条要写进团队肌肉记忆。

任务卡 = AI 时代的 PRD

任务卡(task card)是这套工作流的核心。每接到一个需求,第一步不是打开 IDE 问 AI「怎么实现」,而是把下面八栏填完:

字段含义示例
目标一句话说清这次改动要达成什么「让搜索结果支持按标签过滤」
用户场景谁在什么场景下会用「运营筛选带某个标签的文章」
输入/输出输入什么、输出什么数据结构「输入 tag 列表,输出过滤后的文章 ID」
非目标这次做的事「不做排序、不做全文搜索、不做权限」
验收标准怎么算「做完了」「三种标签组合都返回正确条数,空标签返回全部」
失败处理边界条件 + 异常路径「标签不存在 → 返回空;标签是空字符串 → 返回 400」
参考资料类似实现 / 依赖版本 / 测试命令「参考 article-service 现有 list 接口、用 jest 跑单测」
改动范围大概涉及哪几个文件、哪几个接口「新增 SearchFilter,新增 2 个 endpoint,改 1 个 service」

填完这张卡,AI 才能真正开工。没有这张卡,AI 会按它自己的理解「自由发挥」,三步之后你就会发现它改了八处你没想到的地方。


第一步:读仓库,再下笔

让 AI 写代码之前,最容易被忽略的一步是:让它先读仓库。不读仓库,AI 写出来的代码几乎一定会踩三类坑:

  • 命名不一致:项目里叫 user_id,AI 写成 userId
  • 依赖版本错:项目里用 axios 1.6,AI 写成 fetch 拦截器
  • 风格不一致:项目用 4 空格,AI 写成 2 空格

这三类坑看着小,评审起来全是噪音,会让真正的问题被淹没。

让 AI 先读的清单

把下面这些塞进 prompt,强制 AI 在写代码前读完:

写代码前,先读完下面这些:
1. README.md(项目目标 + 启动命令)
2. 项目根的 AGENTS.md / CLAUDE.md / .cursorrules(如有约定)
3. package.json / pyproject.toml / go.mod(依赖版本)
4. 同目录下相邻的两个文件(参考命名 + 风格)
5. 类似功能的一个实现(如有)
6. 测试运行命令(jest / pytest / go test)

读完列一个「我看到了什么」的小结,再开始改。

「读完列小结」这一条是关键——它强迫 AI 把读到的内容显式说出来,避免它读一半就开始写。读不到位的小结会立刻暴露在评审环节。

用 AGENTS.md 把仓库约定固化

读仓库约定最好固化成文件,不要每次都重复 prompt。常见做法:

  • 项目根放 AGENTS.md:命名规范、错误处理、日志格式、测试要求
  • Cursor 用户用 .cursorrules:IDE 启动时自动注入
  • Claude Code 用户用 CLAUDE.md:会话启动时自动读

固化后,AI 不用每次都「先问」,直接按文件里的约定走。这条对老项目特别重要——约定散在历史 commit 和老员工脑子里,AI 没法自动学到。


第二步:小步实现,每步有 diff

让 AI 改代码,永远不要让它「一次性写完整个模块」。要拆成五步小循环,每步独立评审:

小循环的五步

  1. 数据/API:先定义接口、类型、mock 数据。能跑通「空实现 + mock」。
  2. 核心逻辑:实现主流程,但保持调用方还是 mock 状态。
  3. 调用方/UI:把真实调用方接进去,能跑通端到端。
  4. 错误处理:补异常路径、补日志、补监控埋点。
  5. 文档:更新 README、注释、CHANGELOG。

每一步独立 commit、独立 review、独立测试。AI 一次性写五步,问题会藏在第五步里,到时候回滚都难拆。

每个 diff 必须能独立解释

评审环节会问「这个 commit 改了什么」。让 AI 在每个 commit 之前先输出三件事:

  1. 改动文件清单(新增 / 修改 / 删除)
  2. 每个文件为什么改(一句话)
  3. 怎么验证(跑哪个测试、看哪个 endpoint)

这三件事要写到 commit message 里。AI 写代码容易,写 commit message 反而更暴露它是否真的理解了改动——如果 commit message 含糊,这个 commit 大概率也含糊。

警惕 AI 的「过度实现」

AI 有一个高频毛病:用户没要的功能它会自动加上。比如「加一个搜索过滤」,它可能顺手加上分页、排序、缓存、统计、导出 CSV。这些全是用户没要的需求,每加一个都是新的测试负担和评审负担。

明确告诉 AI:

只实现我列在任务卡里的功能。
不要加排序、不要加分页、不要加缓存、不要加统计、不要加导出。
如果你觉得需要加,先停下来问我。

第三步:真实测试,AI 不能自证正确

测试是这套工作流最容易塌的环节,因为「AI 写完看着对」给人强烈的「已经测过」的错觉。AI 不能自证正确——它没跑过、没编译过、没断言过、没看到失败。

必须跑的真实测试

下面这些必须跑过、看输出、贴日志,才算「改完了」:

测试类型命令检查什么
Lintnpm run lint / ruff check / golangci-lint run命名、风格、import 顺序
类型检查tsc --noEmit / mypy .类型错误、缺失 import
构建npm run build / go build / pytest --collect-only编译/打包是否通过
单元测试jest / pytest / go test ./...业务逻辑边界
集成测试pytest tests/integration跨模块/跨服务
边界测试自定义边界用例空值、极值、并发
失败测试故意输入错误数据异常路径是否走对
回归测试跑完整 test suite没把别的功能改坏

三类必须补的测试

AI 默认不写但必须补的测试:

  1. 边界用例:空数组、空字符串、null、undefined、极大值、极小值
  2. 失败用例:输入不合法数据时是否抛错而不是静默返回错值
  3. 并发用例:两个请求同时改同一资源时是否竞态

这三类 AI 不会主动写,必须在任务卡里显式列

验收标准里必须包含:
- 空输入返回 400,不返回 []
- 重复请求幂等(连续两次只生效一次)
- 并发 100 请求,最终状态一致

测试输出要落到日报

AI 跑测试的输出全部要贴进 commit message 或日报。理由:

  • 评审人要看真实的「passed N / failed M」
  • 出了问题能回溯是哪个测试先红
  • 防止 AI 撒谎说「测试通过」实际根本没跑

第四步:评审清单,9 件事不能漏

评审环节是这套工作流的最后一道人工闸门。下面 9 件事每件都要看,少一件都可能带病上线。

评审 9 件套

  1. 范围:diff 是否只改了任务卡列的范围?有没有顺手改无关文件?
  2. 兼容性:改了接口签名了吗?调用方都更新了吗?
  3. 密钥:有没有把 API key、token、密码写进代码或日志?
  4. 日志:关键路径有没有埋点?日志级别是否合适?
  5. 输入校验:用户输入有没有校验?SQL/XSS/路径穿越防了吗?
  6. 权限:接口鉴权是否对?权限粒度是否够细?
  7. 依赖:新加的依赖是否必需?license 是否允许?
  8. 数据库迁移:有没有 schema 变更?迁移脚本可逆吗?
  9. 失败路径:异常分支都覆盖了吗?异常信息是否泄露内部细节?

让 AI 写评审纪要

把上面 9 条塞进 prompt,让 AI 自己先回答:

按下面 9 条逐一回答(每条不超过 3 句话):
1. 范围
2. 兼容性
3. 密钥
4. 日志
5. 输入校验
6. 权限
7. 依赖
8. 数据库迁移
9. 失败路径

如果某条不适用,回答「N/A + 为什么」。

AI 自己的回答 + diff = 评审材料。人类评审人只需要在 AI 的回答基础上追问。

三类必须人工评审的改动

下面三类改动 AI 自我评审不可信,必须人工过:

  1. 认证/授权改动:登录、token、权限判断。AI 容易漏边界。
  2. 支付/扣费改动:钱相关的逻辑。AI 不懂业务规则。
  3. 数据删除/迁移改动:删数据、改 schema、AI 容易绕过安全检查。

第五步:提交 / CI / 生产,三态分离

代码进了 git 不等于上线了。「我 push 了」和「上线了」之间隔了 CI 跑通、镜像构建、灰度发布、生产部署、健康检查。把这三态分开看,能避免绝大多数「我以为上线了」的事故。

三态时间线

T0: git commit(本地提交)
T1: git push(远端收到)
T2: CI 通过(自动化测试 + 镜像构建 + 安全扫描)
T3: 灰度发布(1% / 5% / 20% 流量)
T4: 全量发布(100% 流量)
T5: 生产验证(探针 / 业务指标 / 错误率)

每一步都要看,不是「push 了就完事」。

灰度发布不能省

小改动可以一次全量,任何涉及以下场景的改动必须灰度

  • 改接口签名
  • 改数据库 schema
  • 改缓存 key
  • 改权限判断
  • 改第三方调用(支付、短信、推送)

灰度的最小做法:先开 1% 流量,盯 10 分钟错误率;再开 10%,盯 30 分钟;最后全量。

生产探针

上线后必须主动验证,不能等用户报错:

探针类型命令 / 工具频率
健康检查curl /health每分钟一次,连 5 分钟
业务关键路径curl /api/search?tag=test上线后立即 + 10min 后
错误率看监控大盘 / Sentry上线后 30min 内
资源占用CPU / 内存 / 连接数上线后 30min 内
业务指标订单量、活跃用户数上线后 1h 内

第六步:回滚预案 + 事后复盘

上线前必须准备好回滚预案,不是上线后。

回滚预案四件套

  1. 回滚命令:比如 kubectl rollout undo deployment/apigit revert HEAD && git push
  2. 回滚触发条件:错误率 >1% / 关键接口 5xx >0.5% / 业务指标下跌 >20%。
  3. 回滚负责人:谁能拍板回滚?周末/夜间怎么联系?
  4. 回滚验证:回滚后跑什么验证?预期什么结果?

预案要写在部署文档里,不能在群里口头说。

上线后 24 小时内的三件事

  1. 盯监控:错误率、响应时间、业务指标,至少盯 24 小时。
  2. 收告警:任何上线后才出现的告警都先排查,不要「先看看再说」。
  3. 记录:把这次上线的指标(响应时间、错误率、资源占用)和上线前对比,写进 CHANGELOG。

事后复盘清单

上线 1 周后做一次复盘,回答下面 5 个问题:

  1. 这次上线有没有出问题?是什么问题?
  2. AI 在哪一步帮上了?哪一步添了乱?
  3. 任务卡的哪些字段写得到位?哪些没写?
  4. 评审环节哪几条本来该发现但漏了?
  5. 下次类似改动,怎么改进任务卡和评审清单?

复盘写到团队的 retros/ 目录里,下次接需求时翻一翻。


完整案例:给内容站加搜索过滤

下面用一个完整案例把上面六步串起来。需求:给一个内容站的文章列表加按标签过滤的功能。

任务卡

目标:让搜索结果支持按标签过滤
用户场景:运营筛选带某个标签的文章
输入/输出:
  输入:tag 字符串数组,可选;page / pageSize
  输出:{ items: [...], total: N }
非目标:不做排序、不做全文搜索、不做权限
验收标准:
  - 三种标签组合都返回正确条数
  - 空 tag 返回全部文章
  - tag 不存在返回空数组(不报错)
  - tag 是空字符串返回 400
  - 并发 100 请求,最终状态一致
失败处理:tag 是 null/undefined → 视为空数组
参考资料:
  - 现有 article-service list 接口
  - jest 跑单测
  - DB 用 PostgreSQL,tags 列是 text[]
改动范围:
  - 新增 SearchFilter(在 article-service)
  - 新增 GET /api/articles?tags=... endpoint
  - 改 article-repository.findMany 接收 tags 参数

三步小循环

第一步:定义接口 + mock 数据

// src/services/SearchFilter.ts
export interface SearchParams {
  tags?: string[];
  page?: number;
  pageSize?: number;
}

export interface SearchResult {
  items: Article[];
  total: number;
}

export class SearchFilter {
  filter(params: SearchParams): SearchResult {
    // TODO: 第二步实现
    return { items: [], total: 0 };
  }
}

测试 + commit:feat(search): add SearchFilter interface and empty implementation

第二步:核心逻辑

filter(params: SearchParams): SearchResult {
  const { tags = [], page = 1, pageSize = 20 } = params;
  if (tags.some(t => t === '')) {
    throw new BadRequestError('empty tag');
  }
  // 调用 repository
  return this.repository.findByTags(tags, page, pageSize);
}

测试 + commit:feat(search): implement SearchFilter core logic

第三步:接入 endpoint + 真实数据

// src/api/articles.ts
router.get('/articles', async (req, res) => {
  const tags = (req.query.tags as string)?.split(',') ?? [];
  const result = await searchFilter.filter({ tags, ...req.query });
  res.json(result);
});

测试 + commit:feat(search): wire up GET /articles endpoint with tags query

跑测试 + 贴日志

$ npm run lint
> eslint src/
✓ 0 errors

$ tsc --noEmit
✓ No type errors

$ jest --testPathPattern=search
PASS src/services/__tests__/SearchFilter.test.ts
  ✓ empty tags returns all articles
  ✓ single tag filters correctly
  ✓ multiple tags (AND)
  ✓ non-existent tag returns []
  ✓ empty string tag throws 400
  ✓ concurrent 100 requests consistent

Tests: 6 passed, 6 total

评审回答(AI 草稿 + 人工追问)

1. 范围:只改了 SearchFilter + endpoint + repository.findByTags,没改其他文件。
2. 兼容性:GET /articles 旧调用仍然能用(tags 可选)。
3. 密钥:无。
4. 日志:filter 入口打了 log.info,含 tags 数量。
5. 输入校验:空字符串 → 400;非法 tag 字符 → 400。
6. 权限:现有 article 接口无需登录,保持一致。
7. 依赖:无新增。
8. DB 迁移:tags 列已存在,无需迁移。
9. 失败路径:DB 错误 → 抛 500,error message 不含 SQL 细节。

灰度 + 生产验证

T0: git push → T1: CI 通过(5min)
T2: 灰度 1% → 监控 10min,错误率 0.01% 正常 → 全量
T3: 探针 curl /api/articles?tags=test → 200 OK,返回 12 条
T4: 监控大盘 30min 内 0 告警

回滚预案

回滚命令:kubectl rollout undo deployment/api
触发条件:错误率 >1% / 5xx >0.5% / 文章列表接口响应时间 >2s
负责人:@oncall(PagerDuty)
验证:curl /api/articles → 200,旧行为可用

一页式交付清单

最后把整个工作流压成一张一页式清单,下次接需求照着走:

接需求

  • 填任务卡 8 栏(目标 / 用户场景 / 输入输出 / 非目标 / 验收标准 / 失败处理 / 参考资料 / 改动范围)
  • 任务卡里显式列边界、失败、并发三类验收

写代码

  • AI 先读仓库清单(README / AGENTS.md / 依赖 / 相邻文件 / 类似实现 / 测试命令)
  • AI 输出「我看到了什么」小结
  • 五步小循环:数据/API → 核心逻辑 → 调用方/UI → 错误处理 → 文档
  • 每个 commit 独立可验证 + 可回滚
  • 禁止 AI「过度实现」:没列的功能先停下问

跑测试

  • Lint 通过
  • 类型检查通过
  • 构建通过
  • 单元 / 集成 / 边界 / 失败 / 回归 五类全跑
  • 输出贴进 commit message

评审

  • AI 自评 9 件套(范围 / 兼容 / 密钥 / 日志 / 校验 / 权限 / 依赖 / 迁移 / 失败)
  • 人工重点评审:认证、支付、数据删除
  • diff 只动了任务卡列的范围

上线

  • 提交 / CI / 生产 三态分开
  • 涉及 schema / 接口签名 / 权限 必走灰度
  • 生产探针:health + 关键 endpoint + 监控 + 资源

回滚 + 复盘

  • 上线前写好回滚预案 4 件(命令 / 触发 / 负责人 / 验证)
  • 上线后 24h 盯监控
  • 1 周后复盘 5 问,写进 retros/

常见误区

最后列六个我见过的、最容易翻车的点:

  1. 「AI 写完看着对,就上线了」 —— AI 不跑测试、不看输出、不懂业务,必须人工验。
  2. 「任务卡随便写」 —— 没有任务卡,AI 会自由发挥,三步之后就失控。
  3. 「一次写整个模块」 —— 拆不开 diff,评审看不动,回滚也难。
  4. 「push 了就算上线」 —— push 是 T0,上线是 T5,中间还有 CI、灰度、生产验证。
  5. 「没回滚预案就上线」 —— 出了问题不知道谁拍板、不知道怎么回滚、不知道回滚后该看什么。
  6. 「不复盘」 —— 同样的坑会反复踩,团队累积不出 SOP。

避开这六条,工作流就跑顺了。