【问题标题】:Is Java assert broken?Java 断言是否损坏?
【发布时间】:2011-02-15 02:18:43
【问题描述】:

在探索这些问题时,我最近在 Java 中发现了 assert 关键字。起初,我很兴奋。有用的东西我还不知道!一种更有效的方法来检查输入参数的有效性!耶学习!

但后来我仔细观察,我的热情并没有因为一个简单的事实而被“完全压制”而是“完全熄灭”:你可以关闭断言。*

这听起来像是一场噩梦。如果我断言如果输入listOfStuffnull,我不希望代码继续运行,那么我到底为什么要忽略该断言?听起来如果我正在调试一段生产代码并怀疑 listOfStuff 可能被错误地传递了 null 但没有看到任何日志文件证据表明该断言被触发,我不能相信 @987654327 @ 实际上得到了一个有效的值;我还必须考虑断言可能已完全关闭的可能性。

这假设我是调试代码的人。不熟悉断言的人可能会看到这一点并假设(相当合理地)如果断言消息没有出现在日志中,listOfStuff 就不是问题所在。如果您第一次遇到assert 是在野外,您是否会想到它可以完全关闭?毕竟,这不像是一个命令行选项可以让你禁用 try/catch 块。

所有这一切都让我想到了我的问题(这一个问题,不是咆哮的借口!我保证!):

我错过了什么?

是否存在一些细微差别使得 Java 的 assert 实现比我认为的更有用?在某些情况下,从命令行启用/禁用它的能力真的非常有价值吗?当我设想在生产代码中使用它来代替像if (listOfStuff == null) barf(); 这样的语句时,我是否以某种方式误解了它?

我只是觉得这里有些重要的东西我没有得到。

*好吧,从技术上讲,它们实际上是默认关闭的;你必须不遗余力地打开它们。但是,您仍然可以将它们完全淘汰。


编辑: 请求启蒙,收到启蒙。

assert 首先是一个调试工具这一概念对我来说意义重大。

我仍然不同意在生产环境中应该禁用对重要私有方法的输入检查的概念,因为开发人员认为错误的输入是不可能的。以我的经验,成熟的生产代码是一个疯狂的、庞大的东西,多年来由具有不同技能水平的人针对快速变化的不同理智程度的需求而开发。即使错误的输入确实是不可能的,六个月后的一段草率的维护编码也可以改变这种情况。 The link gustafc provided(谢谢!)以这个为例:

assert interval > 0 && interval <= 1000/MAX_REFRESH_RATE : interval;

在生产中禁用如此简单的检查让我觉得过于乐观了。但是,这是编码理念上的差异,而不是损坏的功能。

另外,我绝对可以看到这样的东西的价值:

assert reallyExpensiveSanityCheck(someObject) : someObject;

感谢所有花时间帮助我了解此功能的人;非常感谢。

【问题讨论】:

  • 这看起来很像咆哮,根本不像是一个问题。
  • 您曾经在 C 或 C++ 中使用过 ASSERT 吗?您是否曾希望弹指之间就能打开或关闭它们?
  • @Joachim:明白,但说真的,我是来寻求启迪的。这个特性在我看来确实是坏了——但这感觉就像说“我看不出我的代码有什么问题,因此编译器必须坏了!”,只是在更高的层次上。我真的觉得我错过了什么。如果你能建议我用一种方式来表达为什么这个功能让我觉得很糟糕,而不是让你觉得是咆哮,我在听。
  • 您正在寻找的启示是:断言是为了帮助调试,而不是成为生产代码的一部分。
  • 断言只不过是一个“可执行注释”。因此,对于代码的未来读者来说,它具有一些小的非零值。

标签: java language-features assert design-by-contract


【解决方案1】:

assert按合同设计 的有用部分。在这种情况下,断言可用于:

  • 前置条件检查。
  • 后置条件检查。
  • 中间结果检查。
  • 类不变量检查。

