Image: AI generated
速成技巧 — 知道这些就能指挥AI
四句话就够了。
对Agent说:“创建Hurl测试” AI编写验证功能是否正常工作的契约。是不懂代码也能读的纯文本。
对Agent说:“添加这个功能。但是现有Hurl测试必须通过” 这一句话就能防止漂移。如果AI在添加新功能时破坏了现有功能,Hurl会用红色文字告诉你。
对Agent说:“提交” 保存当前正常工作的状态。就像游戏的存档点。下一个任务搞砸了就回到这里。
对Agent说:“回退” 回到最后的存档点。撤销AI搞坏的东西。
这四句话的模式
功能完成 → “创建Hurl测试” → 确认通过 → “提交” → 下一个功能 → “现有Hurl必须通过” → 出问题就"回退"
这就是棘轮。只向前转、不会倒退的齿轮。不管有5个还是50个功能,现有的都不会坏。
为什么这样有效?
第2课学过了。给AI意见就谄媚,给事实就修正。Hurl返回的不是意见——是事实。“test failed: status 401, expected 200”——这没有什么可谄媚的。
动手体验
给第2课实验中的待办事项应用做一个Hurl测试。3分钟就够。
对Agent说:“写一个Hurl测试,验证当前的任务添加功能是否正常工作”
AI会创建一个.hurl文件。
对Agent说:“运行Hurl测试”
通过就是绿色。现在故意搞坏它。
对Agent说:“把任务添加API响应中的id改成todo_id”
对Agent说:“运行Hurl测试”
红色文字显示失败。这就是漂移检测。
对Agent说:“回退”
又变绿了。这就是棘轮的核心。
为什么必须这样指挥
第2课看到了问题。逻辑漂移、上下文消失、谄媚偏差。超过5个功能后现有的就坏了,AI还虚假宣告"运行良好"。
这节课学习防止这些问题的三个工具。全是软件工程师用了几十年的东西。不需要会读代码。AI编写,AI运行。你只需确认"通过了吗?"
三个工具的角色:
| 工具 | 比喻 | 做什么 |
|---|---|---|
| Hurl | 契约 | 声明"这个功能必须这样运行" |
| Git | 存档点 | 保证"可以回到这个时间点" |
| CI/CD | 自动监控摄像头 | 机械化"每次自动确认" |
Hurl — 用纯文本声明API契约
Hurl是什么
Hurl是一个描述"这个API应该怎么运行"的文件。
用游戏来比喻:在RPG中从NPC那里买药水时,有个规则"1瓶药水 → 金币-50,生命值+100"。检查这个规则在补丁后是否改变——这就是Hurl做的事。
来看一个实际的Hurl文件:
# 添加任务
POST http://localhost:8080/api/todos
{
"title": "买牛奶",
"priority": "high"
}
HTTP 201
[Asserts]
jsonpath "$.id" exists
jsonpath "$.title" == "买牛奶"
jsonpath "$.priority" == "high"
jsonpath "$.completed" == false
不懂代码的人也能读:
- POST — 向服务器发送"添加这个"的请求
- http://localhost:8080/api/todos — 待办事项列表的地址
- { “title”: “买牛奶” } — 发送这样的数据
- HTTP 201 — 成功的话必须返回201号响应
- jsonpath “$.title” == “买牛奶” — 返回的数据中必须有"买牛奶"
这就是契约。“添加任务时应该返回201,标题和优先级应该原样返回。“如果这个契约被打破,Hurl会用红色文字告诉你。
再看一个:
# 未认证访问必须被拒绝
GET http://localhost:8080/api/todos
HTTP 401
“不登录就访问待办事项列表应该返回401(需要认证)。“这也是契约。如果AI在"整理"认证代码时打破了这个规则,Hurl会立即捕获。
为什么是Hurl——与单元测试的区别
“测试工具那么多,为什么是Hurl?“对氛围编程者来说有特别的理由。
单元测试检查代码内部的函数。用汽车来比喻,单元测试是拆开发动机零件逐一检查,Hurl是在实际道路上的行驶测试。如果函数名变了,测试也会坏;AI重构时还要一起修改测试。**如果给AI同时修改代码和测试的权限,AI会把测试调整成与代码匹配。**测试通过了,但原来的规则已经消失了。
Hurl不同。它在服务器的入口进行检查。发送请求,检查响应。它不知道代码的内部结构。不管AI怎么改代码,从外部观察到的行为相同就通过,不同就失败。
| 单元测试 | Hurl | |
|---|---|---|
| 汽车比喻 | 发动机零件拆解检查 | 道路行驶测试 |
| AI改了代码时 | 测试也可能跟着变 | 测试不变,只判定结果 |
| 阅读难度 | 需要懂代码 | 像普通文字一样读 |
| 漂移检测 | AI改了测试就漏掉 | 独立于代码,自然检测到 |
**Hurl验证的不是代码而是行为。**AI可以自由改代码。行为不能变。这个区分是捕获漂移的关键。
为什么这种方法有效——研究证明
第2课学了谄媚偏差。“写测试"的建议怎么给,结果也完全不同。
TDAD(Test-Driven AI Development)研究(2026)精确测试了这一点。让AI修复bug时改变测试条件:
| 条件 | 回归率(现有功能损坏比例) |
|---|---|
| 基准(无测试指示) | 6.08% |
| “做TDD"过程式指示 | 9.94%(恶化!) |
| 提供受影响的测试文件作为上下文 | 1.82%(减少70%) |
惊人的结果。**“做TDD"反而更差。**AI试图遵循过程式指示时反而偏离了实际任务。但提供"这个测试文件必须通过"的具体上下文,回归减少70%。
差异很明确:
- “边写测试边开发” → 过程式指示 → AI混乱
- “这个Hurl文件必须通过” → 具体契约 → 回归减少70%
不是指示方法,而是给出什么必须通过的契约。上面的"指令3"正是这个意思。
Git — 可以回退的存档点
Git是什么
玩游戏时会存档。在Boss战前存档,死了就读档。
Git是代码的存档功能。“当前状态正常” → 存档(提交)。下一个任务搞砸了 → 回到之前的存档。
没有Git的氛围编程:
功能1添加 → 正常
功能2添加 → 正常
功能3添加 → 功能1坏了!
→ 想回退...功能2的状态是什么来着?
→ 对AI说"回到之前" → AI不知道"之前"是什么
→ 从头开始
有Git:
功能1添加 → 正常 → 提交(存档1)
功能2添加 → 正常 → 提交(存档2)
功能3添加 → 功能1坏了!
→ "回到存档2" → 恢复到功能1、2正常的状态
→ 用其他方法重新尝试功能3
Git用法:两个词就够
不需要学Git的几十个命令。氛围编程者需要的只有两个。
“提交” — 保存当前状态
"提交当前状态。消息用'任务添加功能完成'"
AI执行:
git add .
git commit -m "任务添加功能完成"
“回退” — 恢复之前的状态
"回退到最后一次提交"
AI执行:
git checkout .
或者回到更早:
"回退到'任务添加功能完成'提交"
什么时候提交
规则很简单:
- 功能完成并正常工作时 → 提交
- 所有Hurl测试通过时 → 提交
- 开始下一个功能之前 → 务必提交
不提交就继续的话,出了问题就没有可以回退的地方。就像玩了3小时游戏不存档一样。
好的模式:
功能完成 → Hurl通过 → 提交 → 下一个功能
坏的模式:
功能1 → 功能2 → 功能3 → ... → 什么东西坏了 → 没地方回退
Git的比喻:珠穆朗玛峰的营地
登珠穆朗玛峰不会一次登顶。大本营 → 营地1 → 营地2 → … → 顶峰。在每个营地搭帐篷、储备物资。天气变坏就退到前一个营地。没有营地的话,暴风雪来了就死了。
Git提交就是营地。每完成一个功能就建一个营地。即使AI在下一个功能中搞坏了,也可以退回到前一个营地。
CI/CD — 机器自动守护
CI/CD是什么
CI(持续集成)是"每次上传代码时自动运行测试”。 CD(持续部署)是"测试通过后自动部署”。
现在只需知道CI。CD以后再说。
没有CI:
你:"添加功能"
AI:"完成了!"
你:(在屏幕上只确认新功能)"看起来不错!"
→ 没发现现有功能坏了就继续了
有CI:
你:"添加功能"
AI:(写代码)
机器:(自动运行所有Hurl测试)
机器:"现有登录测试失败!"
你:"登录坏了。修好它。"
AI:(修复)
机器:"所有测试通过"
你:"提交"
不需要每次手动运行Hurl测试。机器每次自动运行。
用GitHub Actions创建CI
代码上传到GitHub后,GitHub Actions自动运行测试。只需一个配置文件。
让AI来做:
"用GitHub Actions设置CI。
- 每次push时自动运行Hurl测试
- 需要先启动服务器再运行Hurl测试
- 测试失败就阻止合并
(PR是'这个代码可以合并吗?'的请求,合并是实际的合并操作)"
AI创建.github/workflows/ci.yml文件。不需要精确理解内容。AI来创建,你只需知道关键点:
- 每次代码push时自动执行
- 启动服务器并运行Hurl测试
- 只要有一个失败就亮红灯
大致是这样的内容:
name: CI # 这个自动化的名字
on: [push, pull_request] # 每次上传代码时执行
jobs:
test:
runs-on: ubuntu-latest # 在云服务器上执行
steps:
- uses: actions/checkout@v4 # 获取代码
- name: 启动服务器 # 启动应用服务器
run: |
docker compose up -d
sleep 5
- name: 运行Hurl测试 # 运行所有测试
run: |
hurl --test tests/*.hurl
- name: 停止服务器 # 停止服务器
run: docker compose down
CI的比喻:建筑的火灾报警器
建筑有火灾报警器。起火时自动响。不需要保安24小时巡逻。
CI是代码的火灾报警器。Hurl测试坏了自动通知。不需要你每次手动确认。
差异:
| 手动确认 | CI | |
|---|---|---|
| 确认时机 | 想起来的时候 | 每次自动 |
| 确认范围 | 只有新功能 | 全部 |
| 遗漏确认 | 经常 | 没有 |
| 成本 | 时间和精力 | 免费(GitHub Actions免费计划) |
三个工具合在一起:棘轮锁定
Hurl + Git + CI合在一起就成了棘轮(ratchet)。棘轮是只能向一个方向转动的齿轮。转动就前进,松手就停住但不会倒退。
功能1完成 → 写Hurl测试 → 全部通过 → 提交 → 锁定
功能2完成 → 添加Hurl测试 → 现有+新测试全部通过 → 提交 → 锁定
功能3工作 → 现有Hurl测试失败 → 提交被拒绝 → 修复 → 全部通过 → 提交 → 锁定
规则很简单:
- Hurl测试通过就锁定
- 被锁定的测试不能删除/修改
- 添加新功能时也添加新Hurl测试
- 所有现有测试+所有新测试都通过才能提交
当你让AI"重构代码"时,AI自由地修改代码。但如果Hurl测试坏了,提交就被拒绝。AI必须在保持所有现有行为的同时工作。
这与上面的TDAD研究结果完全一致。不是"写测试"的过程式指示,而是"这个Hurl文件必须通过"的具体契约。Agent可以选择方法,但不能违反契约。
第2课的问题如何解决
| 第2课的问题 | 第3课的解决 |
|---|---|
| 逻辑漂移 | Hurl保护现有行为。AI改了代码,行为变了就失败 |
| 上下文消失 | Hurl文件永久保存决定。会话换了契约还在 |
| 谄媚偏差(“都完成了”) | CI机械判定。不是AI的自我报告而是pass/fail |
| 决定-实现混杂 | Hurl在独立于代码的文件中声明决定(行为) |
| 乘法衰减 | 每一步用棘轮锁定,衰减就重置 |
再看一次第2课的关键实验结果:
自主Agent: 40 / 527 (7.6%) — Agent宣告"完成"
棘轮CLI: 527 / 527 (100%) — 机器宣告"还剩487个"
同一个模型。差异在于谁来决定"完成”。AI决定就停在40,机器决定就到527。Hurl + CI正是扮演那个"机器"的角色。
已有应用的事后适用
**如果你还没有做过应用,可以跳过这一节。**需要时再回来。
“我已经用氛围编程做了应用并在运营了,需要从头开始吗?”
不需要。不需要从头开始。**不是打地基而是抗震加固。**不关门营业的店铺,加固建筑。
第1步:用Hurl捕获当前行为
用Hurl记录应用当前如何运行。有API文档就直接转换,没有就让AI做。
提示:如果没有API文档(OpenAPI规范),可以试试codistill。codistill静态分析现有的Go+Gin、NestJS、FastAPI源代码,自动提取OpenAPI规范和DDL。“把代码放进去,规范就蒸馏出来了。“有了提取的OpenAPI,写Hurl测试就容易多了。codistill在第4课会再次讨论。
"分析当前应用的所有API端点,编写Hurl测试。
必须原样捕获当前运行方式为测试。"
目标:用纯文本声明"这就是当前的运行方式”。
不需要一次全做。从最重要的开始,一个一个来:
- 登录/注册——这个坏了什么都用不了
- 支付——涉及钱的最优先
- 核心业务逻辑——你的应用做的主要事情
优先级:
1. 登录API → 写Hurl测试 → 确认通过
2. 支付API → 写Hurl测试 → 确认通过
3. 核心CRUD → 写Hurl测试 → 确认通过
...(有时间再做剩下的)
第2步:用Git保存当前状态
如果还没用Git:
"把这个项目初始化为Git仓库,提交当前状态。
消息用'保存现有应用状态'"
如果已经在用Git,在所有Hurl测试通过的状态下提交。
第3步:加上CI
如果代码在GitHub上:
"用GitHub Actions设置CI。每次push时自动运行Hurl测试。"
第4步:从现在开始安全了
从这里开始,不管你让AI做什么,Hurl都保护现有行为:
"添加这个功能。但是所有现有Hurl测试必须通过。"
漂移发生时CI立即捕获。在到达生产环境之前。
反馈的力量:意见 vs 事实
记住第2课的谄媚偏差。给AI意见就谄媚,给事实就修正。
Hurl给AI的不是意见——是事实:
意见:"登录功能好像有点问题"
→ AI:"我确认了,运行正常!"(谄媚)
事实:"test failed: status 401 ≠ expected 200"
→ AI:(在行级精确修复)
问"确定吗?“AI就会推翻正确的答案。但"line 41: expected user_id, got userId"没有什么可谄媚的。数字和位置不是情感。
这就是Hurl、Git、CI这样的确定性工具(对相同输入始终产生相同结果的工具)能工作的根本原因。这些工具不谄媚。pass就是pass,fail就是fail。
常见问题
Q:怎么知道Hurl文件是正确的?AI可能写错了。
初次编写Hurl文件后运行通过,说明那个时间点的行为被捕获了。之后代码变了Hurl失败,那是行为变了的信号。不是Hurl本身错了,而是在检测行为是否变化。
初次编写与预期不符时:运行后用眼睛确认结果。“任务添加应该返回201但返回了200”——这个你自己能判断。
Q:Hurl测试太多了管理不过来怎么办?
从3-5个开始就够了。登录、核心功能、最重要的业务规则。需要时再一个一个添加。不需要一次做到完美。
端点变多时huma来帮忙。huma读取OpenAPI规范,找到没有Hurl测试的端点,用棘轮循环一个一个填满。机械地强制"有42个端点就必须有42个测试”。huma在第6课会再次讨论。
Q:需要背Git命令吗?
不需要。“提交"和"回退"两个词就够了。AI执行适当的Git命令。
Q:GitHub Actions收费吗?
公开仓库免费。私有仓库每月提供2,000分钟免费(Free计划)。对小项目足够了。
Q:已有应用的话,使用Hurl会改变当前代码吗?
不会。Hurl不碰代码。只是在代码旁边添加.hurl文件而已。只捕获当前行为,不修改代码。
实验:构建Hurl + Git + CI管道
使用第2课实验中的待办事项应用。
前提准备:安装Hurl
让Claude Code来做:
"安装Hurl"
或者手动安装:
# Ubuntu/WSL
curl --proto '=https' --tlsv1.2 -sSf https://hurl.dev/install.sh | bash
确认安装:
hurl --version
显示版本号就是成功了。
步骤1:用Hurl捕获当前状态(15分钟)
对AI说:
"为当前待办事项应用的API编写Hurl测试。
至少包含以下场景:
1. 添加任务 → 201响应,标题原样返回
2. 查询任务列表 → 200响应,返回数组
3. 完成任务 → 200响应,completed变为true
4. 删除任务 → 200或204响应
5. 未认证访问 → 401响应(如果有认证的话)"
运行测试:
"运行Hurl测试"
确认全部通过。有失败的就让AI修复。
步骤2:Git提交(5分钟)
"Git提交当前状态。消息用'添加Hurl测试——保护基本CRUD'"
这是第一个存档点。
步骤3:添加功能+棘轮确认(20分钟)
还记得第2课实验中坏掉的功能吗?这次用Hurl保护着添加。
"给任务添加优先级(高/中/低)功能。
但是现有Hurl测试必须全部通过。
新功能也要添加Hurl测试。"
确认要点:
- 现有Hurl测试全部通过了吗?
- 新Hurl测试也通过了吗?
- 第2课中坏掉的这次被保护了吗?
通过就提交:
"提交。消息用'添加优先级功能+Hurl测试'"
再添加一个:
"添加截止日期功能。
现有Hurl测试全部通过+新功能Hurl测试添加。"
通过就提交。这就是棘轮。只前进。不后退。
步骤4:GitHub Actions CI设置(10分钟,选做)
**没有GitHub账号就跳过这一步。**仅步骤1-3就能体验棘轮的核心。GitHub以后再创建也行。
如果有GitHub仓库:
"用GitHub Actions设置CI。
- 每次push时自动启动服务器并运行Hurl测试
- 测试失败就阻止代码合并"
Push到GitHub,在Actions标签页确认测试自动运行。
步骤5:故意制造漂移实验(10分钟)
确认CI工作后,故意搞坏:
"修改任务添加API的响应格式。
把任务编号的名字从id改成todo_id。"
确认Hurl测试失败。确认CI亮红灯。这就是漂移检测。
"回退。恢复原样。"
确认绿灯回来。
需要记录的:
- 第2课实验 vs 第3课实验:添加同样的功能时,现有功能坏了吗?
- Hurl捕获了几次漂移?
- AI的"完成了"和Hurl的判定不同的情况有吗?
总结
这节课学到的:
- Hurl — 用纯文本声明"应该这样运行"的契约。验证的是行为不是代码
- Git — 创建保证"可以回到这个时间点"的存档点
- CI/CD — 安装"每次自动确认"的机械验证
- 棘轮 — 三者结合就成了"通过就锁定,不会倒退"的齿轮
核心原理:
不要指示AI方法。给出什么必须通过的契约。
“做TDD” → 回归恶化。“这个Hurl必须通过” → 回归减少70%。差异在于指示和契约。
不要换模型,添加契约。
下一课预告
第3课学了用Hurl逐个保护API。但项目变大后需要保护的不只是API。数据库结构、安全策略、UI组件,这些都必须互相一致。
第4课学习yongol。用一个声明式规范管理API、DB、安全、UI,把AI的工作对象从代码转移到规范。突破氛围编程在200个端点时崩溃的墙的方法。
相关文章
- Hurl防止氛围编程漂移 — 详细分析使用Hurl进行API契约验证如何防止氛围编程的漂移
- Ratchet Pattern — AI被要求测试527个函数时为什么停在40个,以及用机械验证器让它跑到底的模式
Reins Engineering 全部课程
| 课 | 标题 |
|---|---|
| 第0课 | 安装Claude Code |
| 第1课 | 如何指挥AI |
| 第2课 | 如何不信任AI |
| 第3课 | 不会崩坏的应用 |
| 第4课 | 把决定移出代码 |
| 第5课 | 有缰绳的AI |
| 第6课 | 通过就锁定 |
| 第7课 | 翻转谄媚的方法 |
| 第8课 | Agent的工厂 |
| 第9课 | 代码之外的自动化 |
| 第10课 | 数据的法 |
| 第11课 | 拯救失败的氛围编程 |
依据资料
- TDAD (Test-Driven AI Development) 2026 — “做TDD"的过程式指示使回归率恶化至9.94%,提供测试文件作为上下文使回归率降至1.82%(减少70%)
- Ratchet Pattern实验 — 自主Agent 40/527 (7.6%) vs 棘轮CLI 527/527 (100%),同一模型上完成判定主体的差异
参考文献
- Meyer, B. (1992). “Applying ‘Design by Contract’.” Computer 25(10), 40-51. link
- Golmohammadi, A., Zhang, M. & Arcuri, A. (2024). “Testing RESTful APIs: A Survey.” ACM TOSEM 33(1), 27:1-27:41. link
- Lightman, H., Kosaraju, V., Burda, Y. et al. (2024). “Let’s Verify Step by Step.” ICLR 2024. link
- Tyen, G., Mansoor, H., Carbune, V. et al. (2024). “LLMs cannot find reasoning errors, but can correct them given the error location.” Findings of ACL 2024. link
变更历史
- 2026-05-24: 初版