刚过去的这半年,我的工作方式被彻底改写了。不是被某个单一工具改写的,而是被一句话:AI 生成代码的能力早就超过了人类review它的意愿。越来越多团队一边享受着“氛围编码”带来的速度幻觉——打开ChatGPT/Claude/DeepSeek,让模型一口气生成几百行代码,贴进去能跑就算赢——一边默默吞下后期集成、回归、返工的成本。问题不在模型,在于我们放弃了软件工程最核心的东西:规范驱动(SDD)。而真正把这件事拉回正轨的,是一套叫Harness的工程化思路。它不负责帮你写更多代码,它负责给AI套上一副可牵引的缰绳,让“氛围编码”真正升级为可控的AI 原生软件工程。
为什么我敢这么说?因为我自己就是从“氛围编码”里趟出来的。最早我用AI改一个内部数据清洗脚本,写提示词“帮我加个去重逻辑”,三秒钟代码出来了,效果看着也对。但一个月后需求变更,要按日期窗口去重,我发现自己完全不敢动那段函数——因为我不知道AI当时是拿了什么假设写出那段逻辑的。没有规范、没有边界、没有可验证的条件,代码就是一个黑箱。所以后来我强制自己转向 SDD + Harness,效果是颠覆式的:AI 依然写代码,但每一步都在规范文件的约束内行动,失败时会自己读日志、改方案、再试。这篇文章不聊空话,直接把我的完整思路、配置、踩坑记录全部摊开。
1. 氛围编码为什么不可控
1.1 氛围编码的典型画像
“氛围编码”这个词,最早是社区用来形容一种现象:程序员坐在IDE前面,把需求打成一段自然语言丢给大模型,得到代码后简单扫一眼就贴进工程。整个过程非常“有氛围感”——屏幕上闪烁的光标、快速滚动的token、即时可跑的代码块,给人一种生产力爆棚的错觉。
但你要仔细拆解氛围编码的动作模式,会发现有几个非常明显的共性:
- 没有需求结构化:提示词往往是“帮我写一个导出Excel的功能”,但根本没定义表头怎么映射、日期格式怎么处理、内存占用上限是多少、失败重试策略是什么。
- 没有验收标准:AI生成的代码只要“看起来对”就被接受,缺少单元测试、契约测试、边界条件验证。
- 没有上下文边界:AI经常在一个局部问题里引入全局副作用。比如写一个解析函数时顺手改了配置文件的加载方式,review时很难察觉。
- 没有可追溯性:当AI生成一段有问题的代码,你问它当时为什么这么写,它自己也记不清——因为对话上下文已经被冲掉了。
在这套模式下,单个任务的完成速度确实快,比如“写个正则提取手机号”这种高频低风险小功能,AI几乎是秒出。但一旦把它放到真实的工程系统里,问题就像滚雪球一样出现。我见过最夸张的例子是同事让AI生成一个批量导入脚本,AI在递归遍历目录时没有排除符号链接,导致线上任务循环读文件,把磁盘打满了。事后排查耗时3小时,而当初AI生成这段代码只用了40秒。氛围编码的真正代价,不是生成质量,而是失控后的排查成本。
1.2 为什么会失控:概率生成与缺少验证闭环
先说一个很多人忽略的事实:大模型本质上是一个概率系统。同一个提示词,你让它跑两次,得到的代码很可能不一样。而工程系统要求的是确定性。哪怕模型能力再强,它也无法在缺少外部约束的前提下保证每一次输出都符合你的隐性需求。
更关键的是,氛围编码缺少一个验证闭环。传统软件开发中,代码写完要过编译、过静态检查、过单元测试、过code review,每一道关卡都是一次“机器/人”对代码的校验。而氛围编码把这些关卡全部砍掉了,直接跳到“能跑就算过”。这等于让AI在全无护栏的情况下自由落体。
有人可能会说:“那我review不就行了?”问题在于,当AI一次性生成300行代码时,人类review的注意力会严重下降。尤其是那些逻辑分支密集的代码,你扫一遍根本看不出状态机漏了一个状态。心理学上这叫Change Blindness,代码数量越大,你发现缺陷的概率越低。所以,指望靠人工review给AI兜底,在体量稍大的任务上根本不现实。
真正的解法不是让AI少写代码,而是让AI在写代码之前先面对一个不可绕过的规范——这就是SDD的出发点。
1.3 规范驱动开发(SDD)的核心逻辑
SDD,Specification-Driven Development,规范驱动开发。它的思想其实非常朴素:开发过程围绕一份“规范文件”展开,而不是围绕对话展开。规范文件里写清楚功能边界、输入输出契约、错误处理策略、性能约束、依赖范围。AI要做的事情不是自由发挥,而是把这份规范翻译成代码。
你可以把SDD理解为给AI先画一张图纸,再让它去施工。图纸不严谨,施工必然走样;图纸足够细,AI的“自由发挥空间”就被压缩到了安全的范围内。
和TDD(测试驱动开发)相比,SDD把“测试”提前到了“编码”之前更抽象的一层。TDD要求先写测试用例,而测试用例本身可能不完整——你容易漏掉很多“没想到”的场景。SDD要求先写完整规范,再从规范里派生出测试用例和验收标准。本质上,SDD是比TDD更上游的约束机制。
规格文件长什么样?我通常用Markdown写,名字就叫spec.md,放在模块根目录。里面固定包含:
# 模块目标 一句话说清楚这个模块解决什么问题。 # 功能清单 - F1: 支持读入 CSV 文件 - F2: 支持按日期字段去重 - F3: 支持输出到 JSON 文件 # 边界与非目标 - 本模块不做数据清洗 - 本模块不负责权限认证 - 最大输入文件 500MB # 接口契约 ## Input - 文件路径: string - date_field: string ## Output - 返回处理后的记录数: int # 错误处理 - 文件不存在:抛出 FileNotFoundError - 字段缺失:跳过并记录 warning,不中断 # 性能要求 - 处理100MB文件耗时 < 30s - 内存峰值 < 1GB # 验收标准 1. 给定一个100MB的CSV,能在30s内完成去重 2. 重复数据保留第一条这份规范既是给AI看的,也是给人看的。当AI生成完代码,你拿着这份规范逐条对验,效率比漫无目的的review高得多。而且更妙的是,这份规范可以进入AI的长期上下文——每次要求AI修改代码时,先把spec.md一起发过去,AI就不会跑偏。
2. Harness:给工程 AI 套上缰绳
2.1 为什么聊天窗口不是工程平台
在从氛围编码转向可控工程的过程中,我尝试过很多方案,最后发现瓶颈不在模型,而在“交互结构”。聊天窗口天然不适合做软件工程,原因是它把每一次代码生成都当成了一次孤立对话。它没有记忆、没有环境、没有可重复执行的验证命令、没有对“失败”的正向利用。
举个例子。你让AI写一个函数,它写出来了,但编译不过。在聊天窗口里,你需要手动把报错信息复制回去,再说一句“请修复”。AI修复后可能又引入新错误,你再复制、再问,如此往复。这个过程效率极低,而且每一步都依赖人去“搬运上下文”。
工程化AI需要的是一个执行环境 + 反馈循环——也就是说,AI不只是生成代码,它要能运行代码、看到运行结果、根据结果自我修正。这就需要一个平台来做这些事,这个平台就是Harness。
2.2 Harness 的工程含义与核心组件
“Harness”这个词,本意是马具、挽具,引申为“驾驭、控制”。在AI软件工程语境下,Harness指的是让AI Agent在受控环境中执行任务的框架。它解决的核心问题是:AI不应该只负责“生成文本”,它应该能调用工具、操作文件、执行命令、观察结果,并且在一系列约束下完成任务。
一个成熟的Harness平台通常包含以下核心组件:
| 组件 | 作用 | 对应的工程概念 |
|---|---|---|
| Agent Runtime | 承载AI推理循环,维护任务状态 | 进程调度 |
| Skill 库 | 预定义的可复用能力单元,比如“代码编译”“测试运行”“Git操作” | 函数库 |
| Plugin 机制 | 动态加载外部扩展,比如连接Jira、连接数据库 | 插件系统 |
| Web Boot | 通过Web界面启动和管理任务 | 控制台 |
| Sandbox 环境 | 隔离运行AI生成的代码,防止破坏主机 | 容器/虚拟环境 |
| Feedback Loop | 运行结果自动回传给Agent,驱动下一次行动 | 闭环控制 |
在这个架构下,AI不再是一个“建议生成器”,而是一个自主执行者。你给它一个目标,它自己拆解步骤,自己调用skill,自己看日志,自己修改代码,直到通过验收标准。这个过程中,人的角色从“检查每一行代码”退后到“定义规范和审阅最终差异”。
这也解释了为什么社区里会出现“Harness和Agent区别”这种问题。Agent是一个抽象的概念,而Harness是把Agent落到工程实处的载体。一个没有Harness的Agent就像没有施工队的图纸,你只能拿它做推演,不能真正交付。
2.3 从“Harness Anything”到“DeepSeek Harness”的演进
社区里现在有很多Harness项目,理念都差不多。其中热度比较高的有DeepSeek Harness、Harness Anything这类开源项目。它们主打的是一个“自带驱动”的AI开发环境,集成了常见的模型接口(DeepSeek、千问、GPT等)、插件系统、技能包管理,甚至可以直接启动Web端来观察Agent执行过程。
这类工具的核心吸引力在于可复现性。你可以把整个环境和技能配置打包成配置文件,团队成员拉下来就能跑出同样的Agent行为。这就把AI编程从“某人电脑里的黑魔法”变成了“项目级的基础设施”。
根据我的实际体验,这类Harness项目最厉害的部分是它的skill机制。每个skill是一个描述文件 + 一组执行脚本,告诉Agent在什么情况下该调用什么命令。比如一个“python_compile”的skill,会让Agent在写完Python代码后自动执行python -m py_compile,如果报错就读取错误信息并尝试修复。有了这类skill,AI的自我纠错能力会被真正激活。
3. 实操:从氛围编码迁移到 SDD + Harness
3.1 一套可复用的迁移路径
我在团队里推行SDD + Harness时,总结了一套“三步走”迁移路径。不走捷径,按这个顺序来,基本不会翻车。
第一步:选一个低风险模块做试点。不要一上来就拿核心交易链路开刀。我选的是一个内部工单系统的导出模块,独立性强、依赖少、验收标准明确。用这个模块试错,就算搞砸了也没多大影响。
第二步:写规范文件。照着我在1.3节给的模板写,注意把“非目标”写清楚。这一步非常关键,“非目标”可以帮助AI减少大量无关操作。比如“本模块不负责数据清洗”,AI就不会自作主张添加链式清洗逻辑。
第三步:把规范文件交给Harness里的Agent,启动一次全自动任务。这里要说清楚,不是把所有内容一次性丢进去就完事了。我会先启动Agent,让它阅读spec.md,然后让它输出实现计划,等我确认计划后再开始写代码。这样能避免AI在错误方向上一头扎进去。
3.2 用 Skill 约束 Agent 的行为边界
Skill是Harness最重要的参数。我强烈建议所有初学者先学会写自定义skill,而不是直接用内置的通用skill。因为通用skill只负责“运行命令”,而自定义skill可以承载团队自己的工程规范。
举个实际例子。我们团队要求所有Python函数必须有类型注解,所有对外接口必须有docstring。这个要求用自然语言告诉AI,AI可能听完就忘。但如果你把它写成一个“代码风格检查”skill,让Agent每次写完代码都自动执行这个skill进行风格校验,那AI就不得不遵守。这正是SDD“以规范驱动”的精髓——规范沉入工具链,而不是停留在对话里。
一个简单的skill文件结构大概长这样:
name: python_style_guard description: 检查 Python 代码风格和类型注解 trigger: after_code_gen command: | python3 /team/skills/check_style.py {{file_path}} success_criteria: "exit code 0" retry: true max_retries: 3这看起来简单,但实际执行的效果惊人。Agent每次生成代码后都会自动跑一遍check_style.py,不通过就继续改,直到通过为止。你不再需要一遍遍提醒它“注意类型注解”,因为工具链已经帮你做了。
3.3 一个完整的规范驱动工作流示例
下面是我在试点模块上跑通的一个完整工作流,分享出来供大家参考。
需求:给内部工单系统写一个“按优先级统计工单数量”的API接口。
第1步,写规范文件spec.md:
# 需求:工单统计 API ## 目标 根据优先级字段统计工单数量,返回 JSON 结果。 ## 接口契约 GET /api/tickets/stat Query Params: - priority: string (可选, 取值 low/medium/high) - date_from: string (可选, YYYY-MM-DD) Response 200: { "total": int, "by_priority": { "low": int, "medium": int, "high": int } } ## 边界 - 不校验用户权限,由网关统一处理 - 不包含分页逻辑 - 数据库连接由全局连接池提供,Service 层不直接创建连接 ## 错误处理 - date_from 格式错误: 返回 400 + err msg - 数据库连接异常: 返回 500 + err msg ## 性能 - 最大查询范围30天,超限返回400 - 响应时间 < 200ms第2步,启动Harness Agent,把spec.md作为任务输入。
第3步,Agent的自主执行过程(我做了精简记录):
- Agent阅读spec,输出计划:写路由 -> 写Service -> 写SQL查询 -> 写测试 -> 跑测试。
- Agent调用
git_create_branchskill创建分支。 - Agent生成代码后,调用
python_style_guardskill检查风格。 - Agent运行单元测试,发现一个边界用例不过:date_from大于30天时没有返回400。Agent自动读代码,定位到校验逻辑缺失,自行修复。
- 最后Agent调用
commitskill提交代码,并附上spec.md的验收清单。
第4步,我做的收尾工作:
- diff审查:重点看Agent有没有在约定范围外改动文件。
- 跑一次完整回归测试。
- 合并分支。
整个过程里,我没有写一行代码,但我写了规范的每一条约束。这就是SDD + Harness的理想形态——规范是人写的,代码是AI写的,工程是受控的。
3.4 常见的一些操作误区
这套工作流跑顺之前,我也踩过不少坑。直接说重点。
第一坑:把规范文件当成摆设。我见过有人写完spec.md就丢给AI“按这个实现”,但AI生成的代码和规范有明显出入,也不纠正。后来我发现原因是没把规范写进Agent的“必读上下文”里,而是混在了一大堆闲聊里。正确做法是把spec.md路径显式传进去,并让Agent先输出“我对规范的理解”,确认对齐后再动手。
第二坑:滥用skill,搞得Agent束手束脚。一开始我给Agent加了十几个自定义skill,包括“代码格式化”“依赖锁定”“模型选型”等等。结果Agent每生成一行代码都要跑七八个校验,速度慢得离谱。后来我精简到3个最重要、最高频的skill:编译/语法检查、风格检查、单测。其余的用自然语言约束就够了。记住,skill是重点关口的卡点,不是越多越好。
第三坑:没有给Agent足够的上下文信息。你让AI写代码,它需要知道项目结构、依赖管理方式、代码风格约定。这些信息如果不提前放进系统提示或知识库,AI就会瞎猜。比如我们项目用uv管理Python依赖,如果我没告诉Agent,它可能就会写pip install,导致后续环境不一致。
4. 落地案例:用 Harness 改造一个遗留模块
4.1 场景背景
为了验证这套方法论在大一点的任务上也可行,我把某业务系统里一个“客服备注导入”的遗留模块拿出来,用SDD + Harness做了一次完整重构。这个模块原本有700多行Python代码,没有测试,逻辑很绕(用了五层if-else处理导入格式)。需求是:新加一种导入格式(CSV带BOM),同时保留原有格式兼容,不能破坏已有线上数据。
这个任务最大的难点是兼容性。你不知道旧代码有多少隐式分支依赖,只能靠测试来保证行为不变。传统做法是人肉看代码、写case、再改。而用Harness后,流程变成了:
- 让Agent先把旧代码的关键行为抽成一份“行为基线”测试集。
- 在这个基线测试集跑通的前提下,再让Agent重写模块结构。
- 每次改动后自动跑基线测试,有失败就自动修复。
4.2 Agent实际干了什么
我把整个任务交给Harness后,观察记录了几项关键行动:
- Agent读完旧代码后,先写了一个
test_legacy_behavior.py,里面覆盖了5种输入格式、3种异常场景。然后运行,确认旧代码通过这8条测试。 - Agent开始重构,把5层if-else拆成了“格式检测器 + 解析器 + 校验器”三层结构。
- 新增CSV带BOM的支持时,Agent在格式检测器里加了一个字节序标记检查。
- 每次重构后跑一次基线测试,失败就修,直到全绿。
最后交给我的diff大概400行,比原来的700行少了近一半,而且测试覆盖从0变成了15个case。整个执行时间大约40分钟。如果是我自己写,估计得花一整天。这个案例给了我两个直接感受:一是SDD规范在“兼容性保护”上价值巨大,二是Harness的自我纠错机制真的可以胜任中等复杂度的重构任务。
4.3 人与AI的分工边界
这个案例也让我重新思考了人和AI的分工边界。很多人担心AI写代码会让程序员失业,但实际体验是,程序员的角色从“打字员”变成了“规范制定者和验收者”。那些最能体现工程价值的动作,比如定义边界、识别风险、设计验收标准,始终掌握在人手里。AI更擅长的是快速执行和反复试错,而人更擅长的是“决定什么不该做”。
尤其是“决定什么不该做”,AI几乎无法自己完成。比如在这个遗留模块里,我明确在规范里写了“旧格式的容错行为保持不变”,AI才没有把一段看起来很蠢但实际上承担着线上兼容的代码删掉。没有这条规范,AI很可能会“聪明反被聪明误”,删掉那部分看似无用的容错逻辑,导致线上故障。
所以我的结论是:SDD是人的阵地,Harness是AI的舞台。两者配合,才叫AI原生软件工程。
5. 常见问题与排查技巧实录
5.1 Harness安装过程中的高频报错
围绕安装和使用Harness,社区里讨论最多的几个问题,我也基本都遇到过。整理成表,方便你排查。
| 症状 | 可能原因 | 解决办法 |
|---|---|---|
harness failed to load plugins | 插件目录路径错误或插件依赖缺失 | 检查启动命令中的--plugins-dir参数,确认依赖安装完整后重启 |
web boot: entries did not activate | Web Boot启动时部分扩展未成功激活 | 查看日志中具体未激活的插件名称,逐个排查版本兼容性 |
| 安装后命令找不到 | 可执行文件未加入PATH | 手动执行安装目录下的二进制,或添加环境变量 |
| 低版本安装失败 | 依赖Python/Node版本不兼容 | 优先使用官方推荐的运行时版本,不要盲目升级到最新 |
| 模型调用超时 | 网络或代理配置问题 | 检查模型API的base_url配置,确认网络策略后重试 |
| Agent执行任务卡在某一skill | skill脚本本身有bug | 手动运行该skill脚本,先确保脚本独立可用 |
这里尤其要注意harness failed to load plugins这个报错。我遇到过一次,排查了半天发现是我在Windows上用了相对路径,插件加载时工作目录变了导致找不到文件。改成绝对路径后问题消失。如果你也碰到类似问题,第一件事就是去看插件目录和环境变量,而不是重新安装。
5.2 Skill 不生效的三种情况
Skill不生效,通常有三种情况,新手很容易踩。
第一种:skill名称拼写错误。Harness启动时会加载指定目录下的所有skill,如果你在配置里写的skill名和文件定义不一致,会静默跳过。注意大小写和下划线。
第二种:trigger条件不匹配。我在3.2节写的skill里用了trigger: after_code_gen,但如果你把skill绑定到before_code_gen,那代码生成后自然不会触发。一定要确认你的skill触发时机符合预期。
第三种:skill脚本退出码不符合预期。Harness判断skill是否成功,一般看退出码是否为0。如果脚本里有些命令返回了非0但实际不影响结果,Agent就会误判为失败并反复重试。这时在脚本末尾显式exit 0可以解决。
5.3 模型选择与上下文管理心得
在我的实践里,Harness的能力上限很多时候受限于你选的模型。像DeepSeek、千问这类高性价比模型,日常编码任务完全够用;但如果任务涉及大量跨文件重构、复杂逻辑推理,还是得用更大参数量的模型,否则Agent会频繁“迷路”。
上下文管理同样关键。Harness会把spec文件、任务描述、历史操作记录全部放进上下文。如果任务太长,模型可能丢失早期的约束。我的经验是:把最重要的规范永远放在上下文最前面,并且在关键节点让Agent输出“当前约束确认”,避免它越走越偏。
另外,凡是需要长期执行的Agent任务,最好开启断点续跑机制。我在Harness里跑过一个耗时2小时的重构任务,中间模型调用偶发超时。没有断点续跑的话,之前所有进度全部作废。有了断点,Agent可以从最近一个完整状态继续执行,省时省力。
6. 写到最后的小建议
如果你现在还在“氛围编码”阶段,不用慌,也不用立刻推翻现有工作流。我的建议是:先挑一个你可以完全掌控的小模块,写一份像样的spec.md,再用Harness或同类工具跑通一次全流程。不用追求一步到位,先把“规范 + 自动执行 + 自动验证”的循环建立起来,再慢慢扩大适用范围。
我个人的体会是,这套方法真正改变的不是代码产量,而是你对AI的信任程度。氛围编码是让AI替你做主,SDD + Harness是让AI在你的框架里做事。后者少了一点惊喜,但多了一份踏实——而这种踏实,恰恰是软件工程最稀缺的东西。未来这套玩法还会演进,比如把规范文件进一步结构化(YAML/JSON Schema),或者把验收标准直接对接CI流水线,但核心思路不会变:让AI自由,但只存在于规范允许的自由里。