65.9K
CodeProject 正在变化。 阅读更多。
Home

软件开发中“快速失败!”原则入门

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.76/5 (24投票s)

2016 年 10 月 12 日

CPOL

9分钟阅读

viewsIcon

48799

本文介绍了“快速失败!”原则。它是什么?我们何时应该使用它?它如何帮助我们编写更好的代码?

当运行中的软件应用程序发生错误时,通常有三种可能的错误处理方法

  1. 忽略! 方法:忽略错误,应用程序继续执行

  2. 快速失败! 方法:应用程序立即停止并报告错误

  3. 安全失败! 方法:应用程序承认错误,并以可能最佳的方式继续执行

哪种方法是最好的?

您应该在您的应用程序中应用哪种方法?

在回答这个重要问题之前,让我们先看一个简单的例子。

假设我们要编写一个(粗糙的)Web应用程序,该应用程序将在喷泉附近显示一个警告消息,以告知人们该水被污染了。

以下HTML代码可以完成工作

<html>
   <body>
      <h2 style="color:red;">Important!</h2>
      <p>Please <b>DO NOT</b> drink this water!</p>
   </body>
</html>

浏览器中显示的结果如下所示

现在让我们插入一个小错误。在DO NOT之后,我们将</b>写成<b>,如下所示

<p>Please <b>DO NOT<b> drink this water!</p>

出现了两个有趣的问题

  • 应该发生什么?

  • 将会发生什么?

第二个问题很容易回答。我们只需要将有bug的HTML代码提供给浏览器即可。这是结果 - 在撰写本文时,Chrome,Edge,Firefox,Internet Explorer和Safari浏览器中都显示了此结果。

在继续阅读之前,请问自己:“浏览器应用了哪种方法?”……

显然,快速失败!方法没有被应用,因为应用程序继续运行而没有报告错误。唯一需要注意的区别是现在显示了更多粗体文本。但是整个消息仍然正确显示,人们得到了警告。没什么好担心的!

让我们尝试另一个错误。在DO NOT之前,我们将<b>写成<b,如下所示

<p>Please <b DO NOT</b> drink this water!</p>

这是结果 - 再次如前面提到的浏览器中所显示的那样

恐慌!现在程序的作用与预期完全相反。后果是可怕的。我们挽救生命的应用程序已经变成了一个杀人应用程序(但不是我们一直梦想着有一天会写的那种杀人应用程序)。

重要的是要认识到上面的例子不仅仅是一个理论上的,夸大的例子。有很多现实生活中的“小错误”导致灾难性后果的案例,例如Mariner 1宇宙飞船在升空后不久由于“缺少连字符”而爆炸。更多示例,请参见:软件错误列表

从上面的例子可以看出,不采用“快速失败!”原则的后果差异很大,可能从完全无害到极其有害。

注意
除非我们查看浏览器的源代码,否则我们不知道应用的是“忽略!”还是“安全失败!”方法。我猜HTML代码中的DONOT被解释为b标签的属性,但没有值(例如DO="foo"),并且不是标准HTML的一部分。因此,它们被忽略了。结束</b>可能也被简单地忽略了,因为“喝这水”被显示为粗体。

那么,对“应该发生什么?”这个重要问题的正确答案是什么?

嗯,这取决于情况。但是,有一些一般规则。

第一条规则是

  • 我们不应该“忽略!”错误 - 除非有非常好的理由。

这条规则是众所周知的,不需要进一步解释。

记住C程序员的10条诫命中的第6条规则,由Harry Spencer用古英语优雅地写成

  • “如果一个函数声称在遇到困难时返回错误代码,那么你应该检查那个代码,是的,即使检查代码会使你的代码大小增加一倍,并让你敲击手指感到疼痛,因为如果你认为‘这不会发生在我身上’,那么神灵一定会惩罚你的傲慢。”

第二条规则是

  • 在开发过程中,我们应该应用快速失败!方法。

这条规则背后的原理很容易理解

  • 快速失败!方法有助于调试。

    一旦出现问题,应用程序就会停止,错误消息有助于检测、诊断和纠正错误。因此,“快速失败!”方法可以带来更可靠的软件,降低开发和维护成本,并防止在生产模式下可能出现的沮丧和灾难。

    即使一个bug没有导致严重故障,也最好尽快检测到它,因为修复bug的成本会随着开发周期(编译、测试、生产时间)的推移呈指数级增长。

  • 开发模式下出现bug的后果通常是无害的。

    客户不会抱怨,钱不会转错账户,火箭也不会爆炸。

