我为什么要使用断言?

时间:2009-07-04 03:13:29

标签: c++ performance assertions

我从未理解过断言 - 你为什么要使用它们?

我的意思是,让我说我是一个公式驱动程序,所有断言都是安全带,头盔等等。

测试(在调试中)都没关系,但现在我们想做赛车(发布)! 我们是否应该放弃所有安全性,因为测试时没有问题?

我永远不会删除它们。我认为大多数声称删除类似于断言的东西的人从不描述他们的代码或断言是绝对的替代。 我从未见过任何真正的性能优势,特别是关于80/20规则。

所以,我是否以某种方式忽略了这一点,或者任何人都可以告诉我,为什么我应该使用断言? 顺便说一句,我正在使用单元测试。

19 个答案:

答案 0 :(得分:41)

首先,的性能差异可能很大。在一个项目中,我们的断言确实造成了3倍的放缓。但他们帮我们发现了一些非常讨厌的错误。

这正是重点。

断言可以帮助您捕获错误。并且因为它们在发布版本中被删除,我们可以负担得起很多它们而不用担心性能。如果你不是真的对任何失败的断言采取行动,它们就会变得毫无价值,所以我们不妨将它们删除。

即使捕获错误并抛出异常也不是真正的解决方案。程序逻辑存在缺陷,即使我们处理异常,程序仍然会被破坏。

什么断言基本归结为“为什么要抓住你无法处理的错误?”

在开发过程中必须捕获一些错误。如果他们没有通过测试并进入客户使用的发布版本,程序就会被破坏,并且没有任何运行时错误检查会修复它。

  

我从未理解过断言 - 你为什么要使用它们?

     

我的意思是,让我说我是一个公式驱动程序,所有断言都是安全带,头盔等等。

是的,这是何时使用断言的一个很好的例子。这些是在运行时可能实际出错的事情,需要检查。你的公式驱动程序可能会忘记一些安全预防措施,如果他这样做,我们希望在任何人受伤之前停止整个事情。

但检查发动机是否已安装呢?我们是否需要在比赛期间检查

当然不是。如果我们在没有发动机的情况下进入比赛,我们就会被搞砸,即使我们发现错误,也不能对它做任何事情。

相反,这是一个必须在开发过程中捕获的错误,或者根本不会捕获。如果设计师忘记将发动机放入汽车,他们需要在开发过程中检测到这个错误。这是一个断言。它在开发过程中与开发人员相关,但之后,错误一定不存在,如果确实存在,我们无能为力。

这基本上就是差异。通过处理可以处理的错误,可以帮助用户。

断言是为了帮助,通过提醒您必须在产品 发货之前必须修复的错误,这些错误必须首先发生。错误不依赖于用户输入,而是依赖于代码执行应该执行的操作。

四的平方根必须从不评估为三。这个错误根本不可能。如果确实发生了,你的程序逻辑就会被破坏。无论我们对它进行多少错误处理都无关紧要,它必须在开发过程中被捕获或根本不被捕获。如果我们使用异常处理来检查这个错误并处理它,那么会有什么例外?告诉用户“程序从根本上被破坏了。不要使用它”?

来自开发者的电子邮件可以实现这一目标。为什么要把它构建到程序代码中呢?这是一个根本不可能发生的问题的例子。如果是这样,我们必须返回并修复该程序。不可能有其他形式的错误处理。

但有些错误,例如无法打开文件进行阅读,可能。尽管如果它发生可能是一件坏事,我们必须接受可以发生。所以我们需要处理它。

断言是为了捕捉不可能发生的错误。

答案 1 :(得分:35)

Andrew Koenig曾经有good philosophical discussion over the usage of exceptions and assertions in shipping code。最后,当程序处于不可挽回的破坏状态时,你正在防止做狂野的事情

  因此,我相信,当a   程序发现了一些东西   其内部无可辩驳的错误   状态,最好终止于   曾经,而不是给它的来电者   有机会假装   没有错。

     

