Fail-Fast 快速失败:尽早暴露错误

Fail-fast(快速失败)是一种设计原则:当系统检测到一个不该发生的状态时,应当立即、显眼地失败,而不是带着错误继续运行下去。它的核心主张是——把问题暴露在离源头最近的地方,让错误尽早被发现,而不是拖到后面变成难以排查的怪现象,或者悄无声息地产出错误结果。 Jim Shore 在 Martin Fowler 主持的 IEEE Software「Design」专栏上写过一篇就叫《Fail Fast》的短文(2004 年 9/10 月号),把这个原则讲得很直接:带着错误「慢慢失败」(failing slowly)的程序会在后面以奇怪的方式出问题,而 fail-fast 的系统则在问题发生的当下就立即、显眼地失败(fail immediately and visibly)——看似更脆弱,实则更健壮,因为缺陷更容易被发现和修复。原文可读 Fowler 站点上的 Fail Fast (PDF),作者本人也在 jamesshore.com 上做了简介。 它要解决的问题 很多 bug 难查,不是因为错误本身复杂,而是因为错误发生的位置和错误暴露的位置离得很远。 设想一个函数收到了一个非法参数(比如本不该为 null 的值),它没有检查,而是默默接受、继续往下传。这个坏值可能流过好几层调用、被写进缓存、存进数据库,直到很久以后在某个完全不相干的地方抛出一个莫名其妙的异常。这时你拿到的堆栈和真正的出错点已经隔了十万八千里。 fail-fast 的思路是反过来:在边界上就把关,一发现状态不对就立刻停下来报错。错误在源头炸掉,定位成本最低。 几个常见的例子 fail-fast 不是某一种具体技术,而是一类做法,散落在很多地方: 参数 / 前置条件校验:方法一进来就检查参数,非法立刻抛异常,而不是带病运行。 断言(assertions):用断言把「不该发生」的假设显式写出来,一旦被打破马上崩。 构造即校验:对象在构造时就保证自己处于合法状态,构造不出来就抛错,而不是造出一个半残对象。 Java 集合的 fail-fast 迭代器:遍历过程中集合被并发修改,立刻抛 ConcurrentModificationException,而不是返回不可预期的结果(这一面在 failover / failfast / failback / failsafe 一文里也有提到)。 编译器:源码有语法错误就停止编译并报错,绝不会「跳过有问题的函数、把剩下的编译出来」。 构建工具 / 静态站点生成器:比如 Hugo,只要有一篇文档的 front matter 解析失败,就会中止整站构建——这正是我这个博客踩过的坑,记录在 一次构建失败导致的「假性停更」。 下面是一个最朴素的 fail-fast 校验: // fail fast: reject the bad input right at the boundary public void transfer(Account from, Account to, long amount) { if (from == null || to == null) { throw new IllegalArgumentException("account must not be null"); } if (amount <= 0) { throw new IllegalArgumentException("amount must be positive: " + amount); } // ... safe to proceed; state is known-good from here on } 为什么这么设计 fail-fast 背后的判断是:带着错误状态继续运行,比直接失败更危险。 ...

Created 30306-06-30 · Updated 30306-06-30 · 1 min · 189 words · -