快速失败通常被认为是软件开发中的一项好习惯。以下是一些支持性的引述

  • “鼓励良好的编码习惯……这有很多推论,包括‘快速失败’……”

    Google Guava团队 - 哲学解释

  • “……‘立即且明显地失败’听起来会使您的软件更脆弱,但实际上会使其更健壮。错误更容易查找和修复,因此进入生产环境的错误更少。”

    Jim Shore / Martin Fowler - 快速失败

  • “一些最难追踪的bug(部分)是由默默失败并继续运行的代码引起的,而不是抛出错误……一旦检测到失败情况,最好尽快返回错误。”

    Henrik Warne - 13年棘手bug的18个教训

  • “我们不会花很长时间才发现有问题。我们快速失败……”

    Joshua Kerievsky - 现代敏捷简介

但是,当应用程序在生产模式下运行时,情况可能会发生根本性变化。不幸的是,没有放之四海而皆准的规则。实践表明,通常最好也默认应用“快速失败!”方法。一个忽略错误并继续任意运行的应用程序造成的最终损害通常比一个突然停止的应用程序造成的损害更糟。例如,如果一个会计应用程序突然停止工作,用户会很生气。但如果它默默地忽略错误并继续运行并产生错误结果(例如不平衡的资产负债表),用户会非常生气。“生气”比“非常生气”好。因此,在这种情况下,“快速失败!”方法更好。

在我们之前的HTML示例中,“快速失败!”方法也会好得多。假设浏览器没有继续执行,而是显示了一个错误消息。那么开发人员将立即意识到问题,代码可以快速轻松地修复,而不会造成任何损害。但是,即使有bug的代码进入了生产环境(出于奇怪的原因),最坏的情况也会不那么可怕。显示“请喝这水”可能很可怕。另一方面,不显示任何消息,或者只显示一个(令人费解的)错误消息,可能会导致极少数人敢于少量尝试饮用水。

在实践中,每个单独的案例有时都必须仔细研究。当最大可能的损害很高时,尤其如此,例如在医疗应用、资金转账应用或太空入侵者应用中。例如,在火箭尚未发射的情况下,应用“快速失败!”规则显然是正确的方法。但是一旦火箭发射,停止应用程序(或者更糟的是,忽略错误)就不再是选择了。现在必须应用“安全失败!”方法,以便我们尽力而为。

一个好的选择有时是快速失败,但要尽量减少损害。例如,如果文本编辑器应用程序中发生运行时错误,应用程序应首先自动将当前文本保存在临时文件中,然后向用户显示有意义的消息(“抱歉,……但您的当前文本已保存在临时文件abc.tmp中”),可选地向开发人员发送错误报告,然后停止。

因此,第三条规则是

  • 在关键应用程序中,必须实施安全失败!方法,以最大程度地减少损害。

总结:

  • 在开发模式下,我们应始终应用快速失败!方法。

  • 在生产模式下

    • 我们通常应默认倾向于快速失败!方法。

    • 可能导致严重损害的关键应用程序需要定制的、特定于上下文的和消除(或至少减少)损害的行为。在容错系统中必须应用安全失败并适当响应!方法。

同样的想法在《Unix编程艺术》中的优秀修复规则中得到体现,由Eric Steven Raymond撰写

  • “修复你能修复的——但当你必须失败时,要响亮且尽早地失败。”

注意
更多信息和示例可在Wikipedia上找到,网址为Fail fastFail safe容错计算机系统

在任何情况下,使用支持快速失败!原则的开发环境总是有帮助的。例如,编译型语言支持“快速失败!”规则,因为编译器可以立即报告大量错误。这里有一个简单的错误示例,很容易逃脱人眼,并可能导致“意外惊喜”,例如系统因无限循环而挂起。

var row_index = 1
...
row_indx = row_index + 1

像这样的拼写错误(即写row_indx而不是row_index)很常见,并且会立即被任何像样的编译器或(更好的是)智能IDE捕获。

幸运的是,有许多非常有效的快速失败!功能可以原生构建到编程语言中。它们都依赖于以下规则:

  • 错误应优先在编译时自动检测,否则应尽可能早地在运行时检测。

强大的快速失败!语言功能示例包括:静态和语义类型、编译时空安全(运行时没有空指针错误!)、契约设计、泛型类型参数、集成单元测试等。

比及早检测错误更好的方法是根本不设计它们。如果编程语言不支持易出错的编程技术,例如全局可变数据、隐式类型转换、被静默忽略的算术溢出错误、真值性(例如""0null等于false)等,就可以实现这一点。

因此,我们应始终优先选择支持快速失败!原则的编程环境(=编程语言+库+框架+工具)。我们将减少调试,并在更短的时间内生产出更可靠、更安全的代码。

历史

2016-10-25:添加了两处引用;修正了拼写错误

2016-10-12:初始版本

© . All rights reserved.