Some (anti-)patterns on using assert (Java, and others)
Asked Answered
R

4

14

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

Rounder answered 15/12, 2008 at 15:42 Comment(4)
Why a last question? Are you going somewhere?Margitmargo
OK, I hesitated between at last and at least, and wrongly corrected to the first option... :-) Thanks, I correct this.Rounder
"at least" makes even less senseSchoof
Doh! ^_^ Me French don't speak English beautifully. :-DRounder
S
11

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

Schoof answered 16/12, 2008 at 0:20 Comment(0)
V
3

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

Vitrain answered 15/12, 2008 at 16:16 Comment(1)
in your last example, would it not be better to throw UnsupportedOperationException from hashCode()?Spoilsman
M
2

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.

Mcclellan answered 15/12, 2008 at 15:58 Comment(0)
S
2

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.

Shredding answered 15/12, 2008 at 15:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.