【问题标题】:What is the strategy if assertion fails如果断言失败,策略是什么
【发布时间】:2010-10-29 05:55:59
【问题描述】:

断言用于检查是否满足条件(前置条件、后置条件、不变量),帮助程序员在调试阶段发现漏洞。

例如,

void f(int *p)
{
  assert(p);
  p->do();
}

我的问题是我们是否需要假设在发布模式下无法满足条件并相应地处理案例?

void f(int *p)
{
  assert(p);

  if (p)
  {
    p->do();
  }
}

毕竟,断言意味着它测试的条件绝不应该为假。但是,如果我们不检查它并且它失败了,程序就会崩溃。听起来是个两难的选择。大家是怎么处理的呢?

【问题讨论】:

标签: c++ assertion


【解决方案1】:

如果断言失败,程序应该崩溃

断言失败意味着程序员在理解程序流如何继续进行时犯了一个根本性错误。这是一种发展援助,而不是生产援助。在生产环境中,可能会处理异常,因为它们“可能”会发生,而断言应该“永远不会”失败。

如果您在阵营中说:“哦,但是如果断言在生产中失败怎么办?我需要抓住它们!”那么你就错过了重点。问问自己,在这种情况下,为什么不直接抛出异常(或以其他方式处理错误)?

一般来说,assert 只是“如果条件不满足,抛出异常”的简写(嗯,有时这是操作语义,但不是指称语义)。相反,断言失败意味着应用程序处于开发人员不相信甚至可能的状态。您真的希望代码在这种情况下继续执行吗?很明显(我会说),没有

【讨论】:

  • 请注意,我的回答并没有明确解决断言是否应该包含在生产版本中的概念(它们不应该),因为它超出了问题。
【解决方案2】:

防御性编程总是最好的。您应该始终假设,尽管您进行了所有测试,但您的应用程序仍会出现错误。因此,在您可以避免 NULL 指针引用并继续前进的情况下添加 NULL 检查符合您的最佳利益。

但是,在某些情况下根本没有简单的方法来避免崩溃,在这些情况下,断言是您在开发周期中检测问题的唯一方法。

但有一点很重要 - 断言也经常用于检测数据完整性的主要问题。如果您继续通过这些断言,您可能会冒损坏数据的风险。在这些情况下,最好是崩溃而不是破坏您的数据。 (显然,任何类型的崩溃处理程序至少会提供一个带有错误描述的合理用户界面)。

【讨论】:

  • 向上。我同意您的想法,即有时让具有良好 UI 通知的程序崩溃总比让它在不通知的情况下以不良状态运行要好。
  • @Eric:如果您可以确定程序处于“不良状态”,则可以相应地处理它(例如带有 UI 通知)。在这种情况下不需要 assert,因为抛出异常或包含错误处理代码就可以了。此外,如果您不是 100% 相信程序 不可能 处于这种状态,那么 assert 不是正确的构造 - 您应该再次,只需测试状态并抛出异常或处理错误。 断言不会说“我希望这是真的”,而是说“我断言这是(必然)真的”。跨度>
  • 您可以检测到不良状态,但不一定能从中恢复。如果一个变量有一个它不应该有的值,你如何从中恢复?猜出正确的价值?这表明其他东西可能完全被破坏,导致这个变量变坏。甚至可能会破坏记忆。你无法从中恢复过来。此外,出于性能原因,您不希望在发布版本中花费太多时间进行错误检查。 (我的要求可能和你的不同,我主要是在做游戏 - 每个分支都在其中的一些核心功能中计数。)
  • @user359996:如果断言不会 100% 发生,那么在发布版本中保留它有什么好处?
  • @user\d+:我认为我们可以同意的一件事是断言必须在生产构建中编译出来(大多数 API 已经为此设置了)。如果某些内容为 NULL 不会产生后果,我不介意快速检查 NULL,但除此之外,我会吃掉异常并有一个全局未捕获的异常处理程序来显示 GUI。
【解决方案3】:

断言是调试代码,而不是操作代码。不要使用它们来捕获输入错误。

【讨论】:

    【解决方案4】:

    严格来说,第二个代码是有冗余的。

    void f(int *p)
    {
      assert(p);
      if (p)    // Beats the purpose of assertion
      {
        p->do();
      }
    }
    

    断言意味着发生了错误。出乎意料/未处理的事情。在上面的代码中,要么

    1) 您正在正确处理 p 为空的情况。 (通过不调用 p->do())- 这应该是正确/预期的事情。然而,那么断言是一个误报

    2) 另一方面,如果不调用 p->do(),就会出错(可能在代码或输出中更进一步),那么断言是正确的,但应该没有意义无论如何都要继续。

    在上面的代码中,程序员正在加倍努力地处理无论如何都是错误的情况。

    也就是说,有些人喜欢将断言视为出了点问题,但让我们看看我们是否仍然得到正确的输出。 IMO,这是一种糟糕的策略,会在错误修复期间造成混乱。

    【讨论】:

      【解决方案5】:

      断言用于捕获测试中的错误。理论上,您已经进行了足够好的测试,知道一旦发布它就会起作用。

      如果在实际操作中可能出现这种情况,请不要依赖断言 - 使用异常或其他一些错误机制。

      【讨论】:

      • 那么,如果经过彻底测试,我是否需要在发布模式下删除断言?或者只是将它们留在那里,以防其他维护代码的开发人员将来进行更改?
      • @Eric,通常您使用assert 宏,当您构建它以供发布时,它会自动编译为空代码块。那么就没有理由摆脱它们,它们将对任何修改代码的人有用。它们也充当文档。
      【解决方案6】:

      正如您提到的,断言对于调试很有用。他们永远不应该把它变成生产代码(编译后,当然可以将它们包装在#ifdefs中)

      如果您遇到无法纠正的问题并且您需要检查生产代码,我会执行以下操作:

      void f(int *p)
      {
      
        if (!p)
        {
          do_error("FATAL, P is null.");
        }
      
        p->do();
      }
      

      其中 do_error 是一个记录错误并干净退出的函数。

      【讨论】:

        【解决方案7】:

        我说将它们留在 à release build 中。任何构建中都必然存在错误。在您的产品中包含断言意味着当您收到来自 à uwer 的问题报告时,您可以更轻松地查明问题。

        不要在处理异常方面付出太多努力。只需确保您可以获得完整的异常,包括堆栈跟踪。这尤其适用于发布版本。

        【讨论】:

          【解决方案8】:

          由于很多人都在评论将断言置于发布模式:

          在我的工作中,效率非常重要(有时,大型数据集的执行需要几十个小时到几天才能完成)。因此,我们有只在调试代码中断言的特殊宏(在 QA 等期间运行)。例如,for 循环中的断言绝对是一种开销,您可能希望在发布代码中避免它。毕竟,如果一切顺利,断言不应该失败。

          发布代码断言很好的一个例子是——如果逻辑根本不应该触及特定的代码分支。在这种情况下,assert(0) 很好[因此任何类型的 assert(0) 都可以始终留在发布代码中]。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-05-29
            • 1970-01-01
            • 2019-01-19
            • 2013-09-06
            相关资源
            最近更新 更多