从 Vibe Coding 到工程化 AI Coding:如何让 AI 写代码更可靠?

从 Vibe Coding 到工程化 AI Coding:如何让 AI 写代码更可靠?

让 Agent 修一个 Bug,它可能很快就给出修改,还附上一句“测试通过”。接下来要做的事情却不少:看它改了哪些文件,核对运行过的测试,确认原来的行为有没有受到影响。

如果任务做到一半需要换一个 Session,还得留下当前进度。代码可以从 Git 里找到,但方案为什么这样定、哪个问题还没解决,需要另外记录。

这篇文章就从这些环节展开,讨论需求怎么写、代码该让 Agent 看哪些,以及改完以后怎么验收。

理解 Vibe Coding

什么是 Vibe Coding

Andrej Karpathy 最初提出 Vibe Coding 时,描述的是一种跟着 AI 生成结果不断尝试的开发方式:用自然语言提出要求,运行代码,遇到问题再让 AI 修改,过程中未必逐行审查代码。

现在这个词也常被用来泛指通过自然语言让 AI 辅助编程。本文讨论的范围更具体:在已有项目中使用 Coding Agent,由它阅读仓库、分析问题、修改文件和运行测试,开发者负责检查方案和结果。

比如前面说的修 Bug,第一版代码很快就能拿到。接下来花多少时间,要看它有没有找对原因、测试是否覆盖了出问题的场景,以及修改后是否需要返工。

代码写出来以后,还要处理什么

实际使用中,麻烦往往不是 AI 完全写不出代码,而是这些修改很难直接交付。比如,本来只想修一个 Bug,它却顺手重构了几个模块,甚至引入新依赖、改变公共接口;测试虽然通过,业务行为却不符合要求。

任务一长,还可能出现另一类问题:早期约束被遗漏,已经否决的方案又被拿出来,实际执行失败却被总结成“已完成”。到最后,连人自己也得重新翻记录,才能弄清楚当前改到了哪里。

等代码写完才追问这些问题,可能已经需要返工了。所以,任务开始前就要说清楚预期结果和修改范围,提供相关代码;开发过程中检查变更、执行验证,并留下确认过的决策和未完成项。

开发者与 Agent 如何分工

拿修 Bug 来说,开发者要先划定范围。如果现有接口必须保留,就提前说明;Agent 提议改变接口时,也得由开发者决定是否接受。

Agent 可以去读代码、找原因,给出方案后完成修改和补测试。开发者重点看方案是否合适,以及最后究竟改了什么。

验收还要看执行结果。项目要求的构建、测试、Lint,以及类型、Schema 等检查,都应留下记录。这些结果供开发者审查,也可以作为进入下一阶段或合并代码的必要条件。

开发者、Agent 与工具的职责及验证门禁

先把 Spec 定义清楚,再让 AI 动手

“增加重试”听起来是个明确的任务。交给 AI 时,可能就只写了这一句:

帮我给这个功能增加重试机制。

Agent 接下来仍有不少事情需要确认。是所有错误都重试,还是只处理临时错误?能否加依赖?现有接口能不能动?如果这些要求都没写,它就可能按自己的理解实现。

我会先把任务写成一份简短的 Spec。Goal 写要修复的问题,Scope 划定修改范围,Constraints 留下不能违反的约定。最后补上 Acceptance Criteria 和 Verification,分别写预期结果和检查方法。

Spec 的五个要素:Goal、Scope、Constraints、Acceptance Criteria 与 Verification

这最后两项要分开写。“API 恢复后,资源最终达到期望状态”是一条验收标准;“用集成测试复现临时错误和恢复过程”才是验证方法。只写前一句,改完以后仍然缺少检查依据。

把模糊需求写成可验收的 Spec

假设一个 Controller 遇到临时 Kubernetes API 错误后,资源一直没有恢复。可以把任务写成下面这样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Goal:
修复 Controller 在临时 API 错误后无法正常恢复的问题。

Scope:
- 检查并修改相关 Reconciler
- 补充对应测试

Constraints:
- 不修改 CRD Schema 和现有公共接口
- 优先复用项目已有的错误处理与重试机制
- 不引入新的重试框架
- 保持 Reconcile 幂等

Acceptance Criteria:
- 临时错误不会被误判为调谐成功
- API 恢复后,资源最终达到期望状态
- 重复调谐不会创建重复资源

