179

在 comp.lang.c++.moderated 上有一个关于断言(在 C++ 中默认仅存在于调试版本中)是否应该保留在生产代码中的讨论。

显然,每个项目都是独一无二的,所以我的问题不是是否应该保留断言,而是在哪些情况下这是值得推荐的/不是一个好主意。

通过断言,我的意思是:

  • 一种运行时检查,用于测试一个条件,当该条件为假时,会显示软件中的错误。
  • 程序停止的一种机制(可能在非常少的清理工作之后)。

我不一定在谈论 C 或 C++。

我自己的观点是,如果你是程序员,但不拥有数据(大多数商业桌面应用程序就是这种情况),你应该继续使用它们,因为失败的断言表明存在错误,你不应该去有一个错误,有损坏用户数据的风险。这迫使您在发布之前进行严格的测试,并使错误更加明显,从而更容易发现和修复。

你的意见/经验是什么?

干杯,

卡尔

在此处查看相关问题


回应和更新

嘿,格雷厄姆,

断言是错误的,纯粹而简单,因此应该像对待一个断言一样处理。由于应在发布模式下处理错误,因此您实际上并不需要断言。

这就是为什么我在谈论断言时更喜欢“bug”这个词。它使事情变得更加清晰。对我来说,“错误”这个词太模糊了。丢失的文件是错误,而不是错误,程序应该处理它。试图取消引用空指针是一个错误,程序应该承认有些东西闻起来像坏奶酪。

因此,您应该使用断言测试指针,但使用正常的错误处理代码测试文件的存在。


稍微偏离主题,但在讨论中很重要。

提醒一下,如果您的断言在失败时闯入调试器,为什么不呢。但是文件无法存在的原因有很多,完全不受代码的控制:读/写权限、磁盘已满、USB 设备已拔出等。由于您无法控制它,我觉得断言是不是处理这个问题的正确方法。

卡尔


托马斯,

是的,我有 Code Complete,并且必须说我非常不同意那个特别的建议。

假设您的自定义内存分配器搞砸了,并将一块仍由其他对象使用的内存归零。我碰巧将这个对象定期取消引用的指针归零,其中一个不变量是这个指针永远不会为空,并且您有几个断言来确保它保持这种状态。如果指针突然为空怎么办。您只是 if() 围绕它,希望它有效吗?

请记住,我们在这里讨论的是产品代码,因此没有闯入调试器并检查本地状态。这是用户机器上的一个真正的错误。

卡尔

4

17 回答 17

96

断言是不会过时的注释。它们记录了哪些理论状态是预期的,哪些状态不应该出现。如果代码发生更改,因此状态允许更改,开发人员很快就会收到通知并需要更新断言。

于 2009-01-08T12:21:02.377 回答
65

请允许我引用 Steve McConnell 的 Code Complete。关于断言的部分是 8.2。

通常,您不希望用户在生产代码中看到断言消息;断言主要用于开发和维护期间。断言通常在开发时编译到代码中,并从代码中编译出来用于生产。

但是,稍后在同一部分中,给出了以下建议:

对于高度健壮的代码,断言然后处理错误。

我认为只要性能不是问题,就可以保留断言,而不是显示消息,而是将其写入日志文件。我认为该建议也在 Code Complete 中,但我现在没有找到它。

于 2008-08-20T12:29:46.277 回答
37

让断言在生产代码中保持打开状态,除非您测量到在关闭断言的情况下程序运行速度明显更快。

如果不值得衡量以证明它更有效,那么为了性能赌博而牺牲清晰度是不值得的。” - Steve McConnell 1993

http://c2.com/cgi/wiki?ShipWithAssertionsOn

于 2010-05-19T22:52:53.040 回答
24

如果您甚至考虑在生产中保留断言,那么您可能认为它们是错误的。断言的全部意义在于您可以在生产中将其关闭,因为它们不是您解决方案的一部分。它们是一种开发工具,用于验证您的假设是否正确。但是当您投入生产时,您应该已经对您的假设充满信心。

