65

我从来没有断言的想法——你为什么要使用它们?

我的意思是,假设我是一名方程式赛车手,所有的断言都是安全带、头盔等。

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

我永远不会删除它们。我认为大多数声称删除类似于断言的东西的人从未分析过他们的代码,或者断言完全被取代了。我从未见过任何真正的性能优势,尤其是关于 80 / 20 规则。

那么,我是否以某种方式错过了重点,或者有人可以告诉我,为什么我应该使用断言?顺便说一句,我正在使用单元测试。

4

19 回答 19

72

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

这正是重点。

断言可以帮助您捕获错误。而且由于它们在发布版本中被删除,我们可以负担得起大量放入而不用担心性能。如果你不在那里对任何失败的断言采取实际行动,它们就会变得毫无价值,所以我们不妨删除它们。

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

断言基本上归结为“为什么要费心去捕捉你无法处理的错误?”

在开发过程中必须捕获一些错误。如果他们滑过测试并进入客户使用的发布版本,程序就会被破坏,再多的运行时错误检查也无法修复它。

我从来没有断言的想法——你为什么要使用它们?

我的意思是,假设我是一名方程式赛车手,所有的断言都是安全带、头盔等。

是的,这是什么时候不使用断言的一个很好的例子。这些是在运行时实际上可能出错的事情,需要检查。你的一级方程式车手可能会忘记一些安全预防措施,如果他忘记了,我们希望在任何人受伤之前停止整个事情。

但是检查引擎是否已安装呢?我们需要在比赛期间检查吗?

当然不是。如果我们在没有引擎的情况下参加比赛,我们就完蛋了,即使我们发现了错误,也为时已晚。

相反,这是一个必须在开发过程中捕获或根本不捕获的错误。如果设计人员忘记在他们的汽车中安装发动机,他们需要在开发过程中检测到这个错误。这是一个断言。开发的时候跟开发者有关系,但是后面的错误一定是不存在的,如果存在,我们也无能为力。

这基本上就是区别。一个例外是通过处理可以处理的错误来帮助用户。

断言可以帮助您,通过提醒您首先决不能发生的错误,必须在产品发货之前修复这些错误。不依赖于用户输入的错误,而是依赖于你的代码做它应该做的事情。

四的平方根决不能计算为三。错误是根本不可能的。如果确实发生了,那么您的程序逻辑就被破坏了。我们围绕它进行多少错误处理并不重要,它是必须在开发过程中捕获的东西,或者根本不捕获。如果我们使用异常处理来检查这个错误并处理它,异常会做什么?告诉用户“程序从根本上坏了。永远不要使用它”?

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

但是可能会出现一些错误,例如无法打开文件进行读取。即使它发生可能是一件坏事,但我们必须接受它可能发生。因此,如果确实如此,我们需要处理它。

断言用于捕获不可能发生的错误。

于 2009-08-07T17:02:45.603 回答
44

Andrew Koenig 曾经对运输代码中异常和断言的使用进行过很好的哲学讨论。最后,当程序处于无法修复的损坏状态时,您要防止做疯狂的事情

因此,我相信,当程序发现其内部状态存在无可辩驳的错误时,最好立即终止,而不是让调用者有机会假装没有任何问题。

如果您愿意,我认为应该为捕获异常后可以做一些明智的事情的情况保留异常。当你发现一个你认为不可能的情况时,很难说以后会发生什么。

于 2009-07-04T03:21:47.327 回答
21

来自 Code Complete 2:“对您期望发生的情况使用错误处理;对不应该发生的情况使用断言。”

一个常见的例子是在除法之前检查分母中的零。

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

单元测试不能替代断言。

于 2009-07-04T03:20:00.077 回答
8

因为它们使调试更容易。

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

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

于 2009-07-04T04:29:15.043 回答
7

这是一个有争议的话题。许多人,比如我自己,实际上更喜欢将它们留在生产代码中。如果您的程序无论如何都要进入杂草,您最好在其中有断言,这样您的客户至少可以给您行号和文件名(或您配置断言执行的任何信息或操作)。如果您忽略了断言,那么客户可以向您报告的所有内容都是“它崩溃了”。

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

于 2009-07-04T04:29:36.353 回答
5

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

断言是为了开发,以确保你不会搞砸。

于 2009-07-04T03:18:35.803 回答
2

从您的帖子中,听起来您并不反对使用断言的想法,而是在调试中使用断言而不是在生产中激活它们的想法。

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

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

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

于 2009-07-04T03:30:13.097 回答
2

我认为在重构时断言是无价的。如果你想用 algorithm2() 替换 alogrihm1(),你可以同时拥有它们并断言结果相等。然后,您可以逐步淘汰 algorithm1()

断言也适用于您可能快速做出的一些更改,但在系统状态的上下文中不太确定。为您所做的假设设置断言,将很快帮助您指出问题(如果有)。

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

于 2009-07-04T05:36:32.657 回答
1

在我参与的许多项目中,断言是使用在调试和发布中具有不同行为的自定义宏完成的。

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

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

于 2009-07-04T09:08:01.803 回答
1

在 Code complete 中有一个部分说类似的内容。每次你写一个 if 没有任何其他内容时,你可能会遗漏一些东西。

就像这段代码

int i = 1
i = i++ 

