67

我想知道为什么这个assert关键字在 Java 中没有得到充分利用?我几乎从未见过它们被使用过,但我认为它们是个好主意。我当然更喜欢简洁:

assert param != null : "Param cannot be null";

详细程度:

if (param == null) {
    throw new IllegalArgumentException("Param cannot be null");
}

我怀疑它们没有得到充分利用,因为

  • 他们来得比较晚(Java 1.4),那时很多人已经建立了他们的 Java 编程风格/习惯
  • 它们在运行时默认关闭
4

11 回答 11

64

理论上,断言是用于测试不变量的假设,这些假设必须为真,才能使代码正确完成。

显示的示例是测试有效输入,这不是断言的典型用法,因为它通常是用户提供的。

断言通常不用于生产代码,因为存在开销,并且假设在开发和测试期间不变量失败的情况已被捕获为编码错误。

你关于他们“迟到”到java的观点也是他们没有被更广泛地看到的一个原因。

此外,单元测试框架允许将编程断言的某些需求置于被测试代码的外部。

于 2008-11-18T14:45:16.243 回答
57

使用断言来测试用户输入是滥用断言。抛出一个IllegalArgumentException无效输入更正确,因为它允许调用方法捕获异常,显示错误,并做它需要做的任何事情(再次请求输入,退出等等)。

如果该方法是您的一个类中的私有方法,则断言很好,因为您只是想确保您不会不小心将空参数传递给它。您使用断言进行测试,并且当您测试了所有通过的路径并且没有触发断言时,您可以将它们关闭,这样您就不会在它们上浪费资源。它们也像注释一样有用。方法的assert开头是维护人员的良好文档,说明他们应该遵循某些先决条件,而assert结尾的后置条件则记录了该方法应该做什么。它们和评论一样有用;此外,因为有了断言,他们实际上测试了他们记录的内容。

断言用于测试/调试,而不是错误检查,这就是它们默认关闭的原因:阻止人们使用断言来验证用户输入。

于 2008-11-18T15:03:12.587 回答
18

使用断言编程

默认情况下,断言在运行时被禁用。两个命令行开关允许您有选择地启用或禁用断言。

这意味着,如果您无法完全控制运行时环境,您甚至无法保证断言代码会被调用。断言旨在用于测试环境,而不是用于生产代码。您不能用断言替换异常处理,因为如果用户在禁用断言(默认)的情况下运行您的应用程序,您的所有错误处理代码都会消失。

于 2008-11-18T15:01:32.590 回答
18

“Effective Java”中,Joshua Bloch 建议(在“检查参数的有效性”主题中)(有点像采用的简单规则),对于公共方法,我们将验证参数并在发现无效时抛出必要的异常,对于非公共方法(未公开,您作为它们的用户应该确保它们的有效性),我们可以使用断言来代替。

于 2008-11-18T18:21:25.667 回答
9

