我应该在现有的代码库中添加异常处理吗?

Should I add exception-handling to existing code base?

本文关键字:添加 异常处理 代码 我应该      更新时间:2023-10-16

我想知道在现有代码中添加异常处理的优点和缺点。

我在一个SDK上工作,在Windows环境中控制h/w卡。

SDK由100多个相互交互的dll组成。我们现有的代码库可能包含100,000行(如果不是1,000,000行)的代码。我们的模块也是高度多线程的。

链接到相应的库,这样就可以使用nothrow new (lic)。

大部分代码没有异常处理。代码就是这样写的

int *p = new int[size];
if (p == NULL)
{
   // handle this case...
   // most probably return an error code
}
char *q = new char[size];
if (q == NULL)
{
    delete[] p;
   // handle this case...
   // most probably return an error code
}

我们也使用RAII技术。例如,我们在堆栈上创建了一个对象,它自动等待并释放临界区。

我们想提高我们SDK的稳定性。我们在考虑添加异常处理,但我不相信这是提高稳定性的正确方法。我必须承认我在EH方面没有太多经验。

通常,在解引用之前,代码会检查是否有除以0或NULL指针。但是,这种情况还是会发生。由于除以零或解引用NULL指针不会抛出异常,我想知道通过10万行代码并添加异常处理有多大用处,这将改变工作流程,如果处理不当,可能会导致内存泄漏。我尝试过SEH,但我不认为开始使用SEH是有意义的,它是微软特有的,不是吗?

在我看来,我认为如果检查现有的代码并简单地检查可能的崩溃会更有用,比如除以0,这可能已经错过了。

另外,如果我要添加异常处理,我该如何进行呢?一次修改所有模块或者从自下而上开始(意思是,如果模块A调用模块B,模块B调用模块C,我将修改C,然后B,然后A,因为我们经常发布我们的软件,我们可能只有时间在下一个版本之前修改C)。

谢谢!

我想知道在现有代码中添加异常处理的优点和缺点。

你没有说你所说的"异常处理"是什么意思,所以我将从一些基本的东西开始:标准c++(你将问题标记为c++) 要求你编写"处理异常"的代码,除了琐碎的应用程序,否则你的代码是错误的。c++标准库的各个部分都允许抛出异常,包括示例代码使用的new。因此,您的代码已经有可能在其中抛出异常,它必须"处理"。在这种情况下会发生什么?基本上,你必须编写"异常安全代码"。

  • 程序在面对异常时泄漏资源是错误的。你使用的是RAII,所以应该没问题。
  • 抛出异常后,任何对象进入不一致状态都是错误的。确保更棘手。

你应该首先关注使你的代码异常安全。

对于遗留代码,您应该在进度允许的一些地方引入异常处理;要么是代码中访问最少的区域(以减少对代码库其余部分的错误风险),要么是它们将产生最大利益的地方(关键错误位置)。

我不建议仅仅为了到处添加异常处理而拖延遗留项目。关于遗留代码最困难的部分是修改它并保持它的工作。毕竟,它已经经过了测试,它的行为也有很好的文档记录。

我同意Raedwald的观点,如果你使用c++而没有非常仔细的编码标准来避免EH(例如:使用nothrow new,避免标准容器等),我假设遗留代码没有,那么代码已经被破坏了,很可能泄漏,并且在面对异常时已经做了零星的事情,如果你依赖dynamic_cast,它可能已经遇到了bad_allocbad_cast

也就是说,从遗留代码库非常实用的角度来看,遗留代码很可能设法摆脱它。毕竟,有多少重要的应用程序可以优雅地从bad_alloc异常中恢复,而不需要非常显式地控制内存分配?不是很多,也不会让整个世界戛然停止。

所以我实际上不建议重写遗留代码来尝试捕获异常并在任何地方使用RAII。您可能会在代码中到处使用RAII,但您绝对需要修改,但我会尝试寻找不进行过多更改的原因。为它编写测试,试着稳定它,把它变成一个黑盒;要使用的功能库,而不是在无休止的LOC中进行维护和更改。

现在我在这里投入并重新开始这个旧线程的主要原因(抱歉!)是因为这个评论:

代码通常检查是否除以0或检查是否为NULL指针,然后解引用它。但是,仍然会发生这样的案件总会发生的。因为除零或对NULL指针解引用不要抛出异常[…]

在我看来,你应该不应该抛出对诸如空指针访问或除以零之类的事情的响应,因为这些都是程序员错误。除非您使用的是关键任务软件,即使软件存在错误,您也希望能够优雅地恢复,以减少生命损失或类似的风险,否则您不希望应用程序在出现编程错误时能够优雅地恢复。你通常不想这样做的原因是它有隐藏bug的缺点,使它们沉默,允许用户忽略和绕过它们,甚至可能懒得报告它们。

而对于程序员的错误,你通常应该选择assert,它根本不涉及异常。如果断言失败,调试构建中的软件将停止运行,通常会显示一条错误消息,告诉您断言在哪里失败,精确到代码行。当运行调试器响应错误报告时,这通常是检测和修复这些编程错误的最快方法,所以请随意使用assert

异常对于程序员无法控制的外部异常事件最有用。一个例子是读取一个已损坏的文件。这不在程序员的控制范围内,所以抛出和恢复是合适的。另一个例子是无法连接到服务器,用户干扰了一个应该完成的操作的中止按钮,等等。这些都是异常的外部输入事件,而不是程序员的错误。

从像空指针访问和除零这样的程序员错误中恢复的最好方法是首先检测它们(assert很方便),编写一个测试来重现它,修复它并使测试通过,然后结束一天,而不是抛出异常并捕获它们,同时将这些错误留在那里。