五、可恢复错误:类型导向的异常
Recoverable Errors: Type-Directed Exceptions

我们当然不是只需要“放弃”就够了。在很多情况i西安,程序员能够把程序从发生的错误中恢复过来。例如:
- 文件 I/O
- 网络 I/O
- 解析数据(例如编译器的语法分析器)
- 验证用户数据(例如用户提交的网页表单)
在这些情况下,遇到错误时你通常不会想要触发放弃。相反,你的程序应该做好了随时遇见这些错误的准备,并且能够合理地解决它们。通常来说是向某些人传递一些信息:例如在网页上输入数据的用户、系统的管理员、开发人员等等。当然,如果合适的话,放弃仍然可以作为一种选择,但通常来说这有点太夸张了。尤其是对于 IO 操作来说,这会让整个系统太过脆弱。想象一下:每当你的网络丢了一个包时,你的程序就决定从此消失!
使用异常
我们使用异常来应对可恢复错误。不是不受检查的那种,跟 Java 的受检查异常也不太一样。
必须要说明的是:虽然 Midori 使用异常,但一个没有标记为 throws 的方法永远不允许抛出异常。 Java 中提供的鬼鬼祟祟的 RuntimeException 在这是不存在的。我们其实也不需要它,因为在 Java 中使用 RuntimeException 的那些情况,在 Midori 中会使用放弃来进行处理。
在最终的系统中,我们神奇的发现,大约有 90% 的函数不会抛出异常!或者说,它们根本不允许抛出异常。这与 C++ 之类的系统形成了鲜明对比——在那些系统中,你必须时刻尽力地避免碰上异常,还需要使用 noexcept 来标记。由于存在放弃机制,API 调用仍然可能会失败,但只有调用者没有满足声明的合约时在会发生——类似于传入的参数类型错误。
我们选择使用异常,但这从一开始就有争议。命令式、过程式、面向对象和函数式语言的方法,在我们组里全都有人支持。C 程序员想要用错误码,担心我们会重新发明 Java 的异常设计,甚至更糟糕—— C# 的设计。函数式的观点认为我们应该使用数据流来应对所有错误,异常是一种面向控制流的方法。最后,我认为我们选择的是一种集所有优点于一身的组合模型。一会我们就会看到,我们的确提供了一种机制来将错误看作是一等公民,并且正如开发人员想要的一样,使用数据流风格的编程方法来处理这些偶发情况。
最重要的是,我们使用这种模型写了一堆的代码,对我们来说这个模型工作的很好,甚至得到了经常使用函数式语言的朋友的认可。由于我们也从返回码模型中汲取了一些经验,这让 C 程序员们也很开心。
语言和类型系统
当时,我做了一个有争议的决定。正如当我们改变了函数的返回类型时,一定会产生兼容性的影响,你也不应该认为改变了函数的异常类型时就没什么兼容性影响。换句话说,异常和错误码一样,都是返回值的一种!
这一点在之前介绍受检查的异常时提到过,它是受检查异常的反对者的论据之一。我的回答有点老套,但很简单:反对得不对。你正在使用的是一门静态类型编程语言,而异常的动态特性才是它们难用的原因。我们想要去解决这些问题,因此我们接受了“异常是一种返回值”的观点,并辅之以强类型。我们从没想过要回头。错误码和异常之间就这样建立起了一座桥梁。