【问题标题】:Why should I use asserts?为什么要使用断言?
【发布时间】:2010-11-08 01:26:34
【问题描述】:

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

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

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

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

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

【问题讨论】:

  • 你从来没有这个想法,但你永远不会删除它们?似乎您同时同意和不同意。
  • 断言仅供不想编写错误处理代码的懒惰程序员使用。如果您知道可能出现错误,请处理它。如果不可能,那么就没有理由断言。
  • 贾斯汀,这不是断言的用途。断言是为了在某些绝对不应该发生的事情发生时(因为它表明编程错误)发生了。它们不适用于处理日常的、可预测的错误情况。

标签: c++ performance assertions


【解决方案1】:

关于这个问题的其他答案

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

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

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

【讨论】:

    【解决方案2】:

    当你不想要的时候不要使用断言。不使用也没有错。

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

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

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

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

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

        【解决方案4】:

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

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

        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,这样所有的断言都不会被编译,所有“不必要的检查”都会被删除可执行文件。

        【讨论】:

          【解决方案5】:

          Andrew Koenig 曾经有一个good philosophical discussion over the usage of exceptions and assertions in shipping code。最后,当程序处于无法修复的损坏状态时,您要防止做疯狂的事情

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

          如果你愿意,我认为例外 应保留在 有可能做某事 捕获异常后明智。 当你发现你的情况 认为是不可能的,很难 说很多可能发生的事情 之后。

          【讨论】:

          【解决方案6】:

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

          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
          

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

          【讨论】:

            【解决方案7】:

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

            这正是重点。

            断言可以帮助您发现错误。而且因为它们在发布版本中被删除,我们可以在不担心性能的情况下放入大量它们。如果您不在现场对任何失败的断言采取实际行动,它们就会变得毫无价值,所以我们不妨删除它们。

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

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

            在开发过程中必须发现一些错误。如果他们滑过测试并进入客户使用的发布版本,则程序只是损坏了,再多的运行时错误检查也无法修复它。

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

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

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

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

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

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

            这基本上是不同的。通过处理可以处理的错误来帮助用户。

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

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

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

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

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

            【讨论】:

            • 我认为这里的很多帖子都是真实的,但这一篇真的很接近我的哲学。断言在开发时帮助我,同时构建我的应用程序。一旦发生我在开发过程中没有预料到的错误,我更喜欢它停止并生成故障转储.. 比赛:-)。 +1 !
            • 这可能是一级方程式车队在开发新引擎时所做的(例如)。他们在带有许多传感器和其他东西的电路上测试他们的引擎。当维修区中的人发现错误时,他们会要求驾驶员在维修区停下来进行一些修正,等等等等。等到他们达到目标的时候:一台准备好投入生产的发动机。 :-)
            • “如果我们在没有引擎的情况下参加比赛,我们就完蛋了”但是“我们在没有刹车的情况下参加比赛”怎么样?赛车仍然可以正常启动,但如果在比赛开始时没有人愿意检查它,那么车手会在比赛中途被搞砸。在这种情况下,如果我们检测到错误,现在采取任何措施还为时——我们可以中止这件事,而不会危及驾驶员的生命。严格来说——如果有东西绕过了断言(在生产中),程序可以做任何事情,包括删除敏感文件。
            • @kizzx2:“比赛前”类似于在测试/编译期间,这是断言很方便的地方。 在比赛中对应于在运行时。所以我真的不明白这与我所说的有什么矛盾。赛车不得在没有刹车的情况下参加比赛。此类错误必须在汽车启动之前 被捕获。就像软件中的某些错误必须在程序启动之前捕获:在编译/测试期间,例如通过断言。如果您确实在没有刹车的情况下参加比赛,那么发出错误信号并没有真正的帮助。安全地处理问题为时已晚。
            • 我很抱歉——类比到此为止。我认为我的观点可以总结为: 1)我们永远不知道什么是不可能的——如果我们知道,没有软件会崩溃。我们认为的不可能(断言)也可能在生产中发生。 2) 当一个失败的断言没有停止时——程序进入了未定义的行为。我发现让程序在没有任何类型的错误处理的情况下继续运行是不负责任的。 -------- 当然这不是绝对的真理。一些程序(关键任务)需要更严格的安全检查,而对于一些程序,“表演必须继续”(例如视频游戏)
            【解决方案8】:

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

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

            【讨论】:

              【解决方案9】:

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

              【讨论】:

                【解决方案10】:

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

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

                【讨论】:

                  【解决方案11】:

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

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

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

                  【讨论】:

                    【解决方案12】:

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

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

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

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


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

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

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

                    另一种方式,如果你问我,更糟糕的是,只是抓住扔出的任何东西并将其隐藏在地毯下(见过这些可怕的空括号尝试捕捉!!)

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

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

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

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

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

                    【讨论】:

                    • 他们正是为了这一刻。墨菲定律接受你的程序的那一刻,你不知道它在做什么。如果您不喜欢它们,您可以将它们取出并在该位置添加错误处理,但如果您的错误处理不是显示意外错误......发生了请联系支持人员,这不是断言的地方并且这个地方的错误处理是正确的。
                    • 如果你使用断言,那么至少你知道你的程序在哪里失败,以及是什么内部不一致导致它失败。如果您选择忽略会导致断言失败的条件,那么您的程序可能会在以后的任何时间崩溃,甚至会毫无怨言地给出错误的结果。这两种情况对用户来说都不是更好,但对于尝试调试的程序员(以及用户和调试器之间技术支持链中的每个人)来说,它们都更糟糕。
                    • 是的,总有一天他们会在生产代码中开火。这是一件GOOD的事情,假设你的程序提供了一些紧急保存的方法(假设它首先有某种文档。)当你遇到不可能的情况时,你应该NOT i> 尝试继续。保存,不覆盖,然后退出。
                    • @Janusz - 我个人认为弹出窗口“发生错误”或断言几乎是等价的,也就是说,如果您的软件堆栈变得非常大并且最终降级非常严重,则几乎无用- 用户体验。
                    • @unknown... 我可以对异常做同样的事情,我看不出断言在哪里可以做的比异常已经可以做的更多。如果我的图书馆的用户认为这种特殊情况还不够糟糕并决定继续,那么错误是他的,而不是我的。还有……他很可能是对的,对你,图书馆的创建者来说,这可能是一个无法挽回的错误,但对他来说,他可能有一个他可以使用的应急计划。断言不会给他这种灵活性。
                    【解决方案13】:

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

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

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

                    【讨论】:

                      【解决方案14】:

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

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

                      【讨论】:

                        【解决方案15】:

                        因为它们使调试更容易。

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

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

                        【讨论】:

                          【解决方案16】:

                          在代码完成中是这样的部分。每次你写一个 if 没有任何其他内容时,你可能会遗漏一些东西。

                          就是这样的代码

                          int i = 1
                          i = i++ 
                          

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

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

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

                          【讨论】:

                            【解决方案17】:

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

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

                            【讨论】:

                              【解决方案18】:

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

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

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

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

                              【讨论】:

                              • 断言减慢代码。我们在电子游戏中使用了它们,并且在大部分测试期间它们都在那里。他们必须在之后被剥离。您不会在 GameCube 上断言。
                              【解决方案19】:

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

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

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

                              单元测试不能替代断言。

                              【讨论】:

                              • “对不应该发生的情况使用断言。”我一直认为这听起来很像异常处理。
                              • 异常处理是针对你知道它们为什么会发生并且你可以在程序内部处理的事情。如果断言失败,那么你不知道该怎么做,程序应该退出。
                              • @George IV:不同之处在于,断言适用于您认为如果您的程序正确,则不可能发生的事情。例外是“不应该”发生的事情,因为如果它们发生了,您的例程/程序将无法工作。因此,库有时会为应用程序断言的事情抛出异常,因为库不知道问题是否从程序的 POV 中无法想象(例如内存不足:在某些环境中,您可以编写代码来避免它,在其他你不能)。
                              • 单元测试不能代替断言。你能添加更多来区分它们吗?
                              猜你喜欢
                              • 1970-01-01
                              • 2018-05-10
                              • 1970-01-01
                              • 2012-10-15
                              • 1970-01-01
                              • 1970-01-01
                              • 2012-09-30
                              • 1970-01-01
                              • 2021-01-08
                              相关资源
                              最近更新 更多