【问题标题】:Using AssertionError and assertions in java在 java 中使用 AssertionError 和断言
【发布时间】:2014-02-26 21:08:35
【问题描述】:

我在 Java 中以标准方式使用断言,并在我的 IDE 中打开它们。所以它们不是生产版本的一部分。最近我看到throw new AssertionError() 的代码示例,我开始思考应该使用AssertionError 而不是断言的情况。

我的猜测是,主要区别在于断言的可选性,因此它们不会降低生产性能,因此它们可以在代码中经常出现,但修复用户报告的难以重现的错误更难。

对于AssertionError,正好相反。

我还发现AssertionError 在代码中不应执行的地方更实用,而不是使用assert false //We should not be here。特别是如果需要返回值。例如:

int getFoo(AnEnum a){
    if (a == AnEnum.ONE)
       return bar();
    else if (a == AnEnum.TWO)
       return SOME_VALUE;
    //else
    assert false; //throw new AssertionError();
    return -1; //not necessary when usin AssertionError
}
  • 我的推理正确吗?
  • 还有哪些其他差异/用例/最佳实践/限制 哪种方法?
  • 关于在AssertionError 中提供描述 - 是否应该提供或者仅仅是它是Error 的事实(以及 断言类型)足以或多或少地确定堆栈跟踪 发现错误时会提供吗?

【问题讨论】:

  • 您的断言实际上有多慢?就我个人而言,我喜欢在现实世界中开车时系好安全带,而不仅仅是在学习时:)到达无效状态,您可能会擦除真实的生产数据。)仅当您有证据表明它们正在显着减慢您的程序时,才考虑删除断言。
  • 基本上你是对的。尽管有些人希望从生产代码中删除断言,因为无效行为被视为比彻底的应用程序失败更好。使用 AssertionError 提供至少一个基本的错误描述可能是一个好主意,这样就可以完成第一级调试而无需深入研究源文件(如果某些 bozo 只报告错误消息,而没有堆栈跟踪)。
  • @JonSkeet 但是你可以只为那部分代码保留断言。使用 AssertionError 你没有任何选择,即使断言被故意禁用,它也会被抛出。
  • @HotLicks 您也可以使用assert false "Should not be reached" 给出原因代码,这样就不会区分assert falsethrow AssertionError()
  • 根据 Java 技术说明 "Programming With Assertions",如果 getFoo 可公开访问,则您的示例无效

标签: java exception-handling assertions


【解决方案1】:

在技术说明"Programming With Assertions: Control Flow Invariants" 中给出了以下代码:

void foo() {
    for (...) {
      if (...)
        return;
    }
    assert false; // Execution should never reach this point!
}

但也给出了以下注释:

注意:请谨慎使用此技术。如果按照 Java 语言规范中的定义无法访问语句,那么如果您尝试断言它未到达,则会出现编译时错误。同样,一个可接受的替代方法是简单地抛出一个 AssertionError。


您可能不希望在关闭断言时抛出AssertionError。由于AssertionError 构造函数是公开的,而且AssertionError(String message, Throwable cause) 可能没有替代品,我猜你应该期待它们,即使它们被关闭。


正如 Jon Skeet 建议的那样,在无法访问的代码(即没有任何要评估的真实表达式)上抛出 AssertionError 永远不会减慢代码速度,因此不会影响性能。


所以最后扔 AssertionError 似乎没问题。

【讨论】:

  • 只发现评论生成赏金之后,当然欢迎其他答案。
  • 我认为比 AssertionError 更具描述性的异常类名称是 CoderMalfunctionError。唉,那是指字符集编码器而不是键盘前面的编码器! ;-)
【解决方案2】:

我建议不要直接抛出AssertionErrors。如果您选择依赖 AssertionErrors 来检查不变量、前置/后置条件、状态条件等,您最好还是在生产环境中使用打开“-ea”标志的常规断言。
原因是断言机制(除了在编译器级别进行优化)让您有机会一次打开或关闭所有断言。即使您现在想不出这样做的理由,如果您将来遇到原因,请考虑您必须检查所有throw new AssertionError(...) 类型代码并用讨厌的@987654324 包围它@ 子句。你明白了。
正如您不希望将幻数硬编码到代码中的许多地方一样,并且可能会改用常量,您也不应该用许多重复来感染您的代码(即throw new AssertionError(...) 部分)。

关于断言的另一个词。我相信你应该在依赖生产代码中的断言错误之前三思而后行。原因是AssertionError 非常通用。它有一个信息和一个原因,但仅此而已。
考虑改为使用特定的RuntimeException 子类,这些子类将通过与问题更相关的特定类以及通过携带与问题相关的实际数据来传达更多信息。
作为一个简单的示例,请考虑您在问题中提到的一个案例,其中存在您不希望到达的代码的某些部分。一个断言或AssertionError 将传达一个事实,即您遇到了一些意想不到的代码,但仅此而已。使用特定的RuntimeException 还可以传递该时间点的局部变量和方法参数的状态。您可能会争辩说,通过设置断言消息或AssertionError 来包含此信息是可行的,但是在使用自动错误记录/处理机制时这不起作用。此类机制可以使用您用于检查意外行为的 RuntimeException 的不同子类上的访问者模式来处理意外行为(我的句柄也指快速失败,不一定是恢复)。

【讨论】:

  • 我在很大程度上同意您的评估(可能比我给出的答案更多),但是我写的答案来自 Sun/Oracle 文档。所以我只能得出结论,根据 Technote 是可以的,如果不允许,构造函数应该被隐藏。除此之外,在assert falsenew Error 之间选择无法访问的代码(其中第一个不被视为退出点)是在两个邪恶之间进行选择。我同意你的观点,错误很糟糕,但必须使用 return null 或类似的东西同样糟糕。
猜你喜欢
  • 1970-01-01
  • 2021-12-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-28
  • 1970-01-01
  • 1970-01-01
  • 2011-03-18
相关资源
最近更新 更多