24

在探索这些问题时,我最近发现了assertJava 中的关键字。起初,我很兴奋。有用的东西我还不知道!一种更有效的方法来检查输入参数的有效性!耶学习!

但后来我仔细看了看,我的热情并没有因为一个简单的事实而被“磨平”而是“完全熄灭”:你可以关闭断言。*

这听起来像是一场噩梦。如果我断言如果输入是 ,我不希望代码继续listOfStuff运行null,那么我到底为什么要忽略该断言?听起来如果我正在调试一段生产代码并怀疑它listOfStuff可能被错误地传递了null但没有看到任何日志文件证据表明该断言被触发,我无法相信listOfStuff实际上发送了一个有效值;我还必须考虑断言可能已完全关闭的可能性。

这假设我是调试代码的人。不熟悉断言的人可能会看到这一点并假设(相当合理地)如果断言消息没有出现在日志中,则listOfStuff不会是问题所在。如果你第一次遇到assert是在野外,你会不会想到它可以完全关闭?毕竟,它不像有一个命令行选项可以让你禁用 try/catch 块。

所有这些都让我想到了我的问题(这一个问题,不是咆哮的借口!我保证!):

我错过了什么?

是否有一些细微差别使 Java 的实现assert比我认为的更有用?在某些情况下,从命令行启用/禁用它的能力真的非常有价值吗?当我设想在生产代码中使用它来代替像这样的语句时,我是否以某种方式误解了它if (listOfStuff == null) barf();

我只是觉得这里有一些重要的东西我没有得到。

*好吧,从技术上讲,它们实际上是默认关闭的;你必须不遗余力地打开它们。但是,您仍然可以将它们完全淘汰。


编辑: 启蒙请求,启蒙接受。

首先是调试工具的概念assert对我来说很有意义。

我仍然不同意在生产环境中应该禁用非平凡私有方法的输入检查,因为开发人员认为错误的输入是不可能的。以我的经验,成熟的生产代码是一个疯狂的、庞大的东西,多年来由具有不同技能水平的人针对快速变化的不同理智程度的需求而开发。即使错误的输入确实是不可能的,六个月后的一段草率的维护编码也可以改变这种情况。 gustafc 提供的链接(谢谢!)包括以下示例:

assert interval > 0 && interval <= 1000/MAX_REFRESH_RATE : interval;

在生产中禁用这样一个简单的检查让我觉得愚蠢的乐观。但是,这是编码理念上的差异,而不是损坏的功能。

另外,我绝对可以看到这样的东西的价值:

assert reallyExpensiveSanityCheck(someObject) : someObject;

感谢所有花时间帮助我理解此功能的人;这是非常赞赏。

4

12 回答 12

23

assert是合同设计的有用部分。在这种情况下,断言可用于:

  • 前置条件检查。
  • 后置条件检查。
  • 中间结果检查。
  • 类不变检查。

断言的评估成本可能很高(例如,类不变量,它必须在调用类的任何公共方法之前和之后保持)。断言通常只在调试版本和测试目的中需要;你断言不可能发生的事情——这些事情是有 bug 的同义词。断言根据自己的语义验证您的代码。

断言不是输入验证机制。当输入在生产环境中确实可能正确或错误时,即对于输入-输出层,使用其他方法,例如异常或旧的条件检查。

于 2010-05-03T15:52:03.670 回答
19

Java 的断言并不是真正用于参数验证的——它特别指出不要使用断言来代替古老的IllegalArgumentException断言(在 C-ish 语言中也不是这样使用的)。它们更多地用于内部验证,让您对代码进行假设,这在查看时并不明显。

至于关闭它们,你也可以在 C(++) 中这样做,只是如果有人有一个无断言的构建,他们就无法打开它。在 Java 中,您只需使用适当的 VM 参数重新启动应用程序。

于 2010-05-03T15:47:40.447 回答
4

我认为它是解释和设想断言用法的方式。

如果您真的想在实际生产代码中添加检查,为什么不直接使用 If 或任何其他条件语句?

那些已经存在于语言中的东西,断言的想法只是让开发人员添加断言,前提是他们并不真的期望这种情况会发生。

例如,检查一个对象是否为空,假设开发人员编写了一个私有方法并从他知道他传递一个非空对象的类中的两个地方调用它(这不是理想的示例,但可能适用于私有方法),而不是添加不必要的检查 if 因为从今天起您知道对象不可能为 null 但是如果明天有人使用 null 参数调用此方法,则在开发人员的单元测试中,由于存在断言而可能会被捕获,并且在最终代码中您仍然不这样做不需要 if 检查。

