jevbooks

← 全部设计模式

常开常闭

失败方向开关 · Fail open, fail closed

断电时门开还是关,先说好

模型超时、报错、被限流的时候,是放过去还是拦下来,事先拧成一个开关。

超时放行拒绝放行拒绝--on-error 放行--on-error 拒绝

问题

同一段检查你用在了两个地方。一个是装在编辑器里的钩子:每次保存前看一眼这次改动,提醒有没有违反团队规范。另一个是构建服务器上的一道门:命令跑起来之前看一眼这条命令,决定放不放。

服务挂了半个小时。编辑器钩子那边,你当初写的是出错了当没看见,跳过检查接着跑,于是这半小时里每一次保存都没被检查,而且没人知道。构建服务器那边,你写的是出错就拦,于是这半小时里所有命令全被拒绝,值班的人整晚都在解释为什么。

你去找那个「正确的默认值」,结果根本没有。同一段判断,两边正确的答案是相反的。编辑器里的钩子必须放行,因为一个挡着人干活的工具活不过中午就被卸了;那道门必须拦住,因为它后面再没有第二道防线。方向不是技术问题,是「你宁可打扰谁」的问题。

解决方案

常开常闭模式说:写错误处理之前先回答一个问题——打断的代价和漏过的代价,哪个大?然后把这个答案放在看得见的地方,做成调用旁边的一个设置,而不是压在报错处理那几行里、谁也看不见的一句。

把它做成一个参数,于是同一段检查在编辑器里写着「出错就放行」,在构建服务器上写着「出错就拒绝」。这还有个好处:这个争论只需要吵一次,方向白纸黑字写在那儿,随时能指给人看。

配套还有两个习惯。一是连着出错就先停一分钟别再发了,这一分钟直接按写好的方向走,免得每次保存都白等三秒;鉴权这种一看就不会自己好的错误,第一次就该停。二是数一数门卡在那个方向的时候,放过了多少、拦下了多少——「挂了半小时」和「挂了半小时并且放过了 3800 条消息」是两起不同的事故。

问 · 答 · 拍

  1. 问照常把材料和题目发出去,只是多带两样东西:你最多愿意等多久,以及什么都没回来的时候往哪边倒。
  2. 答三秒过去了,什么也没回来。
  3. 拍按事先说好的方向办。这台是没人值守的门,于是这条命令被拒绝,日志里留一行;同一段代码装在编辑器里的话,这一行写的就是放行。

名字从哪来

这名字来自锁。停电的时候,逃生门必须自己弹开,让人跑得出去;金库门必须自己锁死,让人进不来。没有哪一种在所有场合更安全。你先想清楚自己更怕把谁困在里面、或者放谁进来,然后再挑锁。

什么时候用

总是。每次调用本来就有一个失败方向,不管你写没写下来;这条模式只是把它变成一行看得见、吵得起来的配置。

什么时候别用

没有。只有一种伪装要小心:凌晨三点、没人值班的「升级给人」,其实就是拒绝。那就直接写成拒绝,别骗自己。

做这件事的项目

  • jev-axi — 为 TypeSafe 的 Jev 提供的 CLI:从 shell 快速获得校准判断。
  • mastra-jev-moderation — 在 Jev 上为 Mastra 做输入审核:一个文件,一次类型化请求。
  • foreman — 使用决策模型的智能体监督者与软件工厂工头。

用这种模式的全部项目 →

看谁的代码
想看代码
async function guard(state, questions, onError) {   // 钩子传 "allow",门传 "deny"
  try {
    const { answers } = await jev(state, questions, { timeoutMs: 3000 });
    return decide(answers);
  } catch {
    return onError;                                 // 超时、限流、服务挂了,都落到这一行
  }
}
const verdict = await guard(state, questions, "deny");
if (verdict === "deny") return refuse(command);
jevbooks 怎么认出它

我们怎么知道一个项目用没用这一招?拿它的 README 问 Jev 一句话,Jev 给个百分比,数字越高说明越像。你要抄这道题,记住一点:别问「这个项目好不好」,要问「有没有讲到这一件具体的事」。

questions.json · 每份 README 都发这道题
{
  "failure-mode-knob": {
    "type": "noul",
    "instructions": {
      "what": "Does the text state what happens when Jev errors or times out: allow, deny, escalate, circuit breaker?",
      "not_for": "No stated behaviour on error."
    },
    "criteria": {
      "true": "The text explicitly describes this. One sentence is enough; other content does not cancel it.",
      "false": "The text does not describe this."
    }
  }
}

用 Jev 做一个 →