让检查持续发生
亮红灯不等于拦住。有检查和检查有约束力,是两件事。
- 让助手反复尝试的循环,以及它的边界在哪里;
- 为什么「工作区是状态、助手是状态迁移」这个视角有用;
- 持续集成与持续交付的差别,检查为什么要真正拦住;
- 三个真实项目里的做法,与三个失败案例的教训。
一、一个五行代码的循环
让助手变强的朴素办法是让它反复尝试。最简形式只有五行:
while true; do
cat PROMPT.md | assistant -p
done
每一轮把同一份任务说明交给它,让它看当前成果、挑下一件事、改完、验证。一轮结束,下一轮重新开始。 提示词没变,但工作区变了:上一轮实现了功能、留下了测试结果、记录了失败原因,下一轮可以接着这些状态往下走。
这就是循环的全部魔法。它没有更玄的机制。
while true 不会自己识别「已经完成」或「无法改进」,也不能保证下一轮比上一轮好。
必须另外定义三样东西:验收条件(什么样算完成)、预算(最多跑多少轮、花多少)、
停止条件(连续几轮没有进展就停)。缺了这三样,这个循环只是在制造开销。
二、工作区是状态,助手是状态迁移
理解这类循环,有一个很好用的视角:把整个工作区看成状态。
工作区里不只有代码,还有任务说明、设计笔记、测试、运行结果、日志、待办清单。助手读入这些内容, 执行一轮工作,把它们变成下一份状态。一次开发就是一次状态迁移:从「这个功能还没做」到「有实现但测试不过」, 再到「失败原因已定位,修完通过检查」。
这个视角解释了一件重要的事:长期任务不能只靠一段越来越长的聊天记录维持。 新一轮的助手必须能从留下来的材料里判断:目标是什么、已经做了什么、哪些结论有证据、哪里还没完成。 如果关键经验只存在于上一轮的对话里,下一轮就得重新踩一遍坑。
三、重新发现持续集成
循环里有一条关键要求:每一轮都要验证并评估结果。理由可以说得很不客气:开发者是不可信的。 「我只改了一点点,不会有事」这句话,正是很多事故的开场白。
既然开发会不断改变工作区,而进入主线的状态都必须是好的,就应该在集成这些改变时执行检查。 把「好」变成具体条件:能构建、测试通过、没有破坏已有功能、满足项目的关键约束。这些条件成立,才允许合入。
做到这一步,你重新发明了持续集成。持续集成强调频繁把小改动集成到共同主线,并自动构建和验证。 它应对的是「各写各的、最后才发现互相冲突」这个局面。改动越小、反馈越及时,出问题时需要怀疑的范围就越小。
发布环节还有两个容易混淆的说法:
| 说法 | 含义 | 什么时候上线 |
|---|---|---|
| 持续交付 | 软件始终处于可发布状态 | 由人决定,随时可以按一下发布 |
| 持续部署 | 通过检查的变更自动发布给用户 | 不需要人按,自动完成 |
flowchart LR A["提交改动"] --> B["自动检查
构建 测试 格式"] B -->|"失败"| C["阻止合并
报告失败位置"] C --> D["按报告修正"] D --> A B -->|"通过"| E["允许合并"] E --> F["构建产物"] F --> G["发布"]
四、检查要有约束力
这是本章最重要的一条。有检查和检查有约束力,是两回事。
如果测试失败之后仍然能照常合并,那这套检查只是在旁边亮了一盏红灯,起不到拦截作用。 要让检查真正起作用,需要三个动作:
- 把检查设成合并的必要条件。检查没过,合并按钮就是灰的。
- 保护主线分支。不允许跳过检查直接推送。
- 检查组合结果。两个改动单独都通过、合起来可能出问题。合并队列可以检查待合并的改动与主线、与其他待合并改动的组合。
五、把发布流程也写成规格
持续集成真正琐碎的部分往往是业务规则,配置文件怎么写只是其中一小块。以下这些情况都要有明确答案:
- 两个改动单独都通过、合起来失败,怎么办?
- 测试偶尔失败,是重跑还是隔离?谁负责查?
- 线上急需修复,谁有权例外放行?放行之后怎么补检查?
- 两个发布任务同时跑,新版本先完成、旧任务后结束,会不会把网站覆盖回旧版?
一个课程网站可以这样约定:合并请求有新提交就重跑检查;合并前检查与最新主线的组合,失败则禁止合并; 主线更新后对这次提交跑检查并构建,全部成功才部署,失败保留上一版;多次发布重叠时,不允许过时产物覆盖新版本; 例外放行需要明确权限与记录。
还有一种更省事的分工:检查阻止、助手修正。检查负责给出明确结论与位置, 助手负责根据结论修改,再由同一套标准验证。这个循环能跑起来的前提是检查的输出足够具体: 只说「有一个错误」会让助手花时间猜;指出违规位置与允许的写法,下一轮才有明确方向。
六、三个真实项目的做法
下面三个案例来自公开的工程分享,各自解决一个具体问题。
把确定步骤写进流程,只把需要判断的交给模型
一家支付公司把工作流拆成两步:实现任务、修复失败这类需要理解与选择的步骤交给助手; 运行格式检查、跑测试这类每次都必须执行且方式固定的步骤,由普通程序保证执行。 他们还限制了检查失败后的重试轮数,不让一个任务无限循环。
这个案例给出一个直接的设计思路:每一步都必须执行、且执行方式已经明确的工序,就不要交给模型判断。 模型适合处理需要理解的问题,固定工序由程序可靠完成。重试也有成本,连续失败时要判断反馈是否带来了新信息。
改善任务的可分性,胜过增加人手
一个团队用十六个助手协作编译一个大型操作系统,结果大家撞上同一个错误,重复修复、互相覆盖。 后来他们改变做法:大部分文件用成熟的编译器编译,只让新编译器负责一部分,再通过切换文件的编译方式缩小失败范围。
关键不在用了多少个助手,而在于验证方式改变了任务能否拆分。 原来只知道「整个项目编译失败」,所有人只能涌向同一个入口;有了可信的参照与可缩小的失败集合, 大问题就能拆成可以分别推进的小问题。增加并行之前,先改善任务表示与反馈。
用行为替身把难以复现的场景变成可反复演练
另一个团队发现,助手可能用过于简单的实现应付狭窄的测试,也可能直接改测试去迁就实现。 他们的对策是把端到端用户故事组织成场景,放在代码库之外作为独立验收依据; 对包含助手的产品,还观察多次执行轨迹中是否满足用户意图,而不只看一次测试是否全绿。
他们还为外部服务建立行为替身,模拟接口与边界情况,让大量场景可以反复运行。 迁移到选课系统,可以设计「最后一个名额刚被别人占走」「请求已提交但响应丢失」这样的练习环境。 开发时不必等真实用户踩到这些问题,才知道失败长什么样。
替身与真实系统的差异也必须验证,否则只是造了一个特别容易过关的考场。
七、三个失败案例的教训
| 案例 | 发生了什么 | 教训 |
|---|---|---|
| 社交网站的主库加完整副本 | 用户量增长后,增加读副本无法解除写入瓶颈,因为每个副本都要承担全部更新。有工程师提出按用户拆分数据,但方案没能推进。 | 看出架构该怎样改,和组织真的能改掉,是两件事。及时重构需要技术判断,也需要组织能力。 |
| 网上买菜 | 配送中心建成时,业务量不到原设计容量的四成,订单量也低于盈利所需水平。 | 如果需求密度没有得到验证,庞大的配送设施会把一次判断错误变得极其昂贵。 |
| 移动端短视频服务 | 上线数月后结束业务。管理层给出的解释是:这个想法不足以支撑独立的订阅服务,或推出时机不合适。 | 有内容、有技术、有产品形态,不等于用户需要一个独立服务。实现便宜了,这个判断依然要做。 |
三个案例的共同点是:它们都不是「代码写错了」。前两个是架构与规模判断,第三个是需求与时机判断。 实现变便宜之后,这三类判断没有变便宜。
① 选一条容易机械判断的规则(例如某个文件不超过多少行、不允许提交含密码的文件、提交说明必须符合格式)。
② 写成一个脚本,能返回成功或失败。
③ 把它设成合入前必须通过的条件(本地钩子或平台设置都可以)。
④ 故意违反一次,确认它真的拦住了你。第四步最重要,没试过就不算装上。