传统的 try-catch 异常处理是否是编程语言发展中的弯路?

https://www.zhihu.com/question/425726667/answer/1525039692

我不觉得是弯路,毕竟对于多数的项目来说,写得爽比线上崩溃一两次要重要得多。

先说一下题主提到的第二点,

2) 异常处理强制了进行动态类型识别,这是一种额外的开销(虽然并不大)

题主指的应该是在异常发生时对异常对象的动态类型转换?其实这也可以看作异常处理的优势:在没有异常发生时,不需要这种额外开销。通过返回值(包括返回 Result)传递错误,由于存在更多的分支,给编译器优化、分支预测、指令 Cache 带来了负面影响;而异常通常只需要在栈上多开一点点空间(用来存放异常处理指针)。从性能上说,虽然在异常发生时它的性能要低于返回值,但在正常执行时,异常反而存在优势。

至于题主说的第一点,

许多时候面对一个子函数调用,我们可能无法知道它是否会抛出异常(当然有些语言有异常规格说明,但其实形同虚设),这导致有些时候当我们错误地假定函数不会抛出异常时,就会出现资源泄漏。写出强异常安全的代码在try-catch显得很隐晦,因而困难。

这就要说到异常的分类了。包括 C++ 和 C# 在内的很多语言,使用的都是 Unchecked Exception;而 Java 用的是 Checked Exception (除了 RuntimeException 之外)。题主谈到的,确实是 Unchecked Exception 的弊病。在我看来,Unchecked Exception 其实是最不靠谱的那种。如果一门语言使用的是 Checked Exception,那么理论上在编写强安全的代码时,难度应该与使用返回值/Result的语言没有差别。

然而,使用 Checked Exception 的 Java 被吐槽最多的,正是它的异常处理。很奇怪吧?Java设计出checked exception有必要吗?

但正如 Andrew 在一篇采访 The Trouble with Checked Exceptions 中所说,在某些时候(比如应用开发),大多数人并不关心如何处理异常,他们只关心当异常发生时我要处理好后事(C# 的 using),剩下的事就交给最上层的 catch 去做好了。这是大家喜欢 Unchecked Exception 的原因。

Unchecked Exception 很烂,但是大家就是喜欢,毕竟懒是人类的天性。

所以,一些项目会禁用 C++ 的异常,甚至不惜自己魔改一套不用异常的 STL,尤其是那些偏底层的、关键的、崩溃代价很高的项目。而大多数人做的并不是这样的项目,写得爽比线上崩溃一两次要重要得多。

题主说的第三点,

许多基于try-catch的编程语言,并不完全强制用户处理所有异常,这带来了便捷,但却又使得程序员会忽略一些本该处理的异常。

这也主要是 Unchecked Exception 的问题。对于不支持 Option/Result 的语言(比如 C 和 go),如果使用返回值的方式,其实同样会导致程序员忽略处理错误。


Rust 以及很多函数式语言的 Option/Result 确实也是我目前最喜欢的错误处理模型了。理论上,一门语言可以完全屏蔽它在底层对错误的处理方式,比如看起来是使用返回值,其实是抛了异常;或者看起来像是抛了异常,其实只是返回了一个值。因为它们只是两种不同的错误处理方式,所提供的能力是一致的;语言具体怎么表达,才是最影响用户编程体验的。我知道的唯一一个后者就是 Haskell,它让你有一种在使用传统的 try/catch 的错觉。

很多使用异常的语言对 try/catch 的支持还有其他的问题,比如语法不够简洁、处理粒度不够细等等。

另外再次推荐一下这篇文章(长文警告):Joe Duffy – The Error Model​

以及我的翻译:dontpanic:错误模型 – The Error Model​

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注