@Don,您对默认情况下关闭断言感到沮丧。我也是,因此编写了这个内联它们的小 javac 插件(即发出字节码if (!expr) throw Ex而不是这个愚蠢的断言字节码。

如果在编译 Java 代码时在类路径中包含fa.jar,它会发挥它的魔力,然后告诉

Note: %n assertions inlined.

@see http://smallwiki.unibe.ch/adriankuhn/javacompiler/forceassertions或者在 github https://github.com/akuhn/javac

于 2009-03-20T13:14:05.060 回答
2

我不确定您为什么要费心编写断言然后用标准的 if then 条件语句替换它们,为什么不首先将条件编写为 ifs 呢?

断言仅用于测试,它们有两个副作用:启用时更大的二进制文件和性能下降(这就是你可以关闭它们的原因!)

不应使用断言来验证条件,因为这意味着在启用/禁用断言时,您的应用程序的行为在运行时会有所不同——这是一场噩梦!

于 2009-11-09T13:40:50.203 回答
2

断言很有用,因为它们:

  • 尽早发现编程错误
  • 使用代码的文档代码

将它们视为代码自我验证。如果它们失败了,则意味着您的程序已损坏并且必须停止。在单元测试时始终打开它们!

The Pragmatic Programmer中,他们甚至建议让它们在生产环境中运行。

打开断言

使用断言来防止不可能的事情。

请注意,如果断言失败,则断言会抛出 AssertionError,因此不会被 catch Exception 捕获。

于 2013-07-29T08:12:43.420 回答
2

tl;博士

  • 是的,在有意义的生产环境中使用断言测试。
  • 如果您愿意,可以使用其他库(JUnitAssertJHamcrest等)而不是内置工具。assert

此页面上的大多数其他答案都推动了格言“断言通常不用于生产代码”。虽然在诸如文字处理器或电子表格之类的生产力应用程序中是正确的,但在 Java 如此常用的自定义业务应用程序中,断言 -生产中的测试非常有用,而且很常见。

就像编程世界中的许多格言一样,在一种情况下开始正确的东西会被误解,然后在其他情况下被误用。

生产力应用

“断言通常不用于生产代码”这句格言虽然很常见,但却是不正确的。

形式化的断言测试起源于诸如Microsoft Word之类的文字处理器或Microsoft Excel之类的电子表格之类的应用程序。这些应用程序可能会在用户每次击键时调用一系列断言测试断言。这种极端的重复严重影响了性能。因此,只有在有限分发中的此类产品的 beta 版本才启用断言。因此格言。

商业应用

相比之下,在面向业务的数据输入、数据库或其他数据处理应用程序中,在生产环境中使用断言测试非常有用。对性能的微不足道的影响使它非常实用 - 并且很常见。

测试业务规则

在生产运行时验证您的业务规则是完全合理的,应该受到鼓励。例如:

  • 如果发票必须始终具有一个或多个行项目,则编写一个断言测试,而不是发票行项目的计数大于零。
  • 如果产品名称必须至少包含 3 个字符或更多字符,请编写一个测试字符串长度的断言。
  • 在计算现金分类帐的余额时,您知道结果永远不会是负数,因此请检查是否存在负数,表明数据或代码存在缺陷。

此类测试对生产性能没有显着影响。

运行时条件

如果您的应用程序期望某些条件在您的应用程序在生产中运行时始终为真,请将这些期望作为断言测试写入您的代码中。

如果您期望这些条件有时会合理地失败,那么不要编写断言测试。也许抛出某些异常。然后在可能的情况下尝试恢复。

健全性检查

在生产运行时进行健全性检查也是完全合理的,应该受到鼓励。当一些奇怪的事情发生时,测试一些人们无法想象是不真实的任意条件已经在无数情况下拯救了我的培根。

例如,在某个库中测试将镍 (0.05) 四舍五入导致镍 (0.05) 有助于我成为第一批发现 Apple 在其Rosetta库中发布的浮点技术缺陷的人之一。PowerPC 到 Intel 的过渡。这样的缺陷传播到公众似乎是不可能的。但令人惊讶的是,该漏洞并未引起原始供应商 Transitive 和 Apple 以及对 Apple 测试版进行测试的早期开发人员的注意。

(顺便说一句,我应该提一下......永远不要使用浮点数来赚钱,使用BigDecimal。)

框架的选择

assert您可能需要考虑使用另一个断言框架,而不是使用内置工具。您有多种选择,包括:

或者自己动手。制作一个小类以在您的项目中使用。像这样的东西。

package work.basil.example;

public class Assertions {
    static public void assertTrue ( Boolean booleanExpression , CharSequence message ) throws java.lang.AssertionError {
        if ( booleanExpression ) {
            // No code needed here.
        } else { // If booleanExpression is false rather than expected true, throw assertion error.
            // FIXME: Add logging.
            throw new java.lang.AssertionError( message.toString() );
        }
    }

}

示例用法:

Assertions.assertTrue( 
    localTime.isBefore( LocalTime.NOON ) , 
    "The time-of-day is too late, after noon: " + localTime + ". Message # 816a2a26-2b95-45fa-9b0a-5d10884d819d." 
) ;

你的问题

他们来得比较晚(Java 1.4),那时很多人已经建立了他们的 Java 编程风格/习惯

是的,这是完全正确的。许多人对 Sun/JCP 为断言测试开发的 API 感到失望。与现有图书馆相比,它的设计乏善可陈。如此多的人忽略了新的 API,并坚持使用已知的工具(第 3 方工具或滚动您自己的迷你库)。

默认情况下,它们在运行时关闭,WHY OH WHY??

在最早的几年里,Java 因性能速度差而受到诟病。具有讽刺意味的是,Java 迅速发展成为性能最佳的平台之一。但糟糕的说唱就像一股臭味。因此,Sun 对任何可能以任何可衡量的方式影响性能的事情都非常警惕。因此,从这个角度来看,将禁用断言测试设为默认值是有意义的。

另一个默认禁用的原因可能与 Sun 在添加新的断言工具时劫持了单词assert. 这不是以前保留的关键字,并且需要对 Java 语言进行的少数更改之一。该方法名称assert已被许多库和许多开发人员在他们自己的代码中使用。有关此历史转变的一些讨论,请阅读此旧文档Programming With Assertions

于 2019-02-20T03:25:26.777 回答
1

断言非常有限:您只能测试布尔条件,并且每次都需要编写代码以获得有用的错误消息。将此与 JUnit 的 assertEquals() 进行比较,后者允许从输入生成有用的错误消息,甚至在 JUnit 运行器中的 IDE 中并排显示两个输入。

此外,您无法在我迄今为止看到的任何 IDE 中搜索断言,但每个 IDE 都可以搜索方法调用。

于 2008-11-18T14:57:00.977 回答
0

事实上,它们是在 Java 1.4 中出现的。

我认为主要问题是,当您在不直接管理 JVM 选项的环境中编码时,例如在Eclipse或 J2EE 服务器中(在这两种情况下都可以更改 JVM 选项,但您需要深入搜索以找到它可以做到),更容易(我的意思是它需要更少的努力)使用if和异常(或者更糟糕的是不使用任何东西)。

于 2008-11-18T14:44:23.030 回答
0

正如其他人所说:断言不适合验证用户输入。

如果您担心冗长,我建议您查看我编写的库:https ://github.com/cowwoc/requirements.java/ 。它允许您使用很少的代码来表达这些检查,它甚至会代表您生成错误消息:

requireThat("name", value).isNotNull();

如果你坚持使用断言,你也可以这样做:

assertThat("name", value).isNotNull();

输出将如下所示:

java.lang.NullPointerException: name may not be null
于 2017-04-06T17:28:23.867 回答