关于断言:断言什么时候应该保留在生产代码中?

关于断言:断言什么时候应该保留在生产代码中?

When should assertions stay in production code?

在comp.lang.c ++上进行了讨论。主持关于断言是否应该保存在生产代码中,而C语言中的断言只存在于调试版本中。

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

通过断言,我的意思是:

  • 运行时检查,用于测试条件,如果为false,则会显示软件中的错误。
  • 程序停止的机制(可能是在最小的清理工作之后)。

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

我自己的观点是,如果你是程序员,但不拥有数据(大多数商业桌面应用程序就是这种情况),你应该保持它们,因为失败的断言显示了一个错误,你不应该去有bug,有可能破坏用户的数据。这会强制您在发货前进行强力测试,并使错误更加明显,从而更容易发现并修复。

你有什么看法/经验?

干杯,

卡尔

在此查看相关问题

回应和更新

嘿格雷厄姆,

An assertion is error, pure and simple and therefore should be handled like one.
Since an error should be handled in release mode then you don't really need assertions.

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

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

稍微偏离主题,但讨论中的一个重点。

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

卡尔

托马斯,

是的,我有代码完成,并且必须说我非常不同意该特定建议。

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

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

卡尔


断言是不会过时的评论。他们记录了哪些理论状态是有意的,哪些状态不应该出现。如果代码被更改,因此状态允许更改,很快就会通知开发人员,并且需要更新断言。


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

Normally, you don't want users to see assertion messages in production code; assertions are primarily for use during development and maintenance. Assertions are normally compiled into the code at development time and compiled out of the code for production.

但是,在同一部分的后面,给出了这样的建议:

For highly robust code, assert and then handle the error anyway.

我认为只要性能不是问题,请保留断言,但不要显示消息,让它写入日志文件。我认为这个建议也在Code Complete中,但我现在还没找到。


保留断言在生产代码中打开,除非您已经测量程序在关闭时运行得更快。

if it's not worth measuring to prove it's more efficient, then it's not worth sacrificing clarity for a performance gamble." - Steve McConnell 1993

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


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

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

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


断言永远不应该停留在生产代码中。如果某个特定的断言似乎在生产代码中有用,那么它不应该是一个断言;它应该是运行时错误检查,即像这样编码的东西:if( condition != expected ) throw exception

"断言"一词的意思是"只能在现场进行的开发时间检查"。

如果你开始认为断言可能会进入现场,那么你将不可避免地开始制造其他危险的想法,比如想知道任何给定的断言是否真的值得做。没有不值得做出的断言。你永远不应该问自己"我应该断言这个吗?"你应该只问自己"有什么我忘记断言的吗?"


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

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


在我的C ++中,我定义了REQUIRE(x),它类似于assert(x),只是如果断言在发布版本中失败,它会抛出异常。

由于失败的断言表明存在错误,因此即使在发布版本中也应该认真对待它。当我的代码性能很重要时,我经常会将REQUIRE()用于更高级别的代码,并将assert()用于必须快速运行的低级代码。如果失败条件可能是由第三方编写的代码传入的数据引起的,或者文件损坏,我也使用REQUIRE而不是断言(最好我会设计代码专门在文件损坏的情况下表现良好,但我们并不总是有时间这样做。)

他们说你不应该向最终用户展示这些断言消息,因为他们不会理解它们。所以?最终用户可能会向您发送一封带有屏幕截图的电子邮件或错误消息的某些文本,这有助于您进行调试。如果用户只是说"它崩溃了",那么你修复它的能力就会降低。最好通过互联网自动发送断言失败消息给自己,但这只有在用户可以访问互联网并且您可以获得他们的许可时才有效。


如果你想让他们用错误处理替换它们。没有什么比刚刚消失的程序更糟糕的了。我认为将某些错误视为严重错误并没有错,但是应该将它们定向到您的程序的一部分,该部分可以通过收集数据,记录它并通知用户您的应用程序有一些不需要的条件来处理它们。正在退出。


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

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


我们的数据库服务器软件包含生产和调试断言。调试断言就是这样 - 它们在生产代码中被删除。只有在(a)某些条件不存在且(b)不可能从这种情况下可靠地恢复时,才会发生生产断言。生产断言表明已遇到软件中的错误或发生了某种数据损坏。

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

话虽如此,我们还试图避免性能关键例程中的生产断言。


我认为断言是在线单元测试。在开发过程中用于快速测试很有用,但最终应该重构这些断言以在单元测试中进行外部测试。


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

也就是说,如果你的程序正在打开/读取/关闭文件,那么无法打开文件就在范围内 - 这是一种真正的可能性,换句话说,忽略了疏忽。因此,应该有与之关联的错误检查代码。

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

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

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

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


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

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

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

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

?Yitzik


大多数时候,当我在java中使用assertion(assert关键字)时,我会自动添加一些生产代码。根据这种情况,它可以是记录消息,例外......或者什么也不是。

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


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

我举个例子

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

反对

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

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


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

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

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


推荐阅读