AI 生成代码的速度已经超过了人读代码的速度。继续把“逐行看懂全部实现”当作主要控制手段,只会让人越来越跟不上 Agent。
我现在采用的基本循环是:
- 先把设计做成可运行的原型,再把功能拆成很小的版本交给 Coding Agent;
- 每一版都必须能运行,人在真实操作中发现问题;
- 设计问题回到设计稿修改,代码问题回到实现层修复;
- 版本稳定后再做性能审查和重构,然后进入下一轮。
这条流程看起来慢,实际比“一次把完整产品写出来”更快。它压缩了每轮的不确定性,也避免 Agent 在错误方向上高效狂奔。
一、先把原型做成可执行的业务说明
第一步不是让 Coding Agent 直接写正式代码,而是用设计工具完成 UI/UX 原型。原型应交付的不只是几张页面截图,还包括 HTML、CSS、组件结构和模拟数据:
- HTML 表达页面和交互关系;
- CSS 固化颜色、字号、间距和尺寸规范;
- React 等组件代码表达界面的拆分方式;
data.js或同类数据文件表达页面需要的数据结构。
业务规则也不该只藏在说明文字里。能通过模拟数据和交互状态表达的规则,尽量直接放进原型。例如不同权限看到什么、某种状态下按钮是否可用、数据为空时页面怎么反馈。文字仍然需要,但文字负责解释边界和例外,原型负责让主要规则可以被看见、被操作。
这样一来,设计稿就不再是“参考图”,而是实现的上游事实源。 Coding Agent 面对的是一套可运行、可比较的目标,而不是一段容易产生多种理解的描述。
如果 PRD 由 AI 协助生成,我还会要求同时产出一份极简简报:让没有参与讨论的人也能看懂产品解决什么问题、这一版做什么、不做什么。复杂的需求访谈和方案推演可以保留,但不能让最终文档复杂到连决策者自己都难以复述。
二、把第一版缩到足够小,而且必须能运行
原型确认后,再把它交给 Coding Agent 实现。此时要明确技术栈和硬边界,例如使用 AppKit 而不是 SwiftUI,但第一版只做一个很小的可运行切片,也许只有主界面和最核心的一条交互。
功能越少,Agent 越容易聚焦。它需要同时理解的状态、依赖和异常分支越少,结果通常越稳定。把十个页面一起生成,看起来节省了十轮沟通,实际上会把十组错误叠在一起,让人无法判断问题究竟来自需求、设计、架构还是实现。
所以版本拆分要满足三个条件:
- 每一版都可以独立运行。 人必须能亲手操作,而不是只看代码或截图。
- 每一版只增加一种主要不确定性。 新增页面、接入真实数据、改变架构最好不要同时发生。
- 每一版都有明确的验收边界。 做到了什么、哪些能力暂时不做,都要写进文档。
刚开始讨论新需求时,我通常会找 GPT 或 Gemini 分别提出问题和方案。一方面可以把一部分发散讨论交给它们,节省主 Agent 的 token;另一方面也能博采众长,避免过早锁死方案。但一旦进入实现,最终决策必须收敛到同一份规格和设计稿。多模型适合发散,仓库中的确认文档负责收敛。
三、问题要回到产生它的那一层
人在运行版本时会发现两类问题。
- 一类是代码问题:崩溃、状态错误、性能异常,或者实现没有遵守已经确认的设计。这些问题直接交给 Coding Agent 修复。
- 另一类是设计问题:信息层级不清、交互不自然、布局在真实数据下失效。此时不要为了省事直接修改正式代码。先回到设计工具调整原型,再用更新后的设计稿替换本地文件,让 Agent 比较设计稿的变更,并结合截图修改实现。
这条边界很重要。设计稿是源,正式代码是它的实现。如果每次发现 UI 问题都直接补代码,设计和产品会逐渐分叉:设计稿展示的是旧产品,代码里堆着只有当前维护者知道的例外。后续每一轮迭代都要为这份分叉付费。
同样的原则也适用于需求和契约。问题在哪一层产生,就修改哪一层的事实源,再让下游跟进。不要只修最末端的症状。
四、用硬门禁控制 Agent 的推进速度
Coding Agent 最危险的时候,往往不是做得慢,而是推进得太快。它可以在几分钟内完成实现、测试、重构和下一阶段拆解,人还没来得及判断上一阶段的方向是否正确。
仅在提示词里写“请等待确认”并不稳。阶段推进需要基于人已经审阅并留下的文档,例如:
需求确认 → 设计确认 → 实现就绪 → 开发 → 验收 → 提交
进入下一阶段前,检查对应文档是否存在、状态是否正确、是否保留了用户确认的原话。流程封装成 Skill 时,可以再用 Hook 执行这些校验。Rules 负责告诉 Agent 不该越界,Hook 负责让越界动作实际无法发生。
门禁也保护人的注意力。人不必跟着 Agent 的每个动作跑,只需要在需求、设计、可运行版本和最终验收几个关键节点做判断。
五、接受代码是黑箱,但不能接受结果不可验证
AI 写得比人读得快之后,质量控制的重点要从“我是否理解每一行”转向“系统是否满足规格,失败时能否及时暴露并回退”。
可用的反馈手段包括:
- 单元测试保护复杂、稳定的核心逻辑;
- 集成测试验证模块和真实依赖的协作;
- Gherkin 用接近自然语言的场景描述关键业务行为;
- E2E 测试验证真实用户路径;
- 变异测试检查测试是否真能抓到错误;
- 覆盖率和代码质量指标发现明显盲区;
- 日志、监控和回滚机制控制上线后的风险。
这里不必教条地坚持“所有代码都必须先写测试”。AI 很容易生成只覆盖 happy path、断言又很弱的自证式测试。测试的价值在于形成可靠反馈,而不是满足某种仪式。每条测试都应该对应一个可观察行为,并且能在真实缺陷出现时失败。
自动化测试的启动方式可以直接写进提示词和项目规则。项目还没有自动化测试时,明确要求 Agent 先初始化测试框架;项目已经有测试体系时,在 AGENTS.md 中规定:实现功能的同时同步编写相应的自动化测试,完成后必须运行这些测试验证结果。这样,测试就不是临时想起来才补的附件,而是实现流程的一部分。
测试跑完也不代表工作结束。Agent 应生成可复查的测试报告,并清除测试产生的账户、文件和数据库记录。否则测试通过了,环境却被垃圾数据污染,下一轮结果反而更难判断。在报告形式上,我明确偏好 HTML 而不是 Markdown,尤其是测试报告、分析报告和汇总报告。HTML 的表达更自由、更直观,还可以通过 CDN 引入 ECharts 展示趋势、分布和多维指标;普通规格和决策文档则继续使用 Markdown。
Playwright 适合自动化关键链路和 CI,但在资源有限的本地环境里,同时运行浏览器测试和重型设计工具可能让机器失去响应。此时可以用浏览器插件或 BrowserSkill 做交互验证,把完整的 Playwright 回归留给关键节点。工具可以替换,关键用户路径必须真的被运行过。
六、稳定之后,再做性能审查和结构调整
功能完成后,不一定马上进入下一个功能。先让 Agent 审查当前实现,往往比继续堆功能更划算。
例如在一个 macOS 项目的第一版里,界面使用 NSScrollView。内容增多后内存明显上涨,性能分析建议改用 NSTableView。这类问题在静态设计稿里很难暴露,必须先有可运行版本和真实数据,再决定是否调整实现。
重构也要控制风险。我的经验是,重写既有模块的效果经常不如新增一个实现、验证后再替换旧实现。 原地大改会同时破坏现有行为和参照物;并行保留新旧实现,则可以比较结果、逐步迁移,并在失败时快速退回。
这个策略尤其适合 AI:让 Agent 在明确边界内建立新实现,比要求它在历史代码中同时理解、拆除和重建更容易验证。但替换完成后要删除旧实现,不能永久保留两套路径。
七、把仓库准备成 Agent 的工作环境
流程稳定以后,要把反复口头交代的内容写进仓库。根目录的 AGENTS.md 至少应该提供:
- 当前运行环境、技术栈和开发工具;
- 项目地图,以及各类文件的职责;
- 修改某个模块前必须读取的规格和前置依赖;
- 实现、测试、验收和提交的命令;
- 阶段门禁和必须等待人的节点;
- 测试报告与垃圾数据清理要求。
对于已有项目,优先在现成结构上改造通常比从空目录开始更稳。Agent 能从现有模块、测试和历史行为中获得参照;代码解析或代码库记忆工具则可以帮助它快速找到入口。私有数据也可以通过受控 MCP 接入,但权限、可访问范围和数据用途必须显式配置。
领域专业术语同样重要。准确术语可以压缩大量解释,并减少模型把相近概念混为一谈。前提是术语在规格或词汇表里有稳定定义,而不是只有某个人知道。
每个实现任务都要留下独立提交
我会把“修改文件的实现任务必须以一次 Git 提交结束”直接写进 AGENTS.md 或 CLAUDE.md:
- 编辑前检查现有变更,并把已有或并发修改视为用户资产;
- 完成后审查差异,运行与改动相称的验证;
- 只提交当前任务相关的文件或代码块,不夹带无关修改;
- 使用简洁、可描述本轮工作的提交信息,并向用户报告提交哈希;
- 不擅自推送、变基、修改历史或打包无关修改;
- 只读任务和没有文件变化的任务不创建空提交。
英文版:
Every implementation task that changes files MUST end with a git commit before the final response.
- Inspect `git status` before editing and treat pre-existing or concurrent changes as user-owned.
- Review the final diff and run proportionate verification before committing.
- Stage only files or hunks that belong to the current task. Never bundle unrelated changes unless the user explicitly asks.
- Use a concise descriptive commit message on `main`, report the commit hash, and do not push, amend, or rewrite history unless asked.
- Read-only tasks and tasks with no file changes do not create empty commits.
Git 或 Jujutsu 都可以。关键是每轮实现都有明确边界、可审查记录和可回退节点。
八、工具选择服务于反馈速度
**我的强工具偏好很明确:**国产模型和 pi/omp 这类 Agent 的组合通常更搭,响应也很快;普通迭代使用 Jujutsu、Matt Pocock Skills 和合适的国产模型,已经完全够用。
这种选择不来自模型榜单,而来自自己的项目测量:完成一个真实小版本需要多少人工纠偏、能否运行、测试能否发现故障、失败后是否容易恢复。需求讨论仍可以使用更擅长推理的模型,普通迭代则优先选择响应更快、成本更低的组合。
也要熟悉自己使用的 Agent:它如何加载规则,是否自动读取目录上下文,什么时候会调用工具,怎样保留会话状态,是否会自动修改或提交文件。不了解这些行为,就无法设计有效的门禁和反馈回路。
收个尾
AI 原生开发的核心循环可以压缩成一句话:把目标做成可执行的事实,把功能拆成可运行的小版本,把问题送回正确的层,把质量交给可观察的反馈。
人在流程里负责方向、边界和验收,Agent 负责高速探索、实现和修复。只要每一阶段都有事实源、每一版本都能运行、每一次推进都经过明确门禁,代码生成得再快,也不会快到失控。