如果你愿意,我认为例外   应该保留给的情况   哪有可能做某事   抓住异常后才明白。   当你发现自己的状况时   想法是不可能的,这很难   多说可能发生的事情   之后。

答案 2 :(得分:17)

从代码完成2:“对预期发生的条件使用错误处理;对不应发生的条件使用断言。”

一个常用的例子是在分割前检查分母中的零。

您应该将断言从生产代码中删除。他们在开发过程中会帮助您发现错误。

单元测试不能替代断言。

答案 3 :(得分:7)

因为它们使调试更容易。

调试的耗时部分是将问题从您首次注意到的症状追溯到代码中的错误。写得好的断言会使你注意到的症状更接近实际的代码问题。

一个非常简单的示例是一个错误,你索引超过数组的末尾并导致内存损坏,最终导致崩溃。从崩溃追溯到违规索引操作可能需要很长时间。但是,如果您在检查索引的索引操作旁边有一个断言,那么您的程序将在错误旁边失败,因此您将能够快速找到问题。

答案 4 :(得分:4)

这是一个有争议的话题。像我一样,很多人确实喜欢在生产代码中留下它们。如果您的程序无论如何都要进入杂草,那么您也可以在那里进行断言,这样您的客户至少可以为您提供行号和文件名(或者您配置断言要执行的任何信息或操作)。如果您退出断言,所有客户都可以向您报告“它已崩溃”。

这意味着您可能不应该在断言检查中执行昂贵的操作,或者至少要查看它们是否会导致性能问题。

答案 5 :(得分:3)

它们使您能够测试您的假设。例如,假设您想要计算速度。你可能想断言你的计算小于光速。

断言是为了发展,以确保你不会陷入困境。

答案 6 :(得分:2)

从你的帖子中,听起来你并不是不同意使用断言的想法,而是在调试中使用断言并且不让它们在生产中处于活动状态的想法。

这样做的原因是,在调试时,您可能希望进程灾难性地失败 - 即抛出异常并退出,以便可以解决错误。在生产中,这可能会影响整个系统,并且只有极少数情况下才会出现错误情况。因此,在生产中,您可能希望记录错误,但保持进程运行。

使用断言可以更改调试和发布之间的行为。

我同意你的观点,断言不应该只是在生产代码中沉默 - 许多错误不会在测试环境中暴露出来,并且知道断言何时在生产中失败是很重要的。

答案 7 :(得分:2)

在我认为重构时,断言是非常宝贵的。如果你想用算法2()替换alogrihm1(),你可以让它们两者并在结果相等的情况下断言。然后,您可以逐步淘汰algorithm1()

断言对于您可能快速进行的某些更改也很有用,但在系统状态的上下文中并不太确定。根据您所做的假设设置断言,可以快速帮助您指出问题,如果有的话。

断言是否应该通过在发布中使用宏来剥离断言是有争议的,但这就是我迄今为止所做的项目所做的事情。

答案 8 :(得分:1)

在我参与的许多项目中,使用自定义宏完成断言,该宏在Debug和Release中具有不同的行为。

在Debug中,如果条件为false,则在代码中的该点启动调试器。

在Release中,错误被写入日志文件,向用户发出警告,然后系统尝试保存未保存的数据。处于未知状态,这可能会失败,但值得尝试。

答案 9 :(得分:1)

在不需要时不要使用断言。不使用它没有错。

断言仅在调试模式下的测试用例真正命中时才有用。很多时候它根本不起作用,取决于测试用例的质量。当您尝试验证假设时会使用断言,因此您可以得到所需的信息,并且在测试过程中几乎不会破坏您的假设。这就是为什么您首先假设它不是它的原因。然而,有无数“预期不可能”的情况在调试期间确实没有达到您的断言,但是在某种程度上仍然使生产中的断言无效。如果您在调试过程中依赖断言,那么您很可能最终会在生产中发生一些意想不到的事情,甚至连您的断言都没有。

