为什么断言不能用于公共方法中的参数检查?

时间:2012-12-12 03:53:23

标签: java methods arguments assert

我在java的经验有限的情况下从未真正使用过断言,并且想知道为什么我在许多网站上阅读过很多关于断言的书籍,同样的警告断言语句不应该用于公共方法中的参数检查?我想知道这是否与assert语句相对于java中其他语句的执行顺序有关。

7 个答案:

答案 0 :(得分:4)

非正式地,参数检查和断言有不同的用途:

  • 参数检查用于检测调用方法的人错误地执行某些操作的情况,而
  • 断言用于检测做错事情的情况。

基本上,当你断言条件时

assert val < total;

该检查向您的代码的读者传达了以下简单英语的想法:“我检查了我的代码,根据我的推理,我确信val总是小于total

另一方面,当您检查参数val时,

if (val >= total) throw new InvalidArgumentException("val");

您的代码说“调用者忘记确保val小于total”。

这是两种不同的想法,因此很自然地采用两种不同的方式在代码中传达它们。

答案 1 :(得分:3)

根据programming with Assertions

  

参数检查通常是方法的已发布规范(或契约)的一部分,无论是启用还是禁用断言,都必须遵守这些规范。 使用断言进行参数检查的另一个问题是错误的参数应该导致适当的运行时异常(例如IllegalArgumentException,IndexOutOfBoundsException或NullPointerException)。断言失败不会引发适当的异常

答案 2 :(得分:3)

断言的意图是检查你的程序逻辑 - 断言失败是“停止一切 - 有一个错误!”指示。特别是,断言失败表示“这里有一个错误”,但“HERE”位于代码内部的某个地方,失败的原因只能通过检查您的代码(API的用户不能和应该这样做)来确定。不应该这样做。)

当你在API中获得错误的数据时,你想表明“嘿!你给了我糟糕的数据!” IllegalArgumentException及其亲属是表明这一点的方式。

(但请注意,对代码中的参数使用断言检查没有任何问题 - 您不支持团队外部人员使用的真正“公共”API。)

但这确实提出了另一个观点:在合理/可能的范围内,您应该“捕获”由于您自己的错误而可能发生的IllegalArgumentException之类的内部异常,并将它们转换为FatalError异常或其他一些,因此用户当您的代码中存在错误时,您的API不会导致他寻找错误的参数。

(另请注意public - Java关键字 - 与“公共接口”之间的区别 - 这意味着某些界面可作为“正式”API提供,供编程之外的个人使用这是我们在这里担心的后一种情况。)

答案 3 :(得分:2)

首先,Java断言在运行时被删除,除非在编译时显式启用它们。

异常更适合于参数验证,因为你期望处理它们,而断言具有语义含义“这在代码中的这一点必须是真的,否则我不知道如何处理它”。 / p>

答案 4 :(得分:1)

因为在生产版本中禁用了断言。如果你需要能够在错误地使用公共方法时捕获,那么断言不会触发生成构建中的检查,并且异常将是更好地发出错误信号的方法。

这对于图书馆来说尤其重要,因为你无法控制你的方法的调用方式和方式;对于应用程序,公共方法中的断言是正常的,因为当你有正确的输入验证时(其中“输入”可以是用户输入,或来自另一个系统或来自持久存储的输入),那么断言应该永远不会被触发。

答案 5 :(得分:1)

断言不应该用于公共方法中的参数检查只是简单的错误的。仅仅因为你在书中读到的东西并不意味着它是正确的。

公共方法中的参数检查完全属于检查错误的一般类别,因此它应该被视为这样,并且我们已经用于捕获错误的机制是断言。

