如何避免意外异常带来的问题?

How to avoid problems due to unexpected exceptions?

这是 C++ 编程中的一个不太常见的问题:

程序员 A 编写了一个 C++ 库(我们称它为 Foo),其中包含一个很好的 public API 程序员 B 的程序调用方法,以实现他的部分程序.在使用 Foo API 时,程序员 B 查看文档和头文件,但不太看 .cpp 文件,主要是因为 public API是为了隐藏实现细节。程序员 B 让他的程序开始工作,最初一切看起来都很好。

有一天程序正在做它的事情,这时 Foo 代码库中发生了一些不寻常的事情,导致 Foo 方法抛出异常。程序员 B 没有意识到该 Foo 方法可能会抛出该类型的异常,因此该异常要么未被捕获(并且程序崩溃),要么该异常被非常通用的异常处理程序捕获(例如 catch(. ..)) 并且没有得到充分的处理(因此程序做了一些非最佳的事情,比如建立一个带有神秘错误消息的对话框)。

由于我希望我的 C++ 软件健壮,我的问题是,避免上述情况的好方法是什么?要求程序员 B 通读 Foo 代码库中的所有 .cpp 文件(以及 Foo 调用的任何 C++ 代码库的所有 .cpp 文件,等等)寻找 throw 语句似乎不是一个合适的解决方案,因为它违背了封装的精神,即使做了也不能保证以后不会再添加更多的 throw 语句。要求程序员 A 记录所有可能抛出的异常似乎是个好主意,但在实践中,程序员 A 很可能会遗漏其中的一些异常,尤其是当他的代码依次调用另一个可能会或可能不会记录其 100% 异常的库时可能的例外。

在Java中有一些对自动异常处理检查的支持;例如你可以注释你的 Java 方法来声明它们可能抛出的异常,如果调用代码没有明确处理所有这些异常(或者它本身声明它可能抛出它们),编译失败并且程序员是被迫修复他的程序 - 从而防止上述问题场景。另一方面,C++ 没有这样的编译时执行,这意味着验证 C++ 程序是否正确处理所有异常似乎完全取决于程序员和 QA 团队。但是在一个重要的程序中,实际执行这样的验证似乎是不切实际的;是否有某种技术(自动或非自动)使 C++ 程序员可以确信他们的程序以一种经过深思熟虑的方式处理所有可能抛出的异常? (我认为,即使只是一个列出可以从各种调用中抛出的所有异常的程序也会很有用——至少会有一个已知可能性的列表来检查异常处理代码)

乍一看这个问题似乎是一个进退两难的问题。直到您发现以下见解。

对于异常而言,抛出异常、抛出异常、捕获异常,占据了抛出异常信息的90%以上的重要性。异常的类型很少那么重要。

事实证明,我们很少创建可以解决问题并提出补救解决方案的代码。几乎总是代码能做的唯一有用的事情就是报告命令失败及其错误消息。

如果库中使用的异常源自 std::exception 并支持对 what() 的有用调用,那么当您 report/log 该消息时,您已经移动到捕获 95%有用的信息。如果 what() 消息包含 __ FILE __ 、 __ LINE __ 和 __ func __ 信息,那么你已经掌握了大多数抛出的异常所提供的重要信息的 98%。

即使一个库没有仔细记录每个特定函数调用可以抛出的每个异常类型,一个体面的库也会提供用于它抛出的所有异常的基本类型(通常是std::exception 或从中派生的东西)。这个异常基础 class 将有一些方法来获取相关的错误消息(例如 what() 调用)。

但是即使您减少到使用 catch (...) 作为最后一道防线,您的错误消息仍然可以指示命令失败以及它是什么命令。而且您有信心处理每一个可能抛出的异常。