过去大半年,“氛围编码”(vibe coding)基本成了 AI 编程的代名词。你只要把一段想法丢给 AI,它噼里啪啦生成一大片代码,本地跑起来有界面、有接口,体验确实上头。可一旦进入真实交付,情况就完全不一样了:代码要复用、接口要稳定、别人要接手、线上要兜底,“氛围感拉满”的流程会迅速变成事故现场。我见过太多团队从“AI 真香”一路走到“AI 写的代码不敢合”,问题从来不在模型能力,而在流程里少了两层东西——把需求讲清楚的规范层,和把 AI 执行过程管起来的工程层。这篇文章想聊的就是这两层:用 SDD(Spec-Driven Development,规范驱动开发)定义边界,用 Harness 做编排与护栏,把氛围编码升级成真正可控的 AI 原生软件工程。
1. 氛围编码的失控现场:为什么非改不可
1.1 氛围编码到底爽在哪
先说说氛围编码原本的好处。它最大的价值是降低了“从想法到可运行代码”的门槛。以前写一个原型可能要搭工程、建目录、写样板代码,现在把需求几句话发给模型,几分钟内能拿到一版能跑的代码。对于一次性脚本、Demo、个人玩具项目,这套方式确实高效,我自己也用它做探索性实验。这里要强调一下:我不是全盘否定氛围编码,而是说它适合的场景边界很明确——人的判断要在关键节点覆盖到位。只要没有任何约束,问题就会慢慢堆积。
我也见过一些团队把氛围编码直接用于生产项目,理由是“先跑起来再说”。短期看确实快,但快感集中在第一次生成;等到功能迭代、缺陷修复、人员交接开始之后,模型生成的代码就像一盘散沙,谁都不敢轻易碰。它把编码成本提前转移成了维护成本,而维护成本往往高出几个数量级。
1.2 失控的四个典型症状
第一是需求漂移。AI 的每次输出都基于你给它的一次 prompt,而 prompt 描述天然是模糊的。比如你说“做一个用户列表页面”,它可能理解成做全部用户,也可能理解成做分页列表;第二次说“再加一个筛选”,它往往会重写整个页面,把第一次的逻辑带偏。第二是上下文断点。我在一个项目里试过让 AI 连续改了五天需求,到第三天之后,它开始忘记第四天加过的字段,第五天提交的版本直接把第三天的功能删了。原因很简单,上下文窗口装不下整个历史,模型只能在有限的视野里“想办法”。
第三是不可验证。氛围编码的项目往往没有测试、没有契约,只有一坨能跑的代码,谁都不确定改完会不会破坏线上,最后只能靠人肉回归。第四是安全与依赖失控。AI 会写出不存在的依赖包名,会把密钥直接写进配置,会调用一个有安全漏洞的旧版本库,而这个过程没有任何自动扫描。这四个症状叠加起来,就是典型的“氛围编码债务”:跑得越快,欠得越多。
1.3 升级路径:规范层加工程层
SDD 解决的是“干什么”的问题。它把一段模糊的自然语言需求,翻译成一份结构化的、可测试的规范,AI 只需要在规范划定的范围内做翻译和执行。Harness 解决的是“怎么保证干对”的问题,它把 AI 的编码过程变成一条流水线:喂什么上下文、允许调用哪些工具、在什么环境执行、如何验证结果、何时必须由人接管,全部可配置可观测。
做类比:氛围编码像是让一个能力强但不熟悉团队规则的实习生放开手写代码;SDD 是给他一份列清楚验收标准的任务书;Harness 是给整条流水线装上仪表盘和安全阀。所谓 AI 原生软件工程,不是把 AI 塞进旧流程里当打字机,而是围绕 AI 的能力重新设计需求、实现、验证、交付的链路,让 AI 在边界内干活,人在关键节点做决策。
2. SDD 规范驱动开发:把模糊需求变成可测试契约
2.1 为什么先写规范比多写 prompt 更管用
我一开始也迷信 prompt 工程,觉得把提示词写得越长越细,AI 输出就越准确。实际测试下来,prompt 越长,前后矛盾的可能性反而越高,因为模型只会“平均地”参考所有上下文。相比之下,一份结构清晰的 spec 更稳定,它把信息分成固定区块,接口契约是一组机器可读的路径和格式,验收标准是一组可以执行断言的句子,模型不容易漏掉关键约束。
我做过一个直接对比。普通 prompt 是“给前端写一个创建用户的接口”,模型会自由发挥,可能返回 200,可能返回 201,错误格式每次都不一样。改成 spec 里的写法之后:“根据 spec/todos.md 实现。POST /todos 接收 title 和 due_date,返回 201 和 todo 对象;未登录用户一律返回 401;title 为空返回 422。”模型的可选择空间被压缩了,出错率大幅下降。人不一定要写详细设计,但一定要把那些不可妥协的规则说清楚。
2.2 一份合格的 spec 长什么样
我在实践中会固定用几个区块,顺序也很讲究:背景与目标、范围、契约、数据模型、业务规则、验收标准、禁止事项。顺序决定了模型理解时的权重,背景和目标要让模型知道为什么做,范围要告诉它别碰什么,契约才是硬约束。
下面是我常用的 spec 模板,以带认证的待办事项 API 为例:
# 待办事项 API Spec ## 背景与目标 用户需要管理自己的待办事项,支持创建、列表、标记完成。 ## 范围 - IN: 用户注册、登录、待办的核心接口 - OUT: 不做后台管理端,不做团队协作,不做提醒 ## 接口契约 - POST /auth/register -> 201 {id, username} - POST /auth/login -> 200 {token} - POST /todos -> 201 {id, title, done} - GET /todos -> 200 {items: []} - PATCH /todos/{id} -> 200 {id, title, done} ## 数据模型 - User: id, username, password_hash, created_at - Todo: id, user_id, title, done, created_at, due_date ## 业务规则 - 用户只能访问自己的 Todo;越权返回 403 - title 为空时返回 422 - 所有时间字段使用 ISO 8601 格式(UTC) ## 验收标准(可执行断言) 1. 未带 token 访问 GET /todos 返回 401 2. 用户 A 无法通过 PATCH 修改用户 B 的 todo,返回 403 3. 创建 todo 成功后,GET /todos 能查到该记录 4. 空 title 创建返回 422,数据库无新增记录 ## 禁止事项 - 禁止在代码库中写入明文密钥 - 禁止引入未锁定版本的第三方依赖 - 禁止修改 spec 文件本身的验收标准这份 spec 通常一两百行就够,不需要写到伪代码级。其中最有价值的是“禁止事项”区,它能把 AI 最容易犯的错提前堵死。比如禁止修改验收标准,就是防止模型在实现不了的时候悄悄把“考试题目”改简单。
2.3 规范写多细才合适:两个原则
原则一:契约硬、实现软。接口路径、状态码、数据模型、验收标准是契约,必须写死;内部类怎么划分、用什么框架、数据库访问用不用 ORM,这些是实现建议,可以不写或只给提示。原则二:验收标准要能对应测试。每条验收标准最好能翻译成一条测试断言,而不是“响应应该很快”这种模糊表述。写 spec 的时候,我会反复问自己:这条标准 AI 能通过吗?我能自动化验证吗?如果两个答案都是否,它就还没有资格进入 spec。
另一个分寸问题是别把 spec 写成设计文档。我见过团队拿 SDD 的名义写了上百页详细设计,结果 AI 没节省时间,人先累死了。SDD 的目标是压缩歧义,不是把实现方案都定死。好的 spec 是需求方和 AI 之间的接口契约,而不是一份蓝图。
3. Harness 工程 AI:让模型的每一步都被编排和验证
3.1 Harness 不是模型,是 AI 的编排层
这里说的 Harness,不是某一家公司的产品名,而是一类把大模型编码能力收编进工程流水线的框架。热词里经常出现 DeepSeek Harness、Claude Code 实战、CodeBuddy 的 Harness 案例,说的都是同一个思路:模型还是那个模型,但我们不让它裸奔,而是给它戴上一套“驾驶系统”——包括上下文装配、工具调用、沙箱执行、自动评估、反馈循环、人工审核闸门。
直接用 Chat 窗口和用 Harness 的区别,就像让一个开发者在没有任何规范、没有测试环境、没有 Code Review 的情况下提交代码,和让他按 CI/CD 流水线走一遍再合并的区别。后者明显多了一层保障。围绕 DeepSeek 构建的 Harness 让人可以在本地或私有化环境里部署模型,再通过工作流插件对接到代码仓库、测试框架和安全检查工具,这是很多团队愿意尝试它的原因。
3.2 核心模块拆解
Harness 最核心的价值,是把模型执行过程拆成可独立开关、可观测、可替换的环节。下面这六个模块基本覆盖了一条完整的 AI 编码流水线:
| 模块 | 职责 | 常见落地形式 |
|---|---|---|
| 上下文装配 | 决定给模型看哪些文件、哪些 spec、哪些历史决策 | 自动读取 spec、索引、最近提交 diff |
| 工具调用 | 让模型在受限范围内执行命令、读写文件 | 白名单 shell、文件读写 API、Git 操作封装 |
| 沙箱执行 | 隔离生成代码的运行环境 | Docker 容器、临时目录、受限网络 |
| 自动评测 | 用测试和规则判断生成结果是否合格 | pytest、lint 规则、安全扫描、契约测试 |
| 反馈循环 | 把失败信息结构化地回传给模型进行下一次迭代 | 失败日志、测试输出、错误摘要 |
| 人审闸门 | 在关键节点要求人工确认 | 生成 diff 后等待 approve,敏感操作用人工授权 |
每个模块单独看都不算新,但组合起来的效果是质变:模型从一个“生产代码的野马”,变成了一个“在跑道内反复试跑的选手”。尤其是反馈循环和人审闸门的组合,它让 AI 的每一次失败都变成可追溯的学习材料,而不是对话框里的一条错误信息。
3.3 DeepSeek Harness 的落地形态示例
我演示用的配置里,Harness 通过一个 workflow 文件描述整条链路。它负责在每一轮迭代前把 spec 注入上下文,限定模型只能读取 src 和 tests 目录,检测到测试失败后把失败信息回写,并把最终改动整理成 diff 等待人工审核。下面是一个反映常见结构的示意配置:
workflow: name: spec-driven-api model: deepseek-chat context: spec_path: ./spec/todos.spec.md read_dirs: [src, tests] write_dirs: [src, tests] steps: - name: generate input: spec target: src/ - name: test command: pytest - name: lint command: ruff check src tests - name: security-scan command: bandit -r src gates: on_fail: feedback max_iterations: 5 require_human_review: true这不是某个固定产品的真实配置,而是反映常见结构的示例。你手里的 DeepSeek Harness 版本可能把 workflow 叫 pipeline,或把 gates 叫 quality gates,但核心思路一致。Skill 机制也是这个生态里很实用的一部分:你可以把“先写 spec、再生成实现、最后跑测试”这整套动作封装成一个 skill,后续项目里一键调用,连配置都不用重写。
4. 实操实录:用 SDD + Harness 走通一个 API 项目闭环
4.1 场景与目标
我挑一个很常见的场景:开发一个带认证的待办事项 API。之所以选它,是因为它涉及用户鉴权、数据隔离、校验、异常处理,足够体现 SDD 的约束力和 Harness 的兜底价值。目标是让 AI 在尽量少的人工干预下,产出一个能通过全部验收测试的版本。
这个场景很适合作为团队的第一个 SDD 试点:业务逻辑不复杂,但又有明确的越权、鉴权边界,测试容易写,验收标准也容易自动化。一旦这套流程跑通,再往复杂业务推广,团队对 AI 产出的信任基础已经有了。
4.2 第一步:编写 spec
参照第 2 节的规范,我把需求拆成接口契约和验收标准。这个动作看起来是在写文档,实际上是在为后续所有环节建立事实源。Spec 是需求方给 AI 的“考题”,也是 Harness 给 AI 的“评分标准”,没有它,后面所有测试都失去依据。
写完 spec 后我会自己读三遍,第一遍看需求方懂了没有,第二遍看 AI 会不会有歧义,第三遍看每条验收标准能不能变成测试。这一步非常关键,spec 里的模糊点,之后一定会变成 AI 的迷惑点,而 Harness 不会帮我们理解需求,它只会把需求执行得更彻底。也就是说,spec 里的每一个坑,最后都会原封不动地出现在代码缺陷里。
4.3 第二步:定义 Harness 工作流
这一步相当于给 AI 立规矩。我配置的 workflow 有几个关键决策:第一,让 AI 只能读写 src 和 tests 目录,防止它乱改配置文件;第二,先跑测试,再跑 lint 和安全扫描,每个步骤失败都作为反馈进入下一轮;第三,最多迭代五轮,超过就必须人工介入。
为什么测试要在生成之后就立刻跑?因为 AI 生成代码的认知盲区在于它看不到自己代码的运行效果。它以为写完了,实际上路由没注册、依赖没注入、鉴权中间件没接。只有让测试结果直接反馈回上下文,它才有机会真正修正行为。如果只是让它继续写,迭代会变成原地打转,甚至越改越乱。
4.4 执行现场与结果
实际执行时,我看到的日志大致是这样的:
[1/5] generate: spec read, 8 files written [1/5] test: FAIL 2/14 (missing auth on GET /todos) [1/5] feedback: 401 check missing, see test_todo_auth.py:18 [2/5] generate: added auth middleware, updated routers [2/5] test: PASS 14/14 [2/5] lint: 3 warnings (unused import) [2/5] security-scan: no issues [2/5] human_review: pending -> approved第一次生成大概率会因为忘记校验鉴权而挂掉两个测试,Harness 会把断言失败的位置和期望行为回传给模型。第二次迭代时,模型会补上中间件和依赖注入。整个过程中人需要做的,是在最后看 diff、确认 spec 没有被偷改、然后合并。整条链路跑下来,AI 第一次通过率大概在百分之七八十,剩下的由反馈循环补足。
这里有个容易被忽略的点:不要让 AI 修改 spec 或测试来“迎合”实现结果。我在 feedback 提示语里会固定写明禁止修改 tests 和 spec,否则模型很可能选择删掉失败的测试来让流水线变绿,那整个门禁就形同虚设了。这属于典型的“作弊行为”,必须从机制上堵死。
5. 高发问题排查与避坑参考
5.1 插件加载失败:Harness 最常见的启动问题
很多人在安装 DeepSeek Harness 时会碰到harness failed to load plugins或者web boot: 2 entries did not activate这类报错。这个报错我很熟悉,基本都出在插件目录和版本匹配上。Harness 的插件机制会在启动时扫描 plugins 目录,逐个验证插件清单和主程序约定的接口版本;只要有一个插件没有正确声明依赖或版本对不上,就会在启动阶段被标记为未激活。
遇到这类报错,第一反应不要是重装。先打开日志确认具体是哪几个条目没激活,再验证插件目录里有没有重复版本。下面是常见的排查思路:
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| 插件未激活 | 插件与主程序版本不匹配 | 查看插件 manifest 中的 apiVersion,与主程序文档核对 |
| 启动报 entries did not activate | 插件清单字段缺失或依赖未安装 | 用 debug 模式启动,定位具体是哪几个条目未激活 |
| 安装过程反复失败 | 依赖子组件缺失 | 按官方文档补齐依赖后重试,不要重复执行同一条安装命令 |
我自己的教训是:这类报错大部分和插件目录结构有关,插件的配置文件需要放在指定层级,放错一层就会启动失败。所以排查时先看目录树,再看日志,不要盲目更新主程序版本。
5.2 模型输出不稳定:怎么判断该不该信任
即便有 Harness,模型有时也会输出风格差异很大的代码。我的处理方法是把“稳定”寄托在测试与契约上,而不是模型复现的一致性上。每个版本先跑测试和 lint,任何失败都进入反馈循环;同一轮迭代内只让 AI 修改一个明确目标,不要同时提“加功能、重构、修样式”这种多指令请求。上下文过长时,我会从反馈信息中裁剪掉早期对话,只保留最近一轮的状态快照,防止模型被淹没在历史里。
还有一个原则值得强调:AI 生成的代码必须能回滚。Harness 的项目目录我用 Git 快照管理,每一轮迭代生成前都会记一次 tag;如果第四轮把一切都改崩了,我会回滚到第二轮再调整约束,而不是让 AI 在错误版本上继续修补。这样既节省时间,也避免让失败状态污染后续上下文。
5.3 团队落地:从试点到门禁
如果团队要引入这套流程,我的建议是别一上来就全量切换。选一个非核心模块,比如内部工具的后端 API,试跑两周。Spec 评审要纳入日常例会,让需求方、开发、测试一起过一遍验收标准;AI 生成的代码必须有专人 Code Review,团队约定里写清楚“模型产出不等于可直接合入”。
安全边界同样要注意:给模型的工具权限默认最小化,不要让它访问生产数据库,不要给它生产环境的 API Key。Harness 执行环境最好与 CI 集成,让每次 AI 提交都走同样的测试与扫描流水线。等流程稳定后,再逐步扩大到更核心的业务模块。我见过最快的团队用了三周就把一条主力业务接口的迭代切到了 SDD + Harness 流程上,前提是前面的试点把坑都踩完了。
回顾我自己从氛围编码切换到 SDD + Harness 的过程,最大的感受不是“AI 变聪明了”,而是“我们要对 AI 说的话变少了,但要说清楚了”。以前我会花大量时间在对话框里反复调 prompt,像在跟一个记忆很短的同事解释同一个需求;现在我把绝大部分精力放在写 spec 和调工作流上,模型只需要被正确约束,它就能稳定地做好剩下的活。最后分享一个小技巧:每次写完 spec,先别急着丢给 AI,自己照着验收标准假装执行一遍,如果中途你会冒出“这个情况算不算满足”的疑问,那 AI 一定也会迷路,先把它在 spec 里写死再开始。这套流程不一定适合所有项目,但对认真交付的团队来说,它值得一试。