我该如何处理断言失败

how should I deal with assert failure?

本文关键字:处理 断言 失败 何处理      更新时间:2023-10-16

在调试/发布模式下,是否有正确或可接受的方法来处理断言失败?

例如:假设我有一个函数,它返回std::vector

我期望返回向量的长度等于另一个对象的长度,我这样做:

std::vector<int> v = get_stuff();
ASSERT(v.size() == this->size() )
a = v[this->size() - 1 ];

现在,如果没有触发断言,这段代码将正常工作,在调试中,这可能会崩溃,但至少断言失败会警告我出了问题。问题是在释放模式下,会出现无声的崩溃。这是否意味着我还必须检查发布代码中的这个错误,然后进行处理?这是可能的,但我认为在它上面添加断言失败没有意义,因为它是由处理的

assert的含义与您想象的不同。它不能代替异常,我认为这就是你对它的看法。它在那里,这样你就可以在调试的早期发现问题。如果您在调试中得到断言,则修复它,然后进行测试。然后再次测试。并确保这种情况会持续下去。如果是。。。好吧,你有个bug。

这是否意味着我还必须检查发布代码中的这个错误,然后进行处理?

如果你期望它发生,是的。检查条件,抛出异常,并谨慎处理。发送错误报告。写入日志文件。更新软件。

断言适用于必须始终为true的条件。不用于异常处理。

它们反映了您在设计过程中所做的假设。如果get_stuff返回错误大小的向量存在任何合法的可能性,则必须单独处理。

您不局限于C库提供的断言,并且有一个在生产/发布版本中仍然运行的附加断言机制是有意义的:

  • 使用C库assert进行昂贵的(CPU、缓存、数据库加载…)检查,以避免降低生产/发布版本的速度
  • 对于即使在生产/发布版本中也要运行的廉价或至关重要的测试,使用您自己的断言机制,在这种情况下,失败表明您的程序此后无法正常运行:例如,如果程序操作的核心数据结构明显已损坏
  • 当您认为可以报告错误并恢复到有用状态以进行进一步的工作时,请使用异常/错误代码等,并且继续提供该服务是优先事项

因此,在您的示例中:

std::vector<int> v = get_stuff();
ASSERT(v.size() == this->size());
a = v[this->size() - 1];

您可以使用一个仅为调试模式的ASSERT,一个也将在生产中启动的ASSERT,或者使用。。。

a = v.at(this->size() - 1);

这样,如果问题在生产中出现,您就可以捕获并处理异常。为了获得异常处理案例的代码覆盖率,您需要为生产构建创建一个单元测试案例。

需要记住的是找到一个现实且可维护的平衡:如果你试图在运行时错误处理中过于详尽,你的代码的大小和复杂性可能会增加5到10倍,你的测试工作也会增加。因此,在处理的地方和处理的程度上要有选择性。简单的断言和堆芯倾倒等相对简单:一个没有测试用例的内衬,可以更自由地使用。

我的偏好是包括ASSERT(用于调试),然后是处理错误的代码。在调试运行中,断言失败会使系统停止,并让我立即看到问题。然后在发布版本中,处理错误情况的代码仍然运行。当然,处理错误的代码必须经过深思熟虑。生成一个崩溃转储文件(在Windows中为小型转储,在Linux中为核心转储)可能是有意义的。

通常你必须"处理"程序中的三种错误,我个人更喜欢用以下方式(ymmv)处理和分类它们:

  • 预计会频繁发生的错误(例如用户输入错误)。它们可能是(部分)可回收的。我通常通过检查/错误代码来处理它们,偶尔也会通过异常来处理
  • 检测到的异常错误,但通常不会出现。我总是对它们使用异常,它们通常会使操作在更大范围内失败
  • 可能不会发生的错误;这是"不可能"发生的。就像在向量类中一样,您希望人们使用适当的索引。您不想在发布模式下花费宝贵的周期来检查这些条件,但当很容易检测到这些情况时,请在调试模式下进行检查,以提供更好的替代方案来应对难以调试的崩溃(例如,使用assert(condition && "explanation of why you asser the condition here and what it means that it is violated")

我通常测试带有断言的函数的先决条件,例如一些指针不是null,或者值在范围内。我大量地散布它们,以便在调试模式下(我所有的测试和单元测试都在其中运行)可以发现这些错误。这有点像一些标准库所具有的"检测未定义行为"模式。