Verification:
- 单元测试:验证临时错误的处理和返回行为
- 集成测试:运行实际调谐循环,验证临时错误恢复后资源能够收敛
- 幂等性测试:验证重复调谐不会创建重复资源
- 执行项目规定的 Build / Test / Lint 检查

这份 Spec 把目标写成了“恢复正常”,没有指定要新增一套重试代码。按照 controller-runtime 的接口约定,Reconcile 返回非终止性错误时,会触发退避重入队。Agent 应先检查现有实现,找出恢复过程断在了哪里,具体行为以项目实际依赖版本为准。

“不修改 CRD Schema”这样的限制,现在就能写清楚。至于要改哪段逻辑,等读过代码再讨论。

复杂任务先看 Plan

如果问题牵涉几个模块,我会先让 Agent 读代码,把准备怎么改写出来。这一步就是 Plan:在文件还没变动时,先看它有没有找对问题,确认方案后再动手。

工具有 Plan Mode 就直接用,也可以在任务里加上这段要求:

1
2
3
4
5
6
7
8
先不要修改代码。

阅读相关实现后给出 Plan:
1. 当前逻辑是什么
2. 准备采用什么方案,为什么
3. 需要修改哪些文件
4. 有哪些假设、风险,以及对现有行为的影响
5. 准备如何验证

比如上面的恢复问题,如果 Plan 要求修改 CRD Schema,就需要追问:恢复调谐为什么必须改变资源定义?这个理由说不清楚,就先调整方案。等多个文件都改完,再找出哪些改动是多余的,会更费力。

改错别字这类小任务就可以省掉 Plan。问题和修改位置都清楚,再写一遍计划帮助不大。

读代码后发现需求有误,也应回头更新 Spec。例如,已有机制就能完成重试,任务可能只需要修正错误处理,不必继续按“新增机制”的方向做。

管理 Context:让 AI 读到相关代码和项目约定

Spec 说明了资源应该恢复,却没有告诉 Agent 项目里有哪些现成代码可用。它如果没读过相邻的 Controller,就可能另写一个 retry helper,错误日志和测试写法也与项目里其他地方不同。

先找到相关实现和测试,看看现有逻辑怎样处理这类错误,再决定怎么改。

项目级 Context:把长期规则写下来

每次修 Bug 都重新解释项目怎么构建、接口能不能改,很容易遗漏。可以把这些约定放进工具支持的规则文件,比如 AGENTS.md、CLAUDE.md 或 Project Rules。

优先写清构建和测试命令、项目特有的约定,以及不能随意修改的区域。技术栈和目录用途简要说明即可,不必把仓库概述再写一遍。

下面是一份 Go Controller 项目的简短示例,命令需要按项目实际情况调整:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# Project

This is a Go project based on Kubernetes controller-runtime.

## Commands

- `make build`
- `make test`
- `make lint`

## Conventions

- Follow existing controller/reconciler patterns.
- Keep public APIs backward compatible.
- Add tests for new reconciliation logic.
- Run gofmt on modified Go files.

## Restrictions

- Do not modify CRD APIs unless explicitly requested.
- Do not update dependencies without explicit approval.

长文档可以只列路径,并说明什么时候需要阅读。比如改资源接口前先看兼容性约定,改调谐逻辑前先看模块设计。发现说明与代码不一致时,要更新对应文档,否则下一个任务还会碰到同样的误导。

规则文件也不是越长越好。《Evaluating AGENTS.md》 在所测任务与 Agent 中,没有发现仓库上下文文件普遍提高任务成功率,还观察到了推理成本增加。这不等于规则文件没用,但值得逐条检查:这条说明能减少什么具体错误,还是只增加了需要阅读的内容?

任务级 Context:告诉 AI 这次应该看什么

到了这次恢复问题,可以进一步指出要读的 Controller 和测试文件。如果另一个 Controller 已有合适的错误处理方式,也把它列出来,说明参考哪一部分:

1
2
3
4
5
6
7
8
9
先阅读:
internal/controller/foo_controller.go
internal/controller/foo_controller_test.go

另外参考:
internal/controller/bar_controller.go

其中 bar_controller.go 的错误处理和 Event 记录方式
可以作为本次实现参考。

参考前,也要确认这份实现仍然适用。旧代码可能有历史限制,不能照搬到新任务里。涉及多个模块时,还需要沿调用关系查看相关逻辑,不能只读一个文件就决定怎么改。

