使用断言的一些(反)模式(Java 和其他)

发布于 2024-07-10 13:05:39 字数 2928 浏览 14 评论 0原文

最后,我有一个问题想在 Stack Overflow 上问! :-)

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

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

我对断言的使用还是有点陌生​​,虽然我已经阅读过关于该主题的几篇文章。 然而,我看到的一些用法让我感到困惑,损害了我的常识...... ^_^ 所以我想我应该问一些问题,看看我想要纠正的东西是否正确,或者它是否违背了惯例。 我很啰嗦,所以我加粗问题,以供那些喜欢浏览内容的人使用。

作为参考,我在 SO 中搜索了 assert 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() 形式来自 DEBUG.ASSERT() 调用的转换。 我不喜欢额外的括号,因为断言是一种语言构造,而不是(这里不再是)函数调用。 我也不喜欢 return (foo); :-)
接下来,断言在这里不测试不变量,而是用作防止错误值的防范措施。 但据我了解,它们在这里毫无用处:断言将抛出异常,甚至不会用伴随字符串记录,并且只有在启用断言的情况下。 因此,如果我们有 -ea 选项,我们只会抛出一个断言,而不是常规的 NullPointerException 断言。 这看起来并不是一个最重要的优势,因为无论如何我们都会在最高级别捕获未经检查的异常。
我是否正确地假设我们可以摆脱它们并接受它(即让Java引发这种未检查的异常)?(或者,当然,如果可能的话,测试空值,这是在其他中完成的)的地方)。

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

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

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

最后,我错过了还有其他有用的用例或烦人的陷阱吗? (大概!)

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

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.

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.

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.

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

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();

(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).

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.

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?

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!)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

如日中天 2024-07-17 13:05:39

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

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

断言是可执行的假设。 我使用断言来表达我对程序当前状态的信念。 例如,诸如“我假设此处 n 为正”或“我假设列表此处只有一个元素”之类的内容。

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).

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.

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".

浅紫色的梦幻 2024-07-17 13:05:39

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

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


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


编辑(2):如果您非常确定某些代码块永远不会到达,您可以编写

throw new AssertionError("You dead");

而不是

assert false:"I am lucky";

例如,如果您在可变对象上覆盖“equals(Object)”,则如果您相信的话,请使用 AssertionError 覆盖 hashCode()永远不会成为关键。 一些书籍中建议采用这种做法。 我不会损害性能(因为它永远不应该达到)。

I use assert, not only for parameter validation, but also used for verifying Threads.
Every time I do swing, I write assert in almost every method to mark "I should only be execute in worker thread/AWTThread". (I think Sun should do it for us.) Because of the Swing threading model, it MAY NOT fail (and randomly fail) if you access swing api from non-UI thread. It is quite difficult to find out all these problem without assert.

Another example I can imagination is to check JAR enclosed resource. You can have english exception rather then NPE.


EDIT:
Another example; object lock checking. If I know that I am going to use nested synchronized block, or when I am going to fix a deadlock, I use Thread.holdLock(Object) to ensure I won't get the locks in reverse order.


EDIT(2): If you are quite sure some code block should never be reach, you may write

throw new AssertionError("You dead");

rather then

assert false:"I am lucky";

For example, if you override "equals(Object)" on a mutable object, override hashCode() with AssertionError if you believe it will never be the key. This practice is suggested in some books. I won't hurt performance (as it should never reach).

开始看清了 2024-07-17 13:05:39

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

所以,我的建议是跳过断言。 不要坚持额外的空指针检查,语言会为你做这件事。 但是,如果指针可能暂时不会被取消引用,则预先进行空检查是一个好主意。 另外,始终对于“永远”不应该发生的情况(最后的 if 分支或默认 switch 情况)使用真正的异常,不要使用“assert false”。 如果你使用断言,有人可能会关闭它,如果这种情况真的发生,事情会变得非常混乱。

you have touched on many of the reasons why i think asserts should be avoided in general. unless you are working with a codebase where assert usage has very strict guidelines, you very quickly get into a situation where you cannot ever turn the assertions off, in which case you might as well just be using normal logic tests.

so, my recommendation is skip the assertions. don't stick in extra null-pointer checks where the language will do it for you. however, if the pointer may not be dereferenced for a while, up-front null checking is a good idea. also, always use real exceptions for cases which should "never" happen (the final if branch or the default switch case), don't use "assert false". if you use an assertion, there's a chance someone could turn it off, and if the situation actually happens, things will get really confused.

ぺ禁宫浮华殁 2024-07-17 13:05:39

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

断言应该在非公共方法中使用来检查后置条件和可能的前置条件。 例如:

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

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

I recommend checking parameters in public (API) methods and throwing IllegalArgumentException if the params aren't valid. No asserts here as an API user requires to get a proper error (message).

Asserts should be used in non-public methods to check post-conditions and possibly pre-conditions. For example:

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

Often using a clever error handling strategy asserts can be circumvented.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文