未充分使用Java断言


67

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

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

冗长的

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

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

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

Java 1.4引入了断言。
Dan Dyer

12
注意:您可以编写assert param!= null:“ Message”; 即。删除括号。与返回一样冗余(someValue); 这是语言功能,而不是函数调用。
PhiLho

@丹谢谢,我已经更新的问题
多纳尔

@Dónal,断言是多余的。换句话说,在上面的示例中,该断言应在参数检查之后出现,以断言该参数确实已被检查。(是的,请再读一遍,断言在Java中是很愚蠢的事情,因为它增加了编译后的字节码的大小,并且带有除非用户这样做就永远不会运行的代码java -ea)。会自动扩展为IllegalArgumentException。)
Pacerier 2014年

Answers:


64

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

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

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

您对它们“迟到”到Java的观点也是为什么它们没有被更广泛地看到的原因。

同样,单元测试框架允许对程序断言的某些需求在要测试的代码外部。


1
极好的答案。我本人将要提到junit及其弟兄,并且C也有一个asset.h,由于与您给出的相同原因,该资产默认情况下处于关闭状态。
Paul Tomblin,

4
@肯·温特尔:“方法参数通常由用户提供”?
语法T3rr0r,2010年

1
@ SyntaxT3rr0r:在这种情况下,“用户”是通用的-包含断言的方法的调用者当然是类/方法的“用户”,并且所传递的值可以是用户输入,数据查询结果到硬值之间的任何值编码的值。
Ken Gentle

2
我认为断言可以用作单元测试的附件。您可以将断言嵌入测试方法中,而不是编写单独的测试方法,尤其是在调试过程中。
Andreas Tasoulas 2013年

那么如何有更多的开销然后检查null和引发异常?
卡佩什·索尼

57

使用断言来测试用户输入是断言。将IllegalArgumentException无效的输入抛出是更正确的,因为它允许调用方法捕获异常,显示错误并执行所需的任何操作(再次询问输入,退出等)。

如果该方法是您的一个类中的私有方法,那么断言就可以了,因为您只是在尝试确保您不会意外地将其传递为null参数。您可以在启用断言的情况下进行测试,并且在测试了所有通过且未触发断言的路径时,可以将其关闭,以免浪费资源。它们也像注释一样有用。一个assert在方法的开始是很好的文档,以维护他们应该遵循一定的前提条件,并且assert在与后置的文档什么的方法应该做到底。它们和评论一样有用。而且,因为有了断言,他们实际上测试了他们记录的内容。

断言用于测试/调试,而不是错误检查,这就是默认情况下处于关闭状态的原因:劝阻人们不要使用断言来验证用户输入。


18

断言编程

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

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


18

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


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上


3
我真正想要的是一个可以在编译阶段禁用断言的插件。那应该解决二进制膨胀问题。
HRJ

@HRJ:绝对如此-我无法相信启用断言是一个运行时选项,而不是编译时选项。
rjmunro 2012年

1
分叉我的源代码并删除AST节点,而不是重写它们。做完了
阿库恩2012年

2

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

断言仅用于测试,它们具有两个副作用:二进制文件较大且启用后性能下降(这就是为什么您可以将它们关闭的原因!)

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


2

断言非常有用,因为它们:

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

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

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

保持断言处于打开状态

使用断言来防止不可能。

请注意,如果断言失败,则它们将引发AssertionError,因此不会被catch Exception捕获。


2

tl; dr

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

此页面上的其他大多数答案都遵循“断言通常不在生产代码中使用”的格言。在生产力应用程序(如文字处理器或电子表格)中为true,而在Java如此常用的自定义业务应用程序中,assertion-生产中的测试非常有用,而且很普遍。

就像编程世界中的许多格言一样,在一个上下文中最初是正确的,然后在其他上下文中则错误地使用了它。

生产力应用

这种“断言在生产代码中通常不使用”的格言虽然很普遍,但却是不正确的。

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

商业应用

相反,在用于数据输入,数据库或其他数据处理的面向业务的应用程序中,在生产中使用断言测试非常有用。对性能的微小影响使其非常实用,而且很常见。

测试业务规则

在生产中的运行时验证业务规则是完全合理的,应该鼓励这样做。例如:

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

此类测试对生产性能没有重大影响。

运行时条件

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

如果你希望这些条件可以合理偶尔失败,然后做写断言测试。也许抛出某些例外。然后尝试尽可能恢复。

健全性检查

生产中在运行时进行健全性检查也是完全合理的,应该鼓励这样做。测试一些无法想象不真实的任意条件,使我的培根在数不清的情况下得救了。

例如,测试将某个镍中的镍(0.05)四舍五入会导致某个库中的镍(0.05),这使我成为第一批发现Apple在其Rosetta库中提供的浮点技术缺陷的人之一。从PowerPC到Intel的过渡。这样的缺陷似乎无法实现。但是令人惊讶的是,该漏洞没有引起原始厂商,Transitive和Apple以及早期访问开发人员在Apple Beta上进行测试的注意。

(顺便说一句,我应该提到...切勿使用浮点数赚钱,请使用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,而使用了已知的工具(第三方工具或自带的微型库)。

默认情况下会在运行时将其关闭,为什么?

最早的几年里,Java因性能低下而受到了不满。具有讽刺意味的是,Java迅速发展成为性能最佳的平台之一。但是糟糕的说唱却像臭味一样散落。因此,Sun非常警惕任何可能以可衡量的方式影响性能的事情。因此,从这个角度来看,禁用断言测试默认值是有意义的。

默认情况下禁用的另一个原因可能与以下事实有关:在添加新的断言功能时,Sun劫持了这个词assert。这不是以前保留的关键字,需要对Java语言进行的少数更改之一。assert许多库和许多开发人员在自己的代码中都使用了方法名称。有关此历史过渡的一些讨论,请阅读此旧文档“使用断言编程”


2
感谢您的理智之词,人们谈论性能就像是为NASA编写软件一样。对我来说,在kibana日志中明确的错误消息价值一百万美元
Kalpesh Soni

1

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

另外,到目前为止,我无法在任何IDE中搜索断言,但是每个IDE都可以搜索方法调用。


0

实际上,他们到达了Java 1.4。

我认为主要的问题是,当您在不像Eclipse或J2EE服务器那样直接由自己管理JVM选项的环境中进行编码时(在两种情况下都可以更改JVM选项,但是您需要深入搜索以查找位置)可以做到),使用起来更容易(我的意思是说,它需要更少的精力)if和例外(或更糟的是什么都不用)。


0

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

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

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

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

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

输出将如下所示:

java.lang.NullPointerException: name may not be null
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.