断言用于检查是否满足条件(前置条件,后置条件,不变量),并帮助程序员在调试阶段找到漏洞。
例如,
void f(int *p)
{
assert(p);
p->do();
}
我的问题是我们是否需要假设在发布模式下无法满足条件并相应处理案例?
void f(int *p)
{
assert(p);
if (p)
{
p->do();
}
}
毕竟,断言意味着它所测试的条件永远不应该是假的。但是,如果我们不检查它并且它失败了,程序崩溃了。听起来像是两难。你们是如何处理它的?
答案 0 :(得分:21)
如果断言失败,程序应该崩溃。
断言失败意味着程序员在理解程序流程如何进行时犯了一个根本性的错误。这是一种发展援助,而不是生产援助。在生产中,可以处理例外,因为它们“可能”发生,而断言应该“永不”失败。
如果你在营地里说:“哦,但如果断言在生产中失败怎么办?我需要抓住它们!”那你就错过了这一点。问自己,在这种情况下,你为什么不抛出异常(或以其他方式处理错误)?
一般来说,断言是而不是只是“如果条件不满足,抛出异常”的简写(好吧,有时这是操作语义,但它不是指示性的语义)。相反,断言失败意味着应用程序处于开发人员认为甚至不可能 的状态。你真的希望代码在这种情况下继续执行吗?显然(我会说),否。
答案 1 :(得分:2)
防守编程总是最好的。您应该始终认为,尽管您进行了所有测试,但您的应用程序仍会附带错误。因此,在您可以避免NULL指针参与并继续前进的情况下添加NULL检查符合您的最佳利益。
但是,有些情况下根本没有简单的方法来避免崩溃,在这种情况下,断言是在开发周期中检测问题的唯一方法。
但重要的一点是 - 断言通常也用于检测数据完整性方面的主要问题。如果您继续通过这些断言,则可能存在破坏数据的风险。在这些情况下,崩溃而不是破坏数据可能更好。 (显然,任何类型的崩溃处理程序至少会带来一个带有错误描述的合理用户界面会更好。)
答案 2 :(得分:1)
严格地说,第二个代码有冗余。
void f(int *p)
{
assert(p);
if (p) // Beats the purpose of assertion
{
p->do();
}
}
断言意味着发生了错误。一些意外/未处理的东西。在上面的代码中,
1)您正确处理p为null的情况。 (通过不调用p-> do()) - 这应该是正确的/预期的事情。但是,断言是误报警。
2)另一方面,如果不调用p-> do(),会出现问题(可能在代码或输出中更进一步),那么断言是正确的,但应该没有意义无论如何继续。
在上面的代码中,程序员正在努力处理那些错误的案例。
也就是说,有些人喜欢将断言视为出错的东西,但让我们看看我们是否仍然得到正确的输出。 IMO,这是一个糟糕的策略,在修复bug时会产生混淆。
答案 3 :(得分:0)
断言是调试代码,而不是操作代码。不要用它们来捕获输入错误。
答案 4 :(得分:0)
断言用于捕获测试中的错误。理论上说,你已经进行了足够的测试,知道一旦你发布它就会有效。
如果在实际操作中可能出现这种情况,请不要依赖断言 - 使用异常或其他错误机制。
答案 5 :(得分:0)
断言对于调试非常有用,如上所述。他们永远不应该把它变成生产代码(如编译,当然可以将它们包装在#ifdefs中)
如果您遇到无法控制的问题需要纠正,并且您需要检查生产代码,我会执行以下操作:
void f(int *p)
{
if (!p)
{
do_error("FATAL, P is null.");
}
p->do();
}
do_error是一个记录错误并干净地退出的函数。
答案 6 :(得分:0)
我说在发布版本中留下它们。任何构建都必然存在错误。在您的产品中设置断言意味着当您从àuwer接收到问题报告时,您可以更轻松地查明问题。
不要在处理异常方面付出太多努力。只需确保您可以获得完整的异常,包括stacktrace。这特别适用于à发布版本。
答案 7 :(得分:0)
由于很多人都在评论将断言置于发布模式:
在我的工作中,效率非常重要(有时,在大型数据集上执行需要几十个小时到几天才能完成)。因此,我们有特殊的宏,只在调试代码中断言(在QA等期间运行)。例如,for循环中的断言肯定是开销,您可能希望在发布代码中避免它。毕竟,如果所有iz都很好,断言不应该失败。
释放代码断言良好的一个例子是 - 如果逻辑根本不应该触及特定的代码分支。在这种情况下,assert(0)很好[因此任何类型的断言(0)都可以保留在释放代码中。