如何避免意外异常导致的问题

How to avoid problems due to unexpected exceptions?

本文关键字:问题 异常 何避免 意外      更新时间:2023-10-16

在C++编程中有一个不常见的问题:

程序员A编写了一个C++库(我们称之为Foo),其中包括一个很好的公共API,程序员B的程序调用该API上的方法,以便实现他的程序的一部分。当使用Foo API时,程序员B查看文档和头文件,但不太查看.cpp文件,这主要是因为拥有公共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()的有用调用,那么当您报告/记录该消息时,您已经开始捕获95%的有用信息。如果what()消息包含__FILE__、__LINE_和__func___信息,那么您将获得大多数抛出异常所提供的98%的重要信息。

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

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