评估断言的成本可能很高(例如,类不变量,它必须在调用类的任何公共方法之前和之后保持)。断言通常只在调试版本和测试目的中需要;你断言不可能发生的事情——这些事情是有 bug 的同义词。断言根据自己的语义验证您的代码。

断言不是输入验证机制。当输入在生产环境中确实可能正确或错误时,即对于输入输出层,请使用其他方法,例如异常或旧的条件检查.

【讨论】:

    【解决方案2】:

    Java 的断言并不是真正用于参数验证的——它是specifically stated that assertions are not to be used instead of dear old IllegalArgumentException(也不是它们在 C-ish 语言中的使用方式)。它们更多地用于内部验证,让您可以对代码进行假设,而这些假设看起来并不明显。

    至于关闭它们,你也可以在 C(++) 中这样做,只是如果有人有一个无断言的构建,他们就无法打开它。在 Java 中,您只需使用适当的 VM 参数重新启动应用程序。

    【讨论】:

    • 链接不是这么说的。它说它们不能用于 PUBLIC 方法中的参数验证——它明确给出了一个在 PRIVATE 方法中使用它们进行参数验证的示例。对于不明显的代码做出假设,这通常不是 cmets 的工作吗?不过,我不知道 C/C++;我肯定看到通过命令行开关打开它们比完全重新编译更具吸引力。
    • 是的,您可以对某些私有方法使用断言。但是在生产中不应该有对这些私有方法的无效调用(如果你不能保证这一点,请使用异常或其他机制)。该页面明确表示应该使用断言来记录假设,而不是 cmets。请参阅i % 3 == 2 示例。
    【解决方案3】:

    我认为它是解释和设想断言用法的方式。

    如果您真的想在实际生产代码中添加检查,为什么不直接使用 If 或任何其他条件语句?

    那些已经存在于语言中的东西,断言的想法只是让开发人员添加断言,前提是他们并不真的期望这种情况会发生。

    例如检查一个对象是否为空,假设一个开发人员编写了一个私有方法并从他知道他传递一个非空对象的类中的两个地方调用它(这不是理想的例子,但可能适用于私有方法) , 而不是添加不必要的检查 if 因为从今天开始你知道 object 不可能为 null 但是如果明天有人使用 null 参数调用此方法,在开发人员的单元测试中,由于存在断言,这可能会被捕获,并且在最终代码中您仍然不需要 if 检查。

    【讨论】:

    • 这比我认为的更相信单元测试的完整性(或存在!),但是,我看到了它的逻辑。 :-) 谢谢。
    【解决方案4】:

    我见过的每种带有断言的语言都具有关闭它们的能力。当你写一个断言时,你应该想“这很愚蠢,在宇宙中这不可能是假的”——如果你认为它可能是假的,那应该是一个错误检查。如果出现严重错误,该断言只是为了在开发过程中为您提供帮助;当您为生产构建代码时,您禁用它们以节省时间并避免(希望)多余的检查

    【讨论】:

    • 我认为是哲学上的差异让我感到困惑。对我来说,检查输入参数的健全性并不是对它们是否错误的评论。就是说这个方法需要他们不要出错。当然,我可能认为错误的输入是不可能的,但我知道什么?代码库很大,我对它的理解是不完整的,在未来的某个时候,一些白痴(比如,你知道,我)可能会再次使错误的输入成为可能。但话虽如此,我看到了能够打开或关闭昂贵支票的价值。
    • “应该是错误检查”:你的意思是,程序输入数据的错误,而不是程序本身的错误检查?
    【解决方案5】:

    断言旨在确保您确信您的代码实现的事情确实得到实现。它在产品的开发阶段有助于调试,通常在发布代码时省略。

    我错过了什么?

    您没有按照应有的方式使用断言。你说“检查输入参数的有效性”——这正是你不想想用断言来验证的那种事情。

    这个想法是,如果一个断言失败,你的代码中 100% 就有一个错误。断言通常用于在错误出现之前识别错误。

    【讨论】:

    • “你的代码 100% 有 bug”:在代码本身,或者在断言检查的代码中。
    【解决方案6】:

    对于代码维护者来说,断言确实是一个伟大而简洁的文档工具。

    例如我可以写:

    foo 应该是非 null 和更大 大于 0

    或将其放入程序主体中:

    assert foo != null;
    assert foo.value > 0;
    

    它们对于记录私有/包私有方法以表达原始程序员不变量非常有价值。

    为了获得额外的好处,当子系统开始表现不稳定时,您可以打开断言并立即添加额外的验证。

    【讨论】:

      【解决方案7】:

      这听起来很对。断言只是一个对调试代码有用的工具——它们不应该一直打开,尤其是在生产代码中。

      例如,在 C 或 C++ 中,断言在发布版本中被禁用。

      【讨论】:

        【解决方案8】:

        如果断言不能被关闭,那么它们为什么还要存在。

        如果你想对输入进行有效性检查,你可以很容易地写

        if (foobar<=0) throw new BadFoobarException();
        

        或弹出一个消息框或任何在上下文中有用的东西。

        断言的全部意义在于它们可以打开以进行调试并关闭以进行生产。

        【讨论】:

          【解决方案9】:

          这并不能直接回答您关于assert 的问题,但我建议您查看guava/google-collections 中的Preconditions 类。它允许你写这样的好东西(使用静态导入):

          // throw NPE if listOfStuff is null
          this.listOfStuff = checkNotNull(listOfStuff);
          
          // same, but the NPE will have "listOfStuff" as its message
          this.listOfStuff = checkNotNull(listOfStuff, "listOfStuff"); 
          

          看起来像这样的东西可能是您想要的(并且无法关闭)。

          【讨论】:

            【解决方案10】:

            断言不是供最终用户看到的。它们是为程序员准备的,因此您可以确保代码在开发时正在做正确的事情。测试完成后,通常会出于性能原因关闭断言。

            如果您预计生产中会发生一些不好的事情,例如 listOfStuff 为空,那么您的代码可能没有经过足够的测试,或者您在让代码执行之前没有对输入进行清理。无论哪种方式,“如果(坏东西){抛出异常}”会更好。断言用于测试/开发时间,而不是用于生产。

            【讨论】:

              【解决方案11】:

              如果您愿意在断言失败时向最终用户支付 1 美元,请使用 assert

              断言失败应该表明程序中存在设计错误。

              一个断言表明我以我知道并保证指定谓词始终成立的方式设计了程序。

              断言对我的代码的读者很有用,因为他们看到 (1) 我愿意为该属性设置一些钱; (2) 在以前的执行和测试用例中,该属性确实成立。

              我的赌注假定我的代码的客户遵守规则,并遵守他和我商定的合同。该合同可以是宽容的(允许所有输入值并检查其有效性)或要求(客户和我同意他永远不会提供某些输入值[描述为先决条件],并且他不希望我检查这些值一遍又一遍地)。 如果客户遵守规则,但我的主张仍然失败,客户有权获得一些赔偿。

              【讨论】:

                【解决方案12】:

                断言用于指示代码中可能可恢复的问题,或作为调试的帮助。对于更严重的错误,您应该使用更具破坏性的机制,例如停止程序。

                它们还可用于在应用程序稍后在调试和测试场景中失败之前捕获不可恢复的错误,以帮助您缩小问题范围。部分原因是完整性检查不会降低生产环境中经过良好测试的代码的性能。

                此外,在某些情况下,例如资源泄漏,这种情况可能并不理想,但停止程序的后果比继续执行的后果更糟。

                【讨论】:

                • 我不同意断言应该用于预期在生产中发生的可恢复错误。这通常是检查异常的用途。但是,在某些情况下,无论是例外还是断言都不合适。
                猜你喜欢
                • 2011-02-27
                • 1970-01-01
                • 2012-04-13
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2021-11-28
                • 2011-01-28
                相关资源
                最近更新 更多