如果方法的公共接口说“此方法的'索引'参数永远不应该是负数”,那么使用负索引调用它是一个错误,并且以下情况适用:

  1. 绝不应该在生产环境中发生。 (测试应保证这一点。)

  2. 即使它是在生产环境中发生的,也没有任何人可以做任何事情来缓解这个问题,因此它可能会因索引超出范围或空指针异常而进一步失败,没有区别。

  3. 没有人应该依赖那里的支票。 (故意允许使用无效参数调用该方法,捕获生成的IllegalArgumentException,并尝试采取纠正措施,这是一个糟糕的坏主意。)

  4. 事实上,保证即使在生产中也会抛出一个特定的异常,因为你认为这是一个错误的条件会诱使n00b程序员编写依赖于生产中抛出的异常的代码,以及因此,它本身就是一个错误。

    错误的另一个概念是断言表示“此处”存在错误。如果断言检查方法参数,那么断言捕获的错误可以在堆栈跟踪中列出的方法调用链的任何位置,或者甚至可能在其他地方。

    那里有数十亿台设备,其中大部分使用宝贵的电池供电,每天执行数以万亿计的指令,这只是针对数百万人保证永远不会发生的情况进行测试 - 开发人员和测试人员已经煞费苦心地进行的小时测试。这很疯狂。

    因此,只需简单assert就可以进行参数检查。如果您需要编写测试代码以确保您的方法确实正确地针对特定的错误条件进行断言,请考虑以下构造:

    assert index >= 0 : new IllegalArgumentException( "index" );
    

    显然,如果启用了断言,这只会执行测试,但真正的优点是,如果断言失败,那么cause异常的AssertionError将是{{1因此,您的测试代码将能够确保捕获到正确的错误。

    有关此主题的更多信息,请参阅我的博客上的这篇文章:michael.gr - Assertions and testing

答案 6 :(得分:1)

迈克给出了一个很好的答案,不幸的是,在Java文献中很少有人为此辩护。 对不起,我在这里没有足够的声誉来支持它。 相反,我会提供自己的答案,我希望能为Mike的观点增加一些额外的支持。

绝大多数Java文献都传播了你不应该使用assert检查公共方法参数的教条。 换句话说,他们说assert不应该用来检查 关于公共方法的前置条件,你应该使用explicit if (!precond) throw SomeException();指示。 标准Java库中充满了此策略的示例。

支持这一点的论据似乎是:

  • 满足前提条件的责任在于函数调用者(客户端的代码),因此不是“您的”代码(供应商的代码)。
  • 断言检查是可选的,因此您最好强制检查。

嗯,这对我来说似乎是一种非常光顾的态度。 您的代码的客户端是程序员,就像您一样。 满足前提条件的是客户的责任,当然, 如果客户不这样做,那就是一个bug; 客户的错误,而不是你的。 当然,您希望您的客户使用启用的断言来检查他们的程序,不是吗? 但一旦他们确信他们的计划是正确的, 你为什么还要强迫你做出无用的例外?

现在,从客户的角度来看。 您正在使用String.charAtdocumentation for this method告诉您

  

public char charAt(int index)

     

返回指定索引处的char值。索引范围从0到   长度() - 1. [...]

明确说明了前提条件。 但后来又增加了

  

抛出:      IndexOutOfBoundsException - 如果index参数为负数或不小于此字符串的长度。

所以,如果你确定你的索引在界限范围内,你还会把你的电话放在try ... catch内吗? 请注意,如果您尊重前提条件,文档并不会真正告诉您不会抛出异常, 但当然这就是你的期望,不是吗?

所以,你确定索引在界限之内,你甚至可能已经断言过,你不会浪费时间和空间与无用的try永远不会捕获任何东西,但是{{ 1}}仍然会浪费时间检查你表现得很好。

我看待它的方式,

  • 断言应该用于检查完全由程序员控制的条件,无论是客户还是供应商。
  • 应使用带有异常的显式条件指令(或其他错误信号设备,如错误返回码)来检查依赖于无法控制的外部因素的条件,如用户输入,不可预测的操作系统限制等。

这就是我教给学生的原因。 我不告诉他们。 这不是教条。 我让他们知道这与他们在大多数书中读到的内容相反, 但我敦促他们自己决定。

这个问题不是针对Java的,而是一个关于编程方法的问题。 我遵循的方法类似于Design by Contract。 某些语言具有支持此样式的特殊语法,例如显式声明前置条件,后置条件和对象不变量的可能性,这些明确包含在代码的公共规范(和文档)中。

JoãoRodrigues