使用assert(Java和其他)时的一些(反)模式

使用assert(Java和其他)时的一些(反)模式,java,language-agnostic,assert,Java,Language Agnostic,Assert,最后,我要问一个关于堆栈溢出的问题!:-) 主要的目标是Java,但我相信它主要是与语言无关的:如果您没有本机断言,您总是可以模拟它 我在一家销售用Java编写的软件套件的公司工作。代码很旧,至少可以追溯到Java1.3,在某些地方,它显示。。。这是一个庞大的代码库,大约有200万行,因此我们不能一次全部重构。 最近,我们将最新版本从Java 1.4语法和JVM切换到Java 1.6,保守地使用了一些新功能,如assert(我们以前使用DEBUG.assert宏——我知道assert在1.4中已

最后,我要问一个关于堆栈溢出的问题!:-)

主要的目标是Java,但我相信它主要是与语言无关的:如果您没有本机断言,您总是可以模拟它

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

虽然我已经读了几篇关于assert的文章,但我对assert的使用还是有点生疏。然而,我看到的一些用法让我困惑,伤害了我的常识…^所以我想我应该问一些问题,看看我想纠正的想法是否正确,或者这是否违背了常规做法。我很罗嗦,所以我给那些喜欢略读东西的人提了一些粗体的问题

作为参考,我在SO中搜索了AssertJava,发现了一些有趣的线程,但显然没有完全相同的

  • 和非常相关,因为我们有很多断言只是检查变量是否为null。在代码中的某些地方,有空对象的用法(例如返回
    新字符串[0]
    ),但并不总是如此。我们必须接受这一点,至少是为了维护遗留代码
  • 一些很好的答案也在这里
  • 哦,所以有理由表明这个问题也是相关的(减少重复的好特性!)
首先是引发我今天提问的主要问题:

SubDocument aSubDoc = documents.GetAt( i );
assert( aSubDoc != null );
if ( aSubDoc.GetType() == GIS_DOC )
{
   continue;
}
assert( aSubDoc.GetDoc() != null );
ContentsInfo ci = (ContentsInfo) aSubDoc.GetDoc();
(是的,我们使用微软的C/C++风格/代码约定。我甚至喜欢它(来自相同的背景)!所以起诉我们。)
首先,
assert()
表单来自
DEBUG.assert()
调用的转换。我不喜欢额外的括号,因为assert是一种语言构造,而不是(这里不再是)函数调用。我也不喜欢
return(foo):-)
接下来,断言在这里不测试不变量,而是用来防止坏值。但据我所知,它们在这里是无用的:断言将抛出一个异常,甚至没有附带字符串记录,并且只有在启用断言的情况下。因此,如果我们有
-ea
选项,我们只会抛出一个断言,而不是常规的NullPointerException。这看起来并不是最重要的优势,因为我们在最高级别捕获未经检查的异常。
我认为我们可以摆脱它们并接受它(即,让Java引发这种未加密的异常)是对的吗?(或者,当然,如果可能的话,测试空值,这是在其他地方完成的)

旁注:如果我必须在上面的代码段中断言,我会针对ci值而不是getter进行断言:即使大多数getter都经过优化/内联,我们也不能确定,因此应该避免调用它两次

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

我看到的另一个相对频繁的使用,似乎还可以:在一个开关的默认值,或者在一系列
的末尾,如果
测试所有可能的值(这些情况可以追溯到我们使用枚举之前!),通常会出现
断言false:“stuff的意外值:+stuff
在我看来是合理的(这些情况不应该发生在生产中),你怎么看?(除了“不切换,使用OO”的建议之外,这些建议在这里是不相关的)


最后,还有其他有用的用例或恼人的问题吗?(可能吧!)

您已经提到了我认为通常应该避免断言的许多原因。除非您使用的代码库中断言的使用有非常严格的指导原则,否则您很快就会陷入无法关闭断言的情况,在这种情况下,您最好只使用正常的逻辑测试


因此,我的建议是跳过断言。不要把多余的空指针检查放在语言可以帮你做的地方。但是,如果指针可能有一段时间没有被解除引用,那么预先检查空值是一个好主意。另外,对于“从不”发生的情况(最终的if分支或默认开关情况),始终使用真实异常,不要使用“assert false”。如果使用断言,可能会有人将其关闭,如果实际发生这种情况,事情会变得非常混乱。

我建议检查公共(API)方法中的参数,如果参数无效,则抛出IllegalArgumentException。这里没有断言,因为API用户需要获取正确的错误(消息)

资产应以非公开的方式使用,以检查后条件和可能的先决条件。例如:

List returnListOfSize(int size) {
    // complex list creation
    assert list.size == size;
}

通常使用巧妙的错误处理策略断言是可以避免的。

我使用
assert
,不仅用于参数验证,还用于验证
throw new AssertionError("You dead");
assert false:"I am lucky";