【问题标题】:Should I add exception-handling to existing code base?我应该向现有代码库添加异常处理吗?
【发布时间】:2011-12-19 14:20:11
【问题描述】:

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

我在开发一个在 Windows 环境中控制硬件卡的 SDK。

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

我们链接到正确的库,以便我们使用 nothrow new(lic.lib 而不是 licp.lib)。

大部分代码没有异常处理。编写代码时考虑到了这一点。

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 指针不会引发异常,我想知道通过 100 000 行代码并添加异常处理有多大用处,这将改变工作流程,如果不处理可能会导致内存泄漏适当地。我尝试了 SEH,但我认为开始使用 SEH 没有意义,而且它是 Microsoft 特有的,不是吗?

在我看来,我认为如果检查现有代码并简单地检查可能的崩溃(例如可能遗漏的除以零)会更有用。

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

谢谢!

【问题讨论】:

  • 你知道new永远不会返回NULL,对吧?

标签: c++ exception


【解决方案1】:

对于遗留代码,您应该在计划允许的情况下在少数地方引入异常处理;代码中访问最少的区域(以降低代码库其余部分出错的风险)或它们会产生最大收益的地方(引用错误的地方)。

我不建议仅仅为了到处添加异常处理而停止遗留项目。遗留代码最难的部分是修改它并使其保持工作。毕竟,它已经过测试并且它的行为有据可查。

【讨论】:

  • 在这种情况下,将遗留代码错误处理转换为异常处理会给 OP 带来哪些优势?
  • @Thomas M. 感谢您的回答。代码确实已经过密集测试,我担心修改它可能/会破坏它的稳定性。
【解决方案2】:

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

“异常处理”并没有准确地说出你的意思,所以我将从基本的东西开始:标准 C++(你将问题标记为c++要求你写“处理异常”的代码,适用于除琐碎的应用程序之外的所有应用程序,否则您的代码是错误。 C++ 标准库的各个部分都允许引发异常,包括您的示例代码使用的new。因此,您的代码可能已经有可能在其中抛出异常,它必须“处理”。在那种情况下会发生什么?基本上,你必须写“exception safe code”。

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

您应该首先专注于使您的代码异常安全。

【讨论】:

  • 我不得不承认我对异常安全代码仍然有些困惑。正如我在我的 OP 中提到的,我们正在使用 new(nothrow)。我看到它的方式是我们需要修改代码以引发异常而不是返回错误代码。并开始使用 new(throw)。我们已经尽可能使用智能指针。
  • 我们已经尽可能地使用智能指针。那应该不是问题。此外,如果出现异常情况,我们的代码不会泄漏,并且在没有异常安全的情况下也不会进入不一致状态。我们经常使用 STL。如果我们与 new(nothrow) 链接,std::deque::push_back 会抛出异常吗?
【解决方案3】:

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

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

所以我实际上不建议重写遗留代码来尝试捕获异常并在任何地方使用 RAII。您可能会在绝对必须修改的代码中到处使用 RAII,但我会尝试寻找不对其进行过多更改的理由。为它编写测试并尝试稳定它并将其变成一个黑匣子;一个功能库,可以在无限期地穿越无尽的 LOC 时使用、维护和更改。

现在我在这里投稿并复活这个旧线程(道歉!)的主要原因是因为这个评论:

代码通常会检查除以 0 或检查 NULL 取消引用之前的指针。但是,还是会发生这样的 案子会发生。由于除以零或取消引用 NULL 指针 不要抛出异常 [...]

在我看来,您应该响应诸如空指针访问或除以零之类的事情,因为这些都是程序员错误。除非您在一个任务关键型软件中工作,即使该软件有错误,您也希望优雅地恢复,以尝试降低牺牲生命的风险或类似的东西,否则您不希望应用程序优雅地恢复编程错误的事件。您通常不想这样做的原因是,它的缺点是隐藏错误,使它们保持沉默,允许用户忽略并解决它们,甚至可能懒得报告它们。

对于程序员的错误,您通常应该支持assert,它根本不涉及异常。如果断言失败,调试版本中的软件将停止运行,通常会显示一条错误消息,告诉您断言在哪里失败,直至准确的代码行。这通常是在运行调试器以响应错误报告时检测和修复这些编程错误的最快选择,因此请随意assert

异常对于程序员无法控制的外部异常事件最有用。一个例子是读取一个被证明是损坏的文件。这不在程序员的控制范围内,因此适合扔和恢复。另一个例子是无法连接到服务器,用户为应该完成的操作按下中止按钮等等。这些是异常的外部输入事件,而不是程序员的错误。

从诸如空指针访问和除以零之类的程序员错误中恢复的最佳方法是首先检测它们(assert 很方便),编写一个测试来重现它,修复它并让测试通过,然后调用一天,不要抛出异常并捕获它们,同时将这些错误留在那里。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-04-16
    • 1970-01-01
    • 2020-09-21
    • 2021-08-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多