使用断言的一些(反)模式(Java 和其他) [英] Some (anti-)patterns on using assert (Java, and others)

查看:21
本文介绍了使用断言的一些(反)模式(Java 和其他)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Finally, I have a question to ask on Stack Overflow! :-)

主要目标是 Java,但我相信它主要是语言不可知的:如果您没有本机断言,您可以随时模拟它.

The main target is for Java but I believe it is mostly language agnostic: if you don't have native assert, you can always simulate it.

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

I work for a company selling a suite of softwares written in Java. The code is old, dating back to Java 1.3 at least, and at some places, it shows... That's a large code base, some 2 millions of lines, so we can't refactor it all at once.
Recently, we switched the latest versions from Java 1.4 syntax and JVM to Java 1.6, making conservative use of some new features like assert (we used to use a DEBUG.ASSERT macro -- I know assert has been introduced in 1.4 but we didn't used it before), generics (only typed collections), foreach loop, enums, etc.

虽然我已经阅读了几篇关于该主题的文章,但我对 assert 的使用仍然有点陌生.然而,我看到的一些用法让我感到困惑,伤害了我的常识...... ^_^ 所以我想我应该问一些问题,看看我想要纠正的东西是否正确,或者它是否违反了惯例.我很罗嗦,所以我加粗了问题,对于那些喜欢略读内容的人.

I am still a bit green about the use of assert, although I have read a couple of articles on the topic. Yet, some usages I see leave me perplex, hurting my common sense... ^_^ So I thought I should ask some questions, to see if I am right to want to correct stuff, or if it goes against common practices. I am wordy, so I bolded the questions, for those liking to skim stuff.

作为参考,我在 SO 中搜索了 assert java 并发现了一些有趣的线程,但显然没有完全重复.

For reference, I have searched assert java in SO and found some interesting threads, but apparently no exact duplicate.

  • How to avoid "!= null" statements in java? and How much null checking is enough? are quite relevant, because lot of asserts we have just check if variable is null. At some places in our code, there are usages of the null object (eg. returning new String[0]) but not always. We have to live with that, at least for maintenance of legacy code.
  • Some good answers also in Java assertions underused.
  • Oh, and SO indicates with reason that When should I use Debug.Assert()? question is related too (nice feature to reduce duplicates!).

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

First, main issue, which triggered my question today:

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() 形式来自 DEBUG.ASSERT() 调用的转换.我不喜欢额外的括号,因为 assert 是一种语言结构,而不是(不再是)函数调用.我也不喜欢 return (foo); :-)
接下来,断言不会在这里测试不变量,而是用作对错误值的保护.但据我所知,它们在这里没用:断言将抛出异常,甚至没有用伴随字符串记录,并且仅当断言被启用时.所以如果我们有 -ea 选项,我们只会抛出一个断言而不是常规的 NullPointerException .这看起来不是最大的优势,因为无论如何我们都会在最高级别捕获未经检查的异常.
我是否认为我们可以摆脱它们并接受它(即让 Java 引发这种未经检查的异常)?(或者,当然,如果可能的话,测试空值,这是在其他地方).

(Yes, we use MS' C/C++ style/code conventions. And I even like it (coming from same background)! So sue us.)
First, the assert() form comes from conversion of DEBUG.ASSERT() calls. I dislike the extra parentheses, since assert is a language construct, not (no longer, here) a function call. I dislike also return (foo); :-)
Next, the asserts don't test here for invariants, they are rather used as guards against bad values. But as I understand it, they are useless here: the assert will throw an exception, not even documented with a companion string, and only if assertions are enabled. So if we have -ea option, we just have an assertion thrown instead of the regular NullPointerException one. That doesn't look like a paramount advantage, since we catch unchecked exceptions at highest level anyway.
Am I right supposing we can get rid of them and live with that (ie. let Java raise such unckecked exception)? (or, of course, test against null value if likely, which is done in other places).

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

Side note: should I have to assert in the above snippet, I would do that against ci value, not against the getter: even if most getters are optimized/inlined, we cannot be sure, so we should avoid calling it twice.

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

Somebody told, in the last referenced thread, that public methods should use tests against values of parameters (usage of the public API) and private methods should rely on asserts instead. Good advice.
Now, both kinds of methods must check another source of data: external input. Ie. data coming from user, from a database, from some file or from the network, for example.
In our code, I see asserts against these values. I always change these to real test, so they act even with assertions disabled: these are not invariants and must be properly handled.
I see only one possible exception, where input is supposed constant, for example a database table filled with constants used in relations: program would break if this table is changed but corresponding code wasn't updated.
Do you see other exceptions?

我看到的另一个相对频繁的使用,这看起来不错:在开关的默认值中,或者在一系列 else if 测试所有可能的值的末尾(这些情况可以追溯到我们使用之前枚举!),通常有一个 assert false : "Unexpected value for stuff:" + stuff;
对我来说看起来是合理的(这些情况不应该在生产中发生),你怎么看?(除了不切换,使用 OO"的建议,这里不相关).

Another relatively frequent use I see, which seems OK: in the default of a switch, or at the end of a series of else if testing all possible values (these cases date back before our use of enums!), there is often an assert false : "Unexpected value for stuff: " + stuff;
Looks legitimate for me (these cases shouldn't happen in production), what do you think? (beyond the "no switch, use OO" advices which are irrelevant here).

最后,还有其他有用的用例或烦人的问题我在这里错过了吗?(可能!)

And finally, are there any other useful use cases or annoying gotchas I missed here? (probably!)

推荐答案

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

The number one rule is to avoid side-effects in assertions. In other words, the code should behave identically with assertions turned off as it does when assertions are turned on and not failing (obviously assertions that fail are going to alter the behaviour because they will raise an error).

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

The number two rule is not to use assertions for essential checks. They can be turned off (or, more correctly, not turned on). For parameter-checking of non-private methods use IllegalArgumentException.

断言是可执行的假设.我使用断言来陈述我对程序当前状态的看法.例如,我假设这里的 n 是正数",或我假设列表中正好有一个元素".

Assertions are executable assumptions. I use assertions to state my beliefs about the current state of the program. For example, things like "I assume that n is positive here", or "I assume that the list has precisely one element here".

这篇关于使用断言的一些(反)模式(Java 和其他)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