您的程序应以 strategy 的方式进行设计,这样即使发生意外情况或您的测试用例也无法涵盖,问题仍将以定义的方式处理,或者产生有意义的诊断信息

您可以使用断言来帮助进行故障排除,但是如果您想一开始就防止问题发生,这将无济于事。原因是,如果您假设利基问题不会在生产中发生(您在生产中禁用断言),就无法防止或处理它。好的软件应该捕获明显的错误(断言有帮助)以及适当位置的错误(断言可能无济于事)。

许多人会告诉您断言应该做什么的标准版本。什么断言对等等都有好处。但是,请根据您自己的经验来证明它是否真的有用。断言不是科学证明或黄金法则,它只是许多人的实践。您应该决定自己采用还是不采用。

答案 10 :(得分:1)

断言只应用于检查发布期间在发布期间不需要的条件。

这是一个如何在开发中使用断言的一个非常简单的例子。

A(char* p)
{
    if(p == NULL)
        throw exception;

    B(p);
    C(p);

}

B(char* p)
{
    assert(p != NULL);
    D(p);
    do stuff;
}

C(char* p)
{
    assert(p != NULL);
    D(p);
    do stuff;
}

D(char* p)
{
    assert(p != NULL);
    do stuff;
}

而不是调用" if(p == NULL)抛出异常;" 5次,你只需要调用一次,这样你就可以在进入B(),C()和D()时知道它不是NULL。否则,断言将在开发阶段退出,因为您已经更改了代码!"不是因为"用户的输入"。

这可以使代码在发布版本中运行得更快,因为您所要做的就是使用" -DNDEBUG"来调用gcc。因此,所有的断言都不会被编译,所有的"不必要的检查"将在可执行文件中删除。

答案 11 :(得分:1)

在代码中,完整版是一个类似的部分。每次你写一个if而没有任何其他你可能会遗漏一些东西。

就像这段代码

int i = 1
i = i++ 

如果我在以后的代码中为负面,那么普通程序员永远不会想到会发生什么。 你的代码有可能产生溢出,像java这样的语言会从max int跳到min int,你得到一个非常大的负数。这就是你通常说的所有情况。呃,这永远不会发生。但是如果它发生了你的程序在做什么?因此,如果你知道有些东西你认为永远不会对它进行测试或对它进行测试,并在else子句中放置一个永远不会发生的断言,而不是编写else语句。 这样你的程序就会在你不确定它正在做什么的那一刻完全崩溃。在生产代码中,应该有一些不同的东西,比如告知用户,维护者然后退出。

断言的另一个用途是契约驱动设计。您可以使用您的界面指定合同,并根据您在程序中的位置确定您的输入,但更多的导入是您断言输出两个。

我同意你的看法,在生产代码中禁用断言会使断言变得毫无用处。对于java vm,默认断言在我看来是危险的。

答案 12 :(得分:0)

我主要用于开发过程中的测试。例如,here is the smoke test of my utf-8 library每当我对库代码进行更改时,我都会运行测试,如果引入了错误,则会触发断言。当然,我本可以使用一个完整的单元测试框架,但就我的目的而言,断言很好。

答案 13 :(得分:0)

无法抗拒引用“不可或缺的凯文和霍布斯”p。 180:

在这样陡峭的山坡上行驶之前,应该给他的雪橇一个安全检查 右。
安全带 ?无。
信号?无。
刹车?无。
操舵 ?无。
WHEEEEEE

答案 14 :(得分:0)

我编写了代码,其中断言在启用时明显影响了性能。例如,通过图形代码检查紧密循环中使用的数学函数的前后条件(平方根函数将其结果平方并将其与输入进行比较等)。当然,它只有几个百分点,但我编写的代码需要这几点。

更重要的是,我编写了代码,其中断言与代码的大小相差几十个百分点。当内存占用问题成为问题时,发布代码中的断言可能是一种令人无法接受的奢侈。

答案 15 :(得分:0)

