我为什么要使用断言?

我从来不知道什么是资产——你为什么要使用它们?

我是说,假设我是一个配方赛车手所有的资产都是安全带,头盔之类的东西。

测试(在调试中)都没问题,但是现在我们想要进行比赛(发布) ! 我们是否应该放弃所有的安全性,因为在测试时没有任何问题?

我永远都不会移除它们。我认为大多数声称删除类似于断言的内容的家伙从来没有对他们的代码或断言进行过侧写,这些内容被完全取代了。 我从来没有看到任何真正的性能优势,特别是关于80/20规则。

那么,我是不是忽略了这一点,或者有谁能告诉我,为什么我应该使用断言? 顺便说一下,我使用的是单元测试。

26120 次浏览

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

断言是用于开发的,以确保您不会搞砸。

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

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

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

单元测试不能替代断言。

安德鲁 · 凯尼格过去有一个 对异常和断言在船运代码中的使用进行了很好的哲学讨论,最后是 当程序处于无法修复的崩溃状态时,你会防止做出疯狂的事情

因此,我相信 程序发现了一些东西 无可辩驳的错误,其内部 最好在 一次,而不是给它的呼叫者 有机会假装 没什么。

如果你愿意,我认为例外 应预留作 做某事是可能的 在捕捉到异常之后是明智的。 当你发现 认为是不可能的,这是很难的 说了很多可能发生的事情 之后。

从你的文章来看,你似乎并不反对使用断言的想法,而是反对在调试中使用断言而在生产环境中不使用断言的想法。

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

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

我同意您的观点,即断言不应该仅仅在生产代码中保持沉默——许多错误在测试环境中不会暴露,知道断言何时在生产环境中失败非常重要。

在“代码完成”中,有一个部分是这样说的。每次你写一个如果没有任何其他你可能会错过一些东西。

就像这个密码

int i = 1
i = i++

普通的程序员永远不会考虑如果 i 在以后的代码中是负数会发生什么。 你的代码产生溢出的可能性很小,像 java 这样的语言会从 max int 跳到 min int,你会得到一个非常大的负数。这些都是你经常说的案子。这永远不会发生。但是如果发生这种情况,您的程序会做什么呢?因此,如果您知道有一些您认为永远不会发生的事情,那么就对它进行测试,并在 else 子句中放入一个断言 false,而不是不编写 else 语句。 这样,当你不再确定程序在做什么的时候,你的程序就会完全崩溃。在生产代码中,应该有一些不同于崩溃的东西,比如通知用户、维护人员然后退出。

断言的另一个用途是契约驱动设计。您使用接口指定一个契约,并根据您在程序中的位置断言您的输入,但是更重要的是断言您的输出2。

我同意您的观点,生产代码中禁用的断言会使断言变得相当无用。在我看来,java vm 的默认断言关闭是一种危险。

我从不在我的代码中使用断言,我非常讨厌它们。我理解检查和处理错误的必要性,但是为了防止一个错误,这个错误会使你的程序自己崩溃。坦率地说,我不认为这有什么好处。

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

作为一个软件工程师,当你的程序断言时,你会知道该怎么做,大多数用户只是害怕他们破坏了什么,最终不会使用你的软件。因此,除非你正在为工程师开发(这是很有可能的) ,即使那样..。

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


好吧... 从所有的评论和火我得到这里,我认为我需要进一步解释我的观点,因为它显然是不被理解的。

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

在我看到的大多数产品代码中,我注意到主要有两种方法来处理这个问题,在代码中添加断言,然后在产品中留下一大堆。这有一种令人恼火的倾向,就是把应用程序关闭给用户,我还没有看到一个断言能够优雅地使一个系统失败... ..。它只是失败了它... 砰... 消失了... 最终用户只是说“卧槽是一个断言失败的错误地址0x330291ff! ! !”

另一种方法,如果你问我的话,甚至是最糟糕的方法,就是抓住扔过来的任何东西,然后把它藏在地毯下面(从来没有见过这些可怕的试图用空的支架接住的方法!)

这两种方法都不能得到一个好的稳定的系统。当然,你可以在测试代码中使用断言,并在生产代码中全部删除它们... ... 但是,你为什么要删除安全网呢? 因为这就是生产。我很惊讶所有这些支票会削弱你们系统的性能。

Build yourself a good exception handling scheme and .. by god... LEAVE IT THERE, you will get much more meaningful information our of your system and if done appropriately always in context than having some deep library throwing asserts because something is missing.

尤其是在创建图书馆的时候... ... 认为你,图书馆的创造者,可以决定什么时候关闭整个系统,因为抛给你的数据出了问题,这是非常自私和狭隘的。让你的图书馆的用户决定什么是足够重要的,它应该保证紧急故障。

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

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

因为它们使调试更容易。

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

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

这是个有争议的话题。许多人,比如我,实际上更喜欢在生产代码中保留它们。如果您的程序无论如何都会陷入困境,那么您最好在其中包含断言,这样您的客户至少可以给您行号和文件名(或者您配置断言要执行的任何信息或操作)。如果您没有提到断言,那么所有的客户都可以向您报告“它崩溃了”。

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