也就是说,有一种情况我会在生产中打开断言:如果我们在生产中遇到一个可重现的错误,我们很难在测试环境中重现,那么在打开断言的情况下重现错误可能会有所帮助在生产中,看看它们是否提供了有用的信息。

一个更有趣的问题是:在您的测试阶段,您什么时候关闭断言?

于 2014-01-20T23:06:12.603 回答
17

断言不应该在生产代码中。如果一个特定的断言看起来可能在生产代码中有用,那么它不应该是一个断言;它应该是运行时错误检查,即编码如下:if( condition != expected ) throw exception

“断言”一词的意思是“仅在开发时进行的检查,不会在现场执行”。

如果您开始认为断言可能会进入该领域,那么您将不可避免地开始产生其他危险的想法,例如想知道任何给定的断言是否真的值得做出。没有不值得做出的断言。你永远不应该问自己“我应该坚持这一点吗?” 你应该只问自己“有什么我忘记断言的吗?”

于 2014-09-23T15:20:50.847 回答
7

除非分析显示断言导致性能问题,否则我说它们也应该保留在生产版本中。

但是,我认为这也需要您稍微优雅地处理断言失败。例如,它们应该产生一个通用类型的对话框,带有(自动)向开发人员报告问题的选项,而不仅仅是退出或使程序崩溃。此外,您应该注意不要将断言用于您确实允许但可能不喜欢或认为不需要的条件。这些条件应该由代码的其他部分处理。

于 2008-08-20T11:08:00.047 回答
6

In my C++ I define REQUIRE(x) which is like assert(x) except that it throws an exception if the assertion fails in a release build.

Since a failed assertion indicates a bug, it should be treated seriously even in a Release build. When my code's performance matters, I will often use REQUIRE() for higher-level code and assert() for lower-level code that must run fast. I also use REQUIRE instead of assert if the failure condition may be caused by data passed in from code written by a third party, or by file corruption (optimally I would design the code specifically to be well behaved in case of file corruption, but we don't always have time to do that.)

They say you shouldn't show those assert messages to end-users because they won't understand them. So? End users may send you an email with a screen shot or some text of the error message, which helps you debug. If the user simply says "it crashed", you have no ability to fix it. It would be better to send the assertion-failure messages to yourself automatically, but that only works if (1) the software runs on a server you control/monitor or (2) the user has internet access and you can get their permission to send a bug report.

于 2008-11-25T17:09:59.210 回答
4

如果您想保留它们,请使用错误处理替换它们。没有什么比一个程序消失更糟糕的了。我认为将某些错误视为严重错误并没有错,但它们应该被引导到您的程序的一部分,该部分可以通过收集数据、记录数据并通知用户您的应用程序有一些不需要的情况来处理它们正在退出。

于 2008-08-20T12:28:05.453 回答
2

我们的数据库服务器软件包含生产和调试断言。调试断言就是这样——它们在生产代码中被删除。生产断言仅在 (a) 存在某些不应该存在的条件和 (b) 无法可靠地从该条件中恢复时发生。生产断言表明软件中遇到了错误或发生了某种数据损坏。

由于这是一个数据库系统,并且我们正在存储潜在的企业关键数据,因此我们会尽我们所能避免损坏的数据。如果存在可能导致我们存储错误数据的情况,我们会立即断言、回滚所有事务并停止服务器。

话虽如此,我们也尽量避免在性能关键的例程中进行生产断言。

于 2008-08-20T13:06:46.127 回答
2

如果它们像任何其他错误一样被处理,我认为它没有问题。请记住,尽管 C 中的失败断言与其他语言一样,只会退出程序,这对于生产系统通常是不够的。

有一些例外 - 例如,PHP 允许您为断言失败创建自定义处理程序,以便您可以显示自定义错误、进行详细日志记录等,而不仅仅是退出。