当你做这样的事情时应该使用断言

a = set()
a.add('hello')
assert 'hello' in a

a = 1;
assert a == 1; // if ram corruption happened and flipped the bit, this is the time to assert

至于例外情况,这是你以编程方式处理的事情:

while True:
  try:
    data = open('sample.file').read()
    break // successfully read
  except IOError:
    // disk read fail from time to time.. so retry
    pass

大多数情况下,当断言发生时重启应用程序会更安全,因为您不想处理不可能的情况。 但是当预期的情况发生时(预期的错误(大多数情况下来自黑盒客户端,网络呼叫等),应该使用异常。

答案 16 :(得分:0)

断言应该用于预测程序员使用API​​ /函数/类/任何东西的方式中的错误。这些错误需要在调试时快速修复。

对于其他一切,抛出异常。

答案 17 :(得分:0)

有关此问题的其他答案

assert()宏用于测试以下条件或假设: 不应在程序中发生。例如,数组索引应 总是>0。另一个假设可以是2 + 2 == 3 + 1。

因此,使用assert()我们可以测试这些假设,只要它们 评估为true,我们的程序将正常运行。当它们为假时, 程序终止。

更多 https://www.softwaretestinghelp.com/assert-in-cpp/

答案 18 :(得分:-5)

我从不在我的代码中使用断言,我充满激情地恨他们。我理解错误检查和处理的必要性,但是要防止因自己崩溃程序而导致程序崩溃的错误....坦率地说,我没有看到优势。

同样在你的代码中留下一个断言,墨菲定律将确保它最终会使你的程序崩溃。我更喜欢在使用它进行处理之前检查数据并抛出相应的异常,因此它的处理方式与任何其他异常状态或操作非常相似。根据我的经验,从长远来看,这使得软件从用户的角度来看具有确定性行为。

作为一名软件工程师,您将知道在程序断言时该怎么做,大多数用户只会害怕他们破坏了某些内容而最终不会使用您的软件。所以,除非你正在为工程师开发(这很可能),甚至那时......

从可用性角度来看,断言是可怕的,即使它们没有“应该”发生,我们都知道它最终会... ...


好的...从我到达的所有评论和火灾中我想我需要进一步解释我的观点,因为它显然不被理解。

我没有说我没有检查异常,奇怪的值或只是错误的状态,我只是说我没有使用断言,因为他们从最终用户的角度来关闭系统的可怕方式。此外,大多数现代语言提供了另一种类型安全的方法来处理这些情况,乳清然后我会使用断言,当一个非常好的例外可以做到这一点,而且非常好。

在我看到的大多数生产代码中,我注意到主要有两种方法可以解决这个问题,但是在整个代码中断言,然后在生产中留下了很多。这有一种令人愤怒的趋势,就是只关闭用户的应用程序,我还没有看到Assert正常地使系统失败......它只是失败了......繁荣......走了......终端用户只是说“WTF是地址0x330291ff的断言失败错误!!!“

另一种方式,如果你问我,最糟糕的就是抓住被抛出的东西并把它隐藏在地毯下面(曾经见过这些可怕的尝试 - 用空的支撑!!)

任何方式都将努力获得一个良好的稳定系统。当然你可以在你的beta代码中使用断言并将它们全部删除到你的生产代码中...但是为什么你会删除你的安全网,因为它是生产。我会非常惊讶所有这些检查都会削弱你的系统性能。

为自己建立一个良好的异常处理方案和...由上帝...离开那里,你将获得更有意义的信息我们的系统,如果适当地完成在上下文中,而不是有一些深层库抛出断言,因为有些东西是丢失。

在创建库时尤其如此...认为你,图书馆的创建者,可以决定何时关闭整个系统,因为投入到你的数据出了问题是非常自私和狭隘的思想。让你的图书馆用户决定什么是足够重要的,它应该保证紧急失败。

所以不...我不使用断言......我使用异常

是的......通常,生产失败的代码很少有我的名字。