普通程序员永远不会考虑如果 i 在后面的代码中为负数会发生什么。您的代码产生溢出的可能性很小,并且像 java 这样的语言将从 max int 跳转到 min int 并且您会得到一个非常大的负数。这就是你通常所说的所有情况。呃,这永远不会发生。但是,如果发生这种情况,您的程序会做什么?因此,如果您知道有些事情您认为永远不会发生,请对其进行测试或反对,并在永远不会发生的 else 子句中放置一个断言 false,而不是不要编写 else 语句。通过这种方式,您的程序应该在您不确定它在做什么的那一刻完全崩溃。在生产代码中,应该有一些不同于通知用户、维护者然后退出之类的崩溃的东西。

断言的另一个用途是契约驱动设计。你用你的接口指定一个契约,根据你在程序中的位置你断言你的输入,但更多的导入你断言你的输出两个。

我同意你的观点,生产代码中禁用的断言使断言变得毫无用处。在我看来,在 java vm 的情况下关闭默认断言是一种危险。

于 2009-07-04T03:34:29.423 回答
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) throw exception;” 5 次,您只需调用一次,因此您已经知道在输入 B()、C() 和 D() 时它不是 NULL。否则,断言将在开发阶段退出,因为您“更改了代码!” 不是因为“用户的输入”。

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

于 2015-07-13T00:36:06.863 回答
1

只是不要在你不想要的时候使用断言。不使用它并没有错。

断言仅在调试模式下的测试用例实际命中它时才有用。很多时候它根本没有命中,这取决于你的测试用例的质量。当您尝试验证假设时会使用断言,因此您得到了您所要求的,在测试期间您几乎不会破坏您的假设。这就是为什么你首先假设它不是。然而,有无数的“预期不可能”的情况在调试期间确实没有达到您的断言,但不知何故仍然在禁用断言的生产中受到影响。如果您在调试期间依赖断言,那么您很可能最终会在生产中发生一些意想不到的事情,即使您的断言也没有捕捉到。

您的程序应该以战略方式设计,以便即使发生意外事件或您的测试用例没有涵盖,仍然以定义的方式处理问题,或产生有意义的诊断信息。

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

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

于 2020-05-06T18:28:51.600 回答
0

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

更重要的是,我编写了代码,其中断言对代码的大小造成了数十个百分点的差异。当内存占用成为问题时,发布代码中的断言可能是不可接受的浪费。

于 2009-07-04T13:18:19.567 回答
0

我主要将它用于开发期间的测试。例如,这里是我的 utf-8 库的冒烟测试每当我对库代码进行更改时,我都会运行测试,如果引入了错误,则会触发断言。当然,我本可以使用成熟的单元测试框架,但就我的目的而言,断言就可以了。

于 2009-07-04T15:58:25.040 回答
0

忍不住引用“不可或缺的卡尔文和霍布斯”p。180:

在像这样下陡峭的山坡之前,应该始终对他的雪橇进行安全检查。
正确的。
安全带 ?没有任何。
信号?没有任何。
刹车?没有任何。
操舵 ?没有任何。
嘻嘻嘻

于 2009-08-07T13:28:19.200 回答
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

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

于 2013-09-27T18:53:25.507 回答
0

断言应该用于以程序员使用 API/函数/类/其他的方式预测错误。这些错误需要在调试时快速修复。

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

于 2015-08-05T07:13:34.477 回答
0

有关此问题的其他答案

assert()宏用于测试程序中不应出现的条件或假设。例如,数组索引应始终 > 0。另一个假设可以是 2+2 == 3+1。

所以使用 assert() 我们可以测试这样的假设,只要它们评估为真,我们的程序就可以正常运行。当它们为假时,程序终止。

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

于 2020-05-31T20:46:51.150 回答
-4

我从不在我的代码中使用断言,我非常讨厌它们。我理解错误检查和处理的必要性,但为了防止您自己崩溃程序而导致程序崩溃的错误....坦率地说,我没有看到优势。

还要在你的代码中留下一个断言,墨菲定律将确保它最终会使你的程序崩溃。我更喜欢在处理数据之前检查数据并抛出适当的异常,以便像任何其他异常状态或操作一样处理它。根据我的经验,从用户的角度来看,从长远来看,具有确定性行为的软件会更加稳定。

作为一名软件工程师,当你的程序断言失败时,你会知道该怎么做,大多数用户会害怕他们破坏了某些东西,最终不会使用你的软件。所以除非你正在为工程师开发(这很有可能),即使那样......

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


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

我并没有说我没有检查异常、奇怪的值或只是简单的错误状态,我只是说我没有使用断言,因为从最终用户的角度来看,它们倾向于关闭系统的可怕方式。大多数现代语言还提供了另一种类型安全的方法来处理这些情况,那么当一个非常好的异常可以解决问题时,我会使用断言,而且也非常好。

在我看到的大多数生产代码中,我注意到主要有两种方法来处理这个问题,在整个代码中添加断言,然后在生产中留下很多。这有一种只向用户关闭应用程序的令人愤怒的趋势,我还没有看到一个断言优雅地使系统失败......它只是失败了......繁荣......消失了......最终用户只是说“WTF是地址 0x330291ff 处的断言失败错误!!!”

另一种方式,如果你问我,更糟糕的是,只是抓住扔掉的东西并将它藏在地毯下(见过这些可怕的空牙套尝试抓住!!)

无论哪种方式都无法获得良好的稳定系统。当然你可以在你的测试代码中使用断言并在你的生产代码中删除它们......但是你为什么要删除你的安全网,因为它是生产的。我会很惊讶所有这些检查都会削弱您系统的性能。

为自己构建一个良好的异常处理方案,并且.. 上帝... 把它留在那里,如果总是在上下文中适当地完成,您将获得更多有意义的系统信息,而不是因为缺少某些东西而导致一些深度库抛出断言。

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

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

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

于 2009-07-04T03:44:32.147 回答