于 2008-08-20T11:22:54.143 回答
2

假设一段代码正在生产中,并且它遇到了通常会触发的断言。断言发现了一个错误!除非它没有,因为断言已关闭。

那么现在会发生什么?程序要么(1)在远离问题根源的地方以无信息的方式崩溃,要么(2)愉快地运行到完成,可能会给出错误的结果。

这两种情况都不受欢迎。即使在生产中也让断言保持活动状态。

于 2019-12-30T07:26:50.407 回答
1

我将断言视为内联单元测试。对于开发时的快速测试很有用,但最终这些断言应该被重构出来,以便在单元测试中进行外部测试。

于 2012-10-27T11:39:30.160 回答
1

我发现最好处理范围内的所有错误,并使用断言来假设我们断言为真。

即,如果您的程序正在打开/读取/关闭文件,则无法打开该文件在范围内——这是一种真正的可能性,换句话说,这是一种疏忽忽略的可能性。所以,它应该有与之相关的错误检查代码。

但是,假设您的 fopen() 记录为始终返回有效的打开文件句柄。您打开文件,并将其传递给您的 readfile() 函数。

该 readfile 函数,在这种情况下,可能根据其设计规范,几乎可以假设它将获得一个有效的文件 ptr。因此,在这样一个简单的程序中为否定情况添加错误处理代码将是一种浪费。然而,在继续执行之前,它至少应该以某种方式记录假设——以某种方式确保——确实是这种情况。它实际上不应该假设它总是有效的,以防它被错误地调用,或者它被复制/粘贴到其他程序中,例如。

所以, readfile() { assert(fptr != NULL); .. } 在这种情况下是合适的,而全面的错误处理则不是(忽略实际读取文件无论如何都需要一些错误处理系统的事实)。

是的,这些断言应该保留在生产代码中,除非绝对有必要禁用它们。即使这样,您也应该只在性能关键部分禁用它们。

于 2013-12-25T00:44:11.187 回答
0

我很少将断言用于编译时类型检查的其他任何事情。我会使用异常而不是断言,因为大多数语言都是为处理它们而构建的。

我举个例子

file = create-some-file();
_throwExceptionIf( file.exists() == false, "FILE DOES NOT EXIST");

反对

file = create-some-file();
ASSERT(file.exists());

应用程序将如何处理断言?try catch我更喜欢处理致命错误的旧方法。

于 2008-08-20T11:19:04.810 回答
0

大多数时候,当我在 java 中使用断言(assert 关键字)时,我会在之后自动添加一些生产代码。根据案例,它可以是日志消息、异常......或者什么都没有。

据我说,您的所有断言在开发版本中都至关重要,而不是在生产版本中。其中一些必须保留,另一些必须丢弃。

于 2008-08-20T11:19:05.730 回答
0

断言不是错误,不应作为错误处理。当一个断言被抛出时,这意味着你的代码或者调用你的代码的代码中存在错误。

有几点可以避免在生产代码中启用断言:1. 您不希望最终用户看到类似“ASSERTION failed MyPrivateClass.cpp line 147 之类的消息。最终用户不是您的 QA 工程师。2. ASSERTION 可能影响绩效

然而,留下断言有一个强有力的理由:断言可能会影响性能和时间,遗憾的是,这有时很重要(尤其是在嵌入式系统中)。

我倾向于投票支持在生产代码中保留断言,但要确保这些断言打印输出不会暴露给最终用户。

~伊兹克

于 2013-09-22T05:23:28.903 回答
-8

断言是错误的,纯粹而简单,因此应该像对待一个断言一样处理。

由于应在发布模式下处理错误,因此您实际上并不需要断言。

我看到断言的主要好处是有条件的中断 - 它们比通过 VC 的窗口钻取来设置需要 1 行代码的东西要容易得多。

于 2008-08-20T11:17:52.893 回答