Archive for 2020年12月

Exception Reconsidered

2020/12/24

我一直是 C++ exception 的反对者。《Programming in Lua(二)- 异常与错误码》提到 Russ Cox 谈论 C++ exception 的所谓「根本缺陷」。我以前的认识是返回 error code 是最好的错误处理方式。

和返回 error code 不同,exception 会触发 call stack 的回退,从而失去了错误发生时的程序运行状态(指代运行状态的术语是 continuation)。这是我之前极力避免 exception 的最大原因。但是最近几年的实践里我发现即使不使用 exception 很多情况下也难以避免丢失 continuation。比如说,越来越普遍的 asynchronized APIs 让 continuation 在代码中的表达更加复杂,代码稍作简化就会丢掉 continuation 信息。另一个例子是,任何程序都无法避免 crash。在操作文件的过程中发生 crash,错误处理只能由下一次程序运行来完成,而 continuation 毫无疑问已经丢失。

如果使用 exception 并不是丢失 continuation 的唯一原因,就必须在假定 continuation 必然丢失的情况下进行错误处理。

计算机工程中减少对 continuation 依赖的方法是增加数据结构的能力。这点写过 parser 的人都会了解。实际上我以前提到过 exception 很适合关系数据库的 CRUD 操作的错误处理,那么在设计数据结构时把数据库的 two-phase commit 借鉴过来就可以了。当操作数据结构的时候,把要操作的部分(比如某个 tree 的一个 sub-tree,或者某个文件 folder)先复制一份。全部操作成功之后,才用这个拷贝替换原来的数据,只需保证这个替换过程是 atomic 的过程。通常的操作都能满足这个要求,比如指针赋值和文件目录的改名(而且后者是 crash-proof)。

我想 exception 在计算机工程里引起这么大的争议,和错误的教育是分不开的。基础理论很早就对 continuation 和数据的关系有深入的了解,而在介绍 exception 的文章里,谈到 exception-safe 仅仅等同于「正确的 pair 操作」。连 Russ Cox 之类的优秀工程师也会把这个教育问题当成 exception 机制的固有问题。SQLite 之类方案的存在一定程度上缓解了这个争议,不过更根本的方法是应该对常用的数据结构提供 copy-on-write 的复制机制。