为什么我们不能在公共方法中使用断言?

23

为什么我们不能在公共方法中使用断言?

我曾在某处阅读过:

"在公共方法中,assert 不合适,因为该方法保证始终执行参数检查。无论是否启用断言,公共方法都必须检查其参数。此外,断言结构不会抛出指定类型的异常,它只能抛出 AssertionError。"

那么,这对私有方法也适用吗?
我没有完全理解上述说法。


10
“不能”和“不应该”之间有区别。 - matt b
3
你有这段引文的来源吗? - jmg
@jmg - 我已在我的答案中添加了源代码和精确的引用。它仅禁止在公共方法中使用断言进行参数检查。 - Andy Thomas
11个回答

26

重要的区别在于,您认为错误值是由:

a) 编程错误导致需要在代码中修复。

b) 输入错误无法在代码中预防,需要在运行时进行处理。

对于第一种情况,应使用assert,因为程序代码需要进行修复。如果是后一种情况,则应使用适当的运行时或检查异常。


我认为assertions用于检测编程错误,而不是用户/外部输入。也许作者将公共方法误认为是外部输入,当你有公共方法并非由外部输入调用时会出现这种情况。

我会使用assertions来检查参数以检测编程错误。在我看来,这通常是它们最好的用途。相比之下,私有方法只能由同一类中的代码调用,并且您应该期望它们经过很好的单元测试并具有有限的可能的访问/用途。

我发现您更有可能通过公共接口出现编程错误,因为不同的人会做出不同的假设(assertions是文档和检查假设的好方法)。内部检查不像您期望的那样有用,因为如果没有编写整个内部代码库,同一程序员将可以访问内部代码。


16

断言(Assertions)不应用于公共方法中对参数进行检查,原因如下:

  • 断言可以被禁用,而参数检查绝不能被禁用,因为它们是方法与调用者之间的契约。
  • 断言失败时不会抛出适当的异常来表示无效的参数。

示例:

    /**
     * @throws ArithmeticException if divisor is zero
     */ 
    public void int divide(int divisor) {
        if (divisor == 0) {
            throw new ArithmeticException("Cannot divide by zero");
        }
        ...
    }

如果您在这里使用了一个断言,它可能会被关闭,并且会抛出一个不太有用和没有信息的AssertionFailedException异常。


3
目前为止,你引用的那句话是无意义的,我认为。
确保的是,assert不是用于参数验证。
但在每个非平凡程序中都有(或应该有)许多不变量,这就是断言可能会派上用场的地方。如果您可以用断言表达不变量,请这样做,无论该方法是否为公共方法。
然后,将发生以下情况之一:
a)一切正常。 b)在运行时,程序因未满足的断言而失败。如果断言正确,则违反了不变量,您就有机会找出原因并修复错误(或重新考虑设计)。

2
这可能是Java SE指南中的原始来源,链接为"Programming with assertions." 不要使用断言来检查公共方法的参数。断言不适用,因为该方法保证始终执行参数检查。无论是否启用断言,都必须检查其参数。此外,assert结构不会抛出指定类型的异常。它只能抛出AssertionError。
这并不禁止在公共方法中使用断言。它只禁止用于检查公共方法的参数。
断言测试不变量。类控制发送到其私有方法的实际参数,并且可以保证不变量。一个类不能控制发送到它的公共方法的实际参数,如果先决条件被违反,应该抛出异常--即使断言被关闭。
更多关于何时使用断言的细节here

1

我给出的回答并不完全准确。当然,您可以在公共方法(或任何您喜欢的地方)使用assert。

重点更多地在于您应该做什么或不应该做什么。我自己非常理解其他人关于何时应该或不应该使用断言的回答。

但是我必须承认,我从来没有使用过断言,并且很少看到代码中有断言。我只工作了几年,但在我工作的4个完全不同的公司中,没有一个公司在代码中使用了断言。无论是用于预订航班的超过1000万行代码的Web应用程序,还是用于控制航天器的控制中心,用于管理大型超市或缺陷跟踪器的软件。它们都没有使用断言。所有公司都有不同的需求和方法。没有使用断言。

对我来说,原因很简单。这里的人们说断言是用于调试的。没问题。而且您可以停用它们以提高速度。一开始没问题。程序越复杂,您花费的时间就越长。即使代码覆盖率达到100%,即使进行了广泛的集成和验证测试,某些错误也只能在生产环境中发现。仅仅因为您的用户最终会比您更多地使用应用程序。而且他们不会像您一样使用它。

有趣的是,因为这样的代码,我们在生产日志中仍然看到堆栈跟踪:

catch (MyException e) {
  logger.war("This should never happen",e);
}