一个任务尽量保持一个干净的 Context

除了提供相关信息,还要避免无关内容干扰 Agent。

如果在同一个 Session 中连续处理新功能、Bug 修复和重构等互不相关的任务,之前的临时方案、错误假设和调试记录可能继续留在上下文中,影响后续判断。

对于相对独立的任务,我更倾向于开启新的 Session,让 Agent 从当前任务需要的信息开始,而不是在一段越来越长的对话中不断追加新需求。

不要让 Session 成为唯一的事实源

任务做到一半换 Session,最容易丢失的往往是“为什么决定这样改”和“还有哪项没检查”。这些信息需要另有记录。

确认过的需求和决策放进 Spec 或项目文档,代码变更由 Git 保存;当前进度、失败检查和下一步操作,另留一份任务记录。后面的 Workflow State 小节会给出一个模板。

为了便于管理,可以把这些信息分成项目规则、任务参考资料和需要持续保存的决策与进度。下面这张图展示的是本文的组织方式,不是必须采用的标准分层。

项目规则、任务参考资料与持久化记录:Project Context、Task Context 与 Durable State

Verify + Review:AI 说“完成了”,不代表真的完成了

代码改完,Agent 可能会在回复里写:

Implementation completed. All tests passed. No breaking changes.

这时可以打开执行记录,把实际运行的命令与项目要求对一下。如果只跑了一个测试文件,“All tests passed”就只能说明这次运行的测试通过了。命令中途退出、测试被跳过,也需要单独记录。

接着按 Spec 查看 Diff 和测试内容。恢复问题有没有对应测试,修改是否涉及无关模块,都能在这里核对。缺少执行记录或测试没有覆盖的验收项,先记为未验证,不能靠这句总结补上。

从验收标准到实际验证

前面的 Controller 测试,要从临时 API 错误开始,检查错误怎样返回,再观察 API 恢复后资源能否继续调谐。还要重复执行调谐,确认没有创建重复资源。只测错误返回,无法覆盖整个恢复过程。

这里有个容易漏掉的区别:测试代码手动再次调用 Reconcile 后成功,不等于 Controller 会自动恢复。 要验证后者,需要让实际调谐循环运行起来。使用 envtest 时,还要注册被测 Controller 并启动 Manager;只启动 API Server 和 etcd,并没有运行你的 Controller。具体可参考 文末的 Kubebuilder envtest 测试说明。

这些场景检查完,再运行项目要求的构建和代码检查。例如,项目配置了下面这些命令:

1
2
3
make build
make test
make lint

命令只是示例,实际要执行哪些检查,以项目配置为准。运行记录需要对应当前代码,不能用修改前的测试结果为修改后的代码背书。

检查失败时,先结合错误输出、调用链和复现步骤定位原因,再修复和验证。不要为了让测试变绿就删除断言、吞掉错误,或者用硬编码结果伪造成功。Mock 可以帮助验证特定错误分支,但不能凭这类结果声称实际调谐链路已经通过验证。

如果失败来自环境不可用、缺少权限,或者继续修复需要改变已确认的方案,就应记录阻塞并交给开发者处理,而不是无限重试,也不能把未完成检查改写成“通过”。

Quality Gate:让验证结果真正起作用

Quality Gate 要明确的是:哪些检查必须通过,才能进入下一阶段或合并代码。这个条件最好由工具执行,而不只是写在 Prompt 里。

对于代码合并,可以通过 CI 的必需检查和分支保护落实门禁。同时要确认要求的测试确实执行过,没有被条件跳过,也没有忽略失败。以 GitHub 为例,部分必需检查的 skipped、neutral 状态也能满足合并要求,具体要结合分支保护规则检查(文末附官方文档)。

任务记录中的 passed 也不能代替执行结果。至少应能追溯到实际命令、日志,以及它们对应的代码版本。

工具检查之外,还需要人工审查。比如这次只修恢复问题,Diff 却改了公共接口,构建和测试可能照样通过,但这项改动已经超出约定范围。审查时要确认接口变化是否获批、原来的行为有没有受到影响,以及既有重试机制能否复用,避免为一次修复维护两套相似逻辑。

从 Prompt 到 Workflow:把有效的做法留下来

如果修复 Issue 时总要重复输入“先定位原因,再提出方案,改完补测试”,可以把这段做法保存下来,下次直接调用。任务更长、需要中途检查方案或跨 Session 继续时,再用 Workflow 安排阶段和记录进度。