于 2010-05-03T15:46:38.110 回答
4

我见过的每种带有断言的语言都具有关闭它们的能力。当你写一个断言时,你应该想“这很愚蠢,在宇宙中这不可能是假的”——如果你认为它可能是假的,那应该是一个错误检查。如果出现严重错误,该断言只是为了在开发过程中为您提供帮助;当您构建生产代码时,您禁用它们以节省时间并避免(希望)多余的检查

于 2010-05-03T15:46:39.793 回答
4

断言旨在确保您确定您的代码实现的事情确实得到实现。在产品的开发阶段,它有助于调试,通常在发布代码时被省略。

我错过了什么?

您没有按照它们的本意使用断言。你说“检查输入参数的有效性”——这正是你不想用断言来验证的事情。

这个想法是,如果一个断言失败,你的代码中 100% 就有一个错误。断言通常用于在错误出现之前识别错误。

于 2010-05-03T15:50:45.203 回答
3

这听起来很对。断言只是一个对调试代码有用的工具——它们不应该一直打开,尤其是在生产代码中。

例如,在 C 或 C++ 中,断言在发布版本中被禁用。

于 2010-05-03T15:50:50.177 回答
3

对于代码维护者来说,断言确实是一个伟大而简洁的文档工具。

例如我可以写:

foo 应为非空且大于 0

或将其放入程序的主体:

assert foo != null;
assert foo.value > 0;

它们对于记录私有/包私有方法以表达原始程序员不变量非常有价值。

额外的好处是,当子系统开始表现不稳定时,您可以打开断言并立即添加额外的验证。

于 2010-05-03T16:12:14.973 回答
2

如果断言无法关闭,那么它们为什么还要存在。

如果要对输入执行有效性检查,可以轻松编写

if (foobar<=0) throw new BadFoobarException();

或弹出一个消息框或任何在上下文中有用的东西。

断言的全部意义在于它们可以打开以进行调试并关闭以进行生产。

于 2010-05-03T17:15:05.657 回答
1

这并不能直接回答您关于 的问题assert,但我建议您查看guava / google-collections中的Preconditions类。它允许您编写这样的好东西(使用静态导入):

// throw NPE if listOfStuff is null
this.listOfStuff = checkNotNull(listOfStuff);

// same, but the NPE will have "listOfStuff" as its message
this.listOfStuff = checkNotNull(listOfStuff, "listOfStuff"); 

看起来像这样的东西可能是您想要的(并且无法关闭)。

于 2010-05-03T15:49:54.247 回答
1

断言不是供最终用户看到的。它们是为程序员准备的,因此您可以确保代码在开发时正在做正确的事情。测试完成后,通常出于性能原因关闭断言。

如果你预计生产中会发生一些不好的事情,比如 listOfStuff 为空,那么要么你的代码没有经过足够的测试,要么你没有在让你的代码有它之前清理你的输入。无论哪种方式,“如果(坏东西){抛出异常}”会更好。断言用于测试/开发时间,而不是用于生产。

于 2010-05-03T15:50:25.203 回答
1

assert如果您愿意在断言失败时向最终用户支付 1 美元,请使用 an 。

断言失败应该表明程序中存在设计错误。

一个断言表明我以我知道并保证指定谓词始终成立的方式设计了程序。

断言对我的代码的读者很有用,因为他们看到(1)我愿意在该属性上设置一些钱;(2) 在以前的执行和测试用例中,该属性确实成立。

我的赌注是假设我的代码的客户遵守规则,并遵守他和我商定的合同。该合同可以是宽容的(允许所有输入值并检查其有效性)或要求(客户和我同意他永远不会提供某些输入值[描述为先决条件],并且他不希望我检查这些值一遍又一遍地)。如果客户遵守规则,而我的主张仍然失败,客户有权获得一些赔偿。

于 2011-07-17T15:46:27.227 回答
0

断言用于指示代码中可能可恢复的问题,或作为调试的帮助。对于更严重的错误,您应该使用更具破坏性的机制,例如停止程序。

它们还可用于在应用程序稍后在调试和测试场景中失败之前捕获不可恢复的错误,以帮助您缩小问题范围。造成这种情况的部分原因是完整性检查不会降低生产中经过良好测试的代码的性能。

此外,在某些情况下,例如资源泄漏,这种情况可能并不理想,但停止程序的后果比继续执行的后果更糟。

于 2010-05-03T15:45:04.400 回答