这意味着在生产中你永远不知道会发生什么。
如果有机会进行检查,就去做吧。当然,在这里日志注释比有用更好笑,最好让异常突出显示。
在任何情况下,都不要把它作为将在生产中禁用的断言。因为那样是毫无用处的。把它变成普通代码引发一个异常。确保适当记录日志。确保界面上显示错误。然后确保您能够获取异常和日志以进行调查。
重要的是,某一天或其他用户会做一些使警告弹出的事情。可能是由于编写不良的代码或其他任何原因,你会看到它。这意味着,与其花费两天时间找出程序为什么有这种奇怪的行为,不如用完整的堆栈跟踪作为起点,在2小时内解决问题。
关闭的断言检查与根本没有检查相同。这是你必须编写、阅读和维护的代码... 但却毫无意义。我理解整个性能论据,即生产中的断言会减慢进程。是的,在极少数情况下,会出现性能问题。在大多数情况下,你几乎什么也得不到,却失去了宝贵的提示。

我尊重你的观点。然而,断言可以在代码中快速地进行分布,而不必考虑性能或对代码覆盖率的影响。我担心仅使用异常来处理不变量可能会导致表达的不变量更少。即使可以关闭它们,表达不变量也是有价值的。 - Andy Thomas

1

总体来说,这似乎是可行的。虽然有些情况下可能会很有用。

考虑到我们可能想要对已知存在的元素执行数据库更新操作。那么查看例程是否成功可能会很有用,例如:

public void update(Object o) {
   int nUpdatedObjects = dao.update(o);
   assert(nUpdatedObjects == 1)
}

在这种情况下,它用于使用快速失败原则验证dao层。

1
同意这种情况是有道理的;我认为引用部分可能是指使用assert()来验证方法参数是否合理。(这很有用,但只是assert()所擅长的一部分,正如你所指出的那样。 :) - sarnold

1

断言是用于调试的;公共方法通常不应该通过调试断言来验证事物,而应该通过进行适当的参数检查并抛出适当的异常来验证。如果您想要验证内部对象状态,则可以使用它,但不要用于验证参数。


1
@Ingo:考虑到断言通常在生产构建中被禁用,这是一个合理的说法。 - geekosaur
2
我不同意。在我的看法中,断言是用来确保不变量的。当然,在生产代码中,人们应该已经非常确定没有违反任何不变量。 - Ingo

1
在公共方法中使用断言是没有问题的。它们可以用来检查你调用方法的对象或类的某些不变量(你认为是真实的事情)是否真实存在。
例如,你可以像我所做的那样,在构建器的公共build()方法中使用断言,以确保构建器通过该类中的自己内部代码正确地初始化了(因为我有许多重载的构造函数)。
但是,永远不要使用断言来检查公共方法的参数。这是一个重要的区别。我认为其他答案已经清楚地解释了原因,所以我不会重复任何内容。

0

这个禁令只适用于公共接口。

来自http://download.oracle.com/javase/6/docs/technotes/guides/language/assert.html#preconditions

按照惯例,公共方法的前提条件是通过显式检查来强制执行的,这些检查会抛出特定的指定异常。

基本上,公共接口保证检查前提条件并抛出特定异常而不是 AssertionError。

对于所有其他情况,断言非常有价值,并且是“按合同编程”的基石。请参见http://java.sun.com/developer/technicalArticles/JavaLP/assertions以获得良好的介绍。

Java 有未检查异常是有原因的--这些异常通常不应被捕获,因为它们可能会导致灾难性的故障。任何内存分配都可能抛出 OutOfMemoryError 异常。一个失败的断言(客户端代码中提供无效参数给我们的 API 的错误)同样具有严重后果。确实可以关闭断言,但永远不应该这样做。如果你担心运行你的代码的人会关闭断言,你可以自己创建一个无法关闭的简单断言类。使用断言的原则没有改变。你需要考虑的关于断言的唯一问题是接口的性能契约。请注意,这也可以是“隐式”的契约(即明显的实现应该非常快,而花费一分钟则超出了暗示的性能契约)。所以要确保在性能契约下验证你的断言是可接受的。

0

公共方法可以被任何人调用,并且不能控制传递的参数值。

如果假设在公共方法中使用断言验证输入参数值,那么如果禁用了断言,这些检查(验证)可能不会发生(或执行),我们将从方法执行中得到不希望的结果。为避免这种不良结果,我们不应使用断言来验证公共方法参数值。

现在你可能会问为什么要使用断言来验证私有方法参数值?

原因是私有方法可以从定义它的类中调用(从实例方法或静态主方法中)。事实上,类的开发者知道私有方法的全部细节-它的功能、如何调用以及要传递的参数值。因此,私有方法参数值可以通过断言安全地进行验证。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接