从 Prompt 到 Workflow:任务执行与工程经验沉淀

不需要每次都启动完整 Workflow。简单任务用简单流程;影响范围大、需要跨会话推进的任务,再加入更多约束和状态管理。

把重复 Prompt 变成 Skill

修复 Issue 通常要经历阅读问题描述、定位原因、制定方案、修改代码、补充测试,最后验证和审查。如果这套步骤已经适用,就不必每次重新输入。

简单的固定提示,可以保存成工具支持的 Command;需要一起携带参考文档、模板或脚本,并按任务加载时,再考虑封装为 Skill。Agent Skills 的格式支持打包这些材料,但封装本身不能保证任务执行正确,验证仍然要做。

也不必所有 Skill 都自己开发。通用的调试、代码审查和测试流程,可以参考社区实现,再按项目需要调整。引入前要检查其中的指令、脚本和权限要求,不能只看名称和介绍。

为 Workflow 设置阶段检查点

当 Spec、Design、Coding 和 Verification 被串成 Workflow 后,还需要明确每个阶段怎样才算完成。例如:

1
2
3
4
Spec    → 需求范围和验收标准是否明确
Design  → 方案和关键决策是否已经确认
Coding  → 约定的变更及对应测试是否已经完成
Verify  → 必要检查是否执行,验收结果是否有记录

部分条件可以用脚本检查,比如文档是否存在、必要字段是否完整、测试是否通过;方案是否获批,则要由指定人员确认。Agent 自己写入“已批准”,不能代替审批。

从 Design 进入 Coding 前,如果方案仍有争议,就先停下来确认,而不是因为已经生成了一份设计文档就继续编码。同样,进入交付阶段也不能只检查一份状态文件,仍要核对实际验证结果。

长任务还需要 Workflow State

前面提到,跨 Session 的任务需要保存决策和进度。下面是一份交接模板,不代表前述 Controller 示例已经完成修改或验证:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
当前阶段:验证
当前任务:修复临时 API 错误后无法恢复的问题

已确认的约束与决策:
- 不修改 CRD Schema 和现有公共接口
- 优先复用现有错误处理与重试机制
- 方案及确认记录:<文档路径或讨论记录>

代码位置:
- 分支与提交:<分支名、提交 ID>
- 未提交变更:<是否存在;存在时写明位置和范围>

验证记录:
- 已执行:<实际命令、结果、日志位置、对应代码版本>
- 失败项:<错误摘要;没有则注明无>
- 尚未验证:<剩余验收项,例如 API 恢复后的自动调谐>

下一步:<具体操作及预期检查结果>
需要人工决定:<阻塞项;没有则注明无>
停止条件:<权限不足、需要改变已确认方案等>

不必每次重写整个任务经过,只更新发生变化的部分。测试结果与代码版本对不上时,接手者应先确认是否需要重跑,不能照着记录中的“通过”继续往下做。

长任务的 Workflow State:记录进度、决策、重试与停止条件

Workflow 本身也应该被评估

流程增加以后,要看看返工和人工介入是否减少,又多花了多少时间和 Token。步骤更多,不一定就更划算。

可以拿同一组任务比较新旧流程,记录验收是否通过、返工次数、人工介入和执行成本。比较时使用相同的验收标准,并从相同的代码和环境起点分别运行,不要让后一组继承前一组改好的代码或历史记录。

样本不多时,先看具体失败在哪一步,不急着判断哪套流程更好。如果结果没有改善,等待时间和 Token 却增加了,就检查哪些步骤没有带来收益,能简化的就简化。

小结

回到前面的 Controller 例子,验收要看三个结果:临时 API 错误没有被当成成功,API 恢复后资源能够继续调谐,重复执行也不会创建重复资源。Agent 有没有新增重试代码,并不是验收重点。

审查时,把这三个结果与实际 Diff、测试记录逐项对上。只通过编译,还不能说明资源会恢复;只检查了错误返回,也没有覆盖后续调谐。没有验证的部分就留下来,写清楚还缺什么检查。

下一个任务可以先这样做:提供相关代码,写下预期结果,改完以后核对变更和测试。等同一类问题反复出现,再把有效的做法写进项目规则或 Skill,不必一开始就搭建整套 Workflow。

延伸阅读

0%