使用断言的一些(反)模式(Java等)

2022-09-04 20:32:58

最后,我有一个关于Stack Overflow的问题要问!:-)

主要目标是Java,但我相信它主要是与语言无关的:如果你没有原生断言,你可以随时模拟它。

我为一家公司工作,该公司销售一套用Java编写的软件。代码很旧,至少可以追溯到Java 1.3,在某些地方,它显示...这是一个很大的代码库,大约200万行,所以我们不能一次重构它。
最近,我们将最新版本从 Java 1.4 语法和 JVM 切换到 Java 1.6,保守地使用了一些新功能,例如(我们曾经使用过 DEBUG。ASSERT宏 - 我知道在1.4中已经引入,但我们以前没有使用它),泛型(只有类型化集合),foreach循环,枚举等。assertassert

我对断言的使用仍然有点绿色,尽管我已经阅读了几篇关于这个主题的文章。然而,我看到的一些用法让我感到困惑,伤害了我的常识......^_^所以我想我应该问一些问题,看看我想要纠正东西是否正确,或者它是否违背了惯例。我很冗长,所以我加粗了问题,对于那些喜欢略读的东西。

作为参考,我在SO中搜索了断言java,发现了一些有趣的线程,但显然没有确切的重复。

首先,引发我今天问题的主要问题:

SubDocument aSubDoc = documents.GetAt( i );
assert( aSubDoc != null );
if ( aSubDoc.GetType() == GIS_DOC )
{
   continue;
}
assert( aSubDoc.GetDoc() != null );
ContentsInfo ci = (ContentsInfo) aSubDoc.GetDoc();

(是的,我们使用MS的C / C++样式/代码约定。我甚至喜欢它(来自相同的背景)!所以起诉我们。
首先,形式来自呼叫的转换。我不喜欢多余的括号,因为assert是一种语言结构,而不是(这里不再是)函数调用。我也不喜欢:-)
接下来,断言不在这里测试不变量,而是用作防止不良值的防范措施。但据我所知,它们在这里是无用的:断言将引发异常,甚至没有用伴随字符串记录,并且只有在启用断言时。因此,如果我们有选择,我们只需要一个抛出的断言,而不是常规的NullPointerException。这看起来并不是一个最重要的优势,因为我们无论如何都会在最高级别捕获不受控制的异常。
我假设我们可以摆脱它们并忍受它(即让Java提出这种未被忽略的异常)是对的吗?(或者,当然,如果可能的话,测试空值,这是在其他地方完成的)。assert()DEBUG.ASSERT()return (foo);-ea

旁注:如果我必须在上面的片段中断言,我会针对ci值而不是getter这样做:即使大多数getter都是优化/内联的,我们也不能确定,所以我们应该避免调用它两次。

有人在最后引用的线程中告诉公共方法应该使用针对参数值的测试(公共API的使用),私有方法应该依赖于断言。好的建议。
现在,这两种方法都必须检查另一个数据源:外部输入。即。例如,来自用户、数据库、某些文件或网络的数据。
在我们的代码中,我看到针对这些值的断言。我总是将这些更改为实际测试,因此即使禁用断言,它们也会起作用:这些不是不变的,必须正确处理。
我只看到一个可能的异常,其中输入应该是常量,例如,一个数据库表充满了关系中使用的常量:如果这个表被更改但相应的代码没有更新,程序就会中断。
您是否看到其他例外情况?

我看到的另一个相对频繁的使用,这似乎是可以的:在开关的默认值中,或者在一系列测试结束时所有可能的值(这些情况可以追溯到我们使用枚举之前!),对我来说通常有一个
看起来合法的(这些情况不应该在生产中发生),你怎么看?(除了“没有开关, 使用OO“建议,这些建议在这里无关紧要)。else ifassert false : "Unexpected value for stuff: " + stuff;

最后,我在这里错过了任何其他有用的用例或烦人的陷阱吗?(可能!


答案 1

头号规则是避免断言中的副作用。换句话说,代码在关闭断言时的行为应该与打开断言且不失败时的行为相同(显然,失败的断言会改变行为,因为它们会引发错误)。

第二条规则是不要将断言用于基本检查。它们可以关闭(或者更准确地说,不打开)。对于非私有方法的参数检查,请使用 IllegalArgumentException。

断言是可执行的假设。我使用断言来陈述我对程序当前状态的信念。例如,“我假设n在这里是正数”,或者“我假设列表在这里正好有一个元素”。


答案 2

我不仅使用 ,不仅用于参数验证,还用于验证线程。每次我摆动时,我都会在几乎每个方法中都写出assers来标记“我应该只在worker thread/AWTThread中执行”。(我认为Sun应该为我们做这件事。由于 Swing 线程模型,如果从非 UI 线程访问 Swing api,它可能不会失败(并且随机失败)。如果不断言,很难找出所有这些问题。assert

我能想象到另一个例子是检查JAR封装的资源。你可以有英语例外,而不是NPE。


编辑:另一个例子;对象锁定检查。如果我知道我将使用嵌套的同步块,或者当我要修复死锁时,我会使用Thread.holdLock(Object)来确保我不会以相反的顺序获得锁。


编辑(2):如果你非常确定某些代码块不应该被访问, 你可以写

throw new AssertionError("You dead");

而不是

assert false:"I am lucky";

例如,如果您在可变对象上覆盖“equals(Object)”,如果您认为它永远不会是键,请使用AsseritionError覆盖hashCode()。一些书中建议这种做法。我不会损害性能(因为它永远不应该达到)。


推荐