我认为断言在重构时是无价的。如果希望用算法2()替换 alogrihm1() ,可以同时使用它们,并在结果相等的情况下断言。然后可以逐步淘汰算法1()

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

是否应该在版本中使用宏或类似的方法来剥离断言,这是有争议的,但这是我到目前为止工作的项目中已经完成的工作。

在我工作过的许多项目中,断言都是用一个自定义宏来完成的,这个宏在调试和发布中有不同的行为。

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

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

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

更重要的是,我编写的代码中,断言对代码的大小产生了几十个百分点的影响。当内存占用是一个问题时,发布代码中的断言可能是不可接受的浪费。

我主要在开发期间用它进行测试。例如,这是我的 utf-8库的烟雾测试每当我修改库代码时,我都会运行测试,如果引入了 bug,断言就会触发。当然,我可以使用一个成熟的单元测试框架,但是就我的目的而言,断言就足够了。

无法抗拒引用“不可或缺的加尔文和霍布斯”第180页:

在下这样一个陡峭的山坡之前,一个人应该总是给他的雪橇一个安全检查。
对。
安全带? 没有。
信号? 没有。
刹车? 没有。
转向? 没有。

首先,性能差异 可以是巨大的。在一个项目中,我们的断言实际上导致了3倍的减速。但是他们帮助我们发现了一些非常讨厌的虫子。

这就是重点。

断言可以帮助您捕捉 bug。而且 因为在发布版本中已经被删除了,我们可以在不担心性能的情况下放入大量的 因为。如果你没有实际行动,任何失败的断言,他们变得毫无价值,所以我们不妨删除它们。

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

断言基本上可以归结为“为什么要费心捕获您无法处理的错误呢?”

在开发过程中必须捕获一些错误。如果他们错过了测试,进入到客户使用的版本构建中,程序就会崩溃,无论多少运行时错误检查都无法修复它。

我从来不知道什么是资产——你为什么要使用它们?

我是说,假设我是一个配方赛车手所有的资产都是安全带,头盔之类的东西。

是的,这是 没有使用断言的一个很好的例子。这些事情在运行时可能会出错,需要进行检查。你的一级方程式赛车手可能会忘记一些安全措施,如果他忘了,我们想在有人受伤之前停止比赛。

但是如何检查发动机是否安装? 我们是否需要检查 在比赛中

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

相反,这是一个必须在开发过程中捕获或根本不捕获的错误。如果设计人员忘记在他们的汽车上安装发动机,他们需要在开发过程中检测这个错误。那是断言。它与开发过程中的开发人员有关,但是之后,错误必须不存在,如果存在,我们就无能为力了。

这基本上就是区别所在。一个异常是为了帮助用户,通过处理可以处理的错误。

断言是用来帮助 的,通过提醒您从一开始就不可能发生的错误,这些错误必须在产品 可以发布之前修复。不依赖于用户输入,而是依赖于代码执行应该执行的操作的错误。

四的平方根必须等于 永远不会的三次方。这个错误是不可能的。如果它真的发生了,那么您的程序逻辑就被破坏了。不管我们围绕它进行了多少错误处理,它都是在开发过程中必须捕获的,或者根本不捕获。如果我们使用异常处理来检查这个错误并处理它,那么异常会做什么呢?告诉用户“这个程序已经彻底崩溃了。永远不要用它”?

一封来自开发者的电子邮件就可以做到这一点。为什么要在程序代码中构建它呢?这是一个绝对不能发生的问题的例子。如果是这样,我们必须回去修复程序。不可能有其他形式的错误处理。

但是有些错误,比如无法打开文件进行读取,是 有可能。即使它可能是一件坏事,如果它发生,我们必须接受它 可以发生。所以如果发生了,我们得处理好。

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

当您执行这样的操作时,应该使用断言

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

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

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

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

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 eption;”5次,只需要调用它一次,这样在输入 B ()、 C ()和 D ()时就知道它不是 NULL。否则,断言将在开发阶段退出,因为您“更改了代码!”而不是因为“用户的输入”。

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

断言应该用于预测程序员使用 API/function/class/whatever 时出现的错误。这些 bug 需要在调试时快速修复。

对于其他所有内容,抛出异常。

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

断言只有在调试模式下的测试用例实际命中时才有用。很多时候它根本不会命中,这取决于测试用例的质量。断言是用来验证一个假设的,所以你得到了你想要的,在测试中你很难打破你的假设。所以你一开始就假设是这样,不是吗。然而,有无数的“预期不可能”的情况下,真的没有打击你的断言在调试期间,但不知何故仍然打击在生产断言禁用。如果您在调试期间依赖断言,那么您最有可能在生产中发生一些意想不到的事情,甚至您的断言也没有捕捉到。

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

您可以使用断言来帮助进行故障排除,但是如果您首先想要防止问题的发生,那么断言就没有帮助。原因在于,如果假定生产环境中不会出现这种情况(在生产环境中禁用断言) ,那么就无法防止或处理利基问题。好的软件应该捕捉明显的错误(断言有帮助) ,以及利基错误(断言可能不会有帮助)。

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

这个问题的其他答案

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

因此,我们可以使用断言()来测试这些假设,只要它们 计算为 true 时,我们的程序正常运行。当它们为 false 时, 程序终止。

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