Java's new assertion mechanism, a welcome addition to the
language now available in version 1.4, allows programmers to increase
the robustness of their code by sprinkling it liberally with assert
statements. The new assertion feature is easy to use, but any
language feature, no matter how simple, can be used well or poorly.
Here I'll explain how to use Java's assertion facility, and how not
to misuse it.
Using assertions couldn't be easier. Anywhere you can put a
statement in Java, you can now write
assert boolExpr;
where boolExpr is any Boolean expression. If the expression is true,
execution continues as if nothing happened. If it's false, an
exception is thrown. You can disable assertions at runtime if you
wish, effectively removing them from your code.
Why Use Assertions?
Assertions are a cheap and easy way of building confidence in
your code. When you write an assertion, you're enabling the machine
to check your beliefs about your program. You write the assertion
thinking that it's true, but as you well know, not all of your
beliefs about your code are true - if they were, you wouldn't have
any bugs. Assertions can help uncover bugs early on.
For example, here's a bit of code that makes a choice based
on the remainder of dividing n by 3:
if (n % 3 == 0) {
...
} else if (n % 3 == 1) {
...
} else {
assert n % 3 == 2;
...
}
Now obviously, n % 3 == 2 in the final else of this
statement, since the remainder when you divide a number by 3 is
either 0, 1, or 2. So there's no need to do an explicit test. But
these new assert statements are easy to write and you can always
disable them, so you add one just for kicks. That will turn out to
have been a wise choice when the code is run with a negative value of
n. The Java % operator, like the mod operator of most programming
languages, gives negative results when its left operand is negative:
-5 % 3 is -2, not 2. The assertion will fail, the program will stop,
and you can easily correct both the code and your false belief about
how % works.
Here's another example from some recent code of my own:
TransactionEntry te = (TransactionEntry)
assoc.getEntry(key);
if (te == null) {
te = new TransactionEntry(key, dur);
assoc.put(key, te);
} else {
assert te.getState() == te.REMOVED;
te.recreate(dur, session);
}
What this code is about doesn't matter. As you can tell just
from the control flow, I firmly believe that if assoc.getEntry(key) returns a nonnull TransactionEntry, then that
TransactionEntry must be in the REMOVED state. This is a desired
property of my system and is enforced elsewhere (or so I believe),
but is far from obvious in this piece of code. The assertion both
documents my belief and confirms it at runtime, making me a little
more confident that my system is correct.
The Details
Having seen why assertions are a good idea, let's look at
Java's assertion facility in more detail.
The Java language has a new statement, the assert statement,
which takes one of two forms. The simpler form is the one introduced
earlier:
assert boolExpr;
If the expression evaluates to true, nothing happens, but if
it evaluates to false, an AssertionError is thrown. The new class
AssertionError is a subclass of Error.
If you want to include an additional information string with
the AssertionError, provide it after the Boolean expression:
assert a.length != b.length:
"array lengths not equal";
The second expression can in fact be of any type, and will be
converted to a string in the usual way.
Assertions are disabled by default in Sun's JVM. You can
selectively enable them by class loader, package, or class by calling
some new methods of the ClassLoader class, or more commonly via
command-line arguments when you invoke the Java Virtual Machine. The
command-line arguments aren't part of the spec, but in Sun's
implementation you would use the -ea and -da options to enable and
disable assertions, respectively. For example, to enable all
assertions except for those in the com.astrel.util package, you would
write
java -ea -da:com.astrel.util MyApp
If you then wanted to reenable assertions for the Heap class
within com.astrel.util, you could write
java -ea -da:com.astrel.util \
-ea:com.astrel.util.Heap MyApp
Although the ClassLoader methods can be invoked at any time,
they'll only affect classes loaded after the call. In other words,
the status of a class's assertions - enabled or disabled - is
determined once and for all when the class is loaded.
No method will tell you whether the current class has
assertions enabled, but you can easily determine this with the
following code:
boolean enabled = false;
assert enabled = true;
Here the assert statement is used only for its side effect.
If assertions are enabled, the Boolean will be set to true; otherwise
it will remain false.
That's all there is to using assertions. Now some guidelines
on using them well.
Tips on Using Assertions
- Disable assertions for deployment.
Some people say that assertions should never be disabled.
Disabling assertions for deployment, as the saying goes, is like
throwing the lifeboats overboard just before the ship leaves port. I
agree in principle, but disagree on a technicality. The main benefit
of Java assertions is the ability to disable them. If you don't plan
on disabling them, you shouldn't be using them. I do believe, and
fervently, that your code should perform as much internal checking as
is feasible, and that these checks should not be removed. But such
checks shouldn't be assertions, in the narrow technical sense of
being written with Java's assert statement.
For the remainder of this article, I'll use the word "check"
to mean any self-validating bit of code that shouldn't be disabled.
- Make it possible to reenable assertions at any time.
Although your deployed program will run with assertions
disabled, it should be possible even for the end user to reenable
them when necessary by restarting the application with an appropriate
flag. The assertion facility is designed so that assertions can
remain in the deployed class file and yet still have no impact on
running time - disabled assertions can be removed by the class loader
or JIT compiler. (That's why you can't change the assertion status of
a class dynamically.) Thus your application need only provide access
to the appropriate JVM command-line option in order to enable
assertions in the field. By providing this ability, you give yourself
another tool for catching bugs in the wild, under conditions that you
may not be able to duplicate on your own.
- Don't use side effects in assertions.
Since assertions will be disabled in deployed code, they
should not change the state of the system - otherwise, the system
would behave differently when deployed. (It may behave differently
anyway, because disabling an assertion may affect the timing of
multithreaded programs, but hopefully your design is robust to
withstand such minor timing changes.) There are a couple of
techniques that violate this rule: one, which detects whether
assertions are enabled, we saw above; another I'll describe later
when I discuss postconditions.
- Assertions should be expensive.
In other words, either the Boolean expression should take a
long time to execute, or the cumulative time of executing the
assertion in typical runs of your program should be large. If the
assertion doesn't slow you down, why bother disabling it? Or to put
it another way: write cheap tests as checks, not assertions. Those
checks will help you catch bugs in the deployed system without
impacting performance. You can't beat that.
Of course, if assertions as a whole are too expensive, your
program will run too slowly to test, and you'll test less frequently.
In this way, assertions can actually decrease the robustness of your
code. Here is where the ability to selectively disable assertions
comes in handy. When a class or package is first coming up to speed,
enable assertions in it to gain confidence in its correctness. When
the program becomes too slow to test frequently, disable assertions
in the older, well-tested parts of the system.
- If you can recover from it, don't assert it.
Assertions throw an error instead of an exception because
their purpose is to crash your program. (The whole reason behind
having separate Error and Exception classes is that Errors indicate
faults from which you shouldn't try to recover.) So catching an
AssertionError and trying to continue is a sure sign that you should
be doing a check, not an assertion. It's reasonable to catch an
AssertionError, but only to log or otherwise process it before your
program exits. In other words, any such catch should end by
rethrowing the error.
- Don't mention assertions in documentation.
An assertion is an implementation choice, like the name of a
local variable; whether it's enabled, disabled, or exists at all
should in no way affect the contract of a method. (But feel free to
boast to your co-workers that you've made your code more robust by
"asserting the heck out of it.")
- Don't use assertions for argument checking.
If your method's contract claims it will check for null
arguments, then you should do just that - check, not assert. There
are two problems with using an assertion. First, the assertion will
be disabled in production runs of the program, so your method won't
be up to spec. (That really is like throwing the lifeboats
overboard.) Second, the assert statement throws the wrong kind of
thing, an AssertionError. If an argument is null, you should be
throwing a NullPointerException; if it's generally invalid, an
IllegalArgumentException; and so on.
If you're tempted to use assert anyway because you can write the brief
assert arg != null;
in place of the verbose
if (arg == null)
throw new NullPointerException();
then how about writing a helper method? In fact, let's postulate a
class full of them:
Check.isNotNull(arg);
will throw a NullPointerException, while
Check.legalArg(arg.length > 0);
would throw IllegalArgumentException. (Two-argument versions of these
methods would take message strings to be included in the exception,
just like the assert statement.) Concerned about the overhead of an
extra method call? Don't be: these Check methods are static and
small, which means that a good JIT compiler could easily inline them.
(On my system, running JDK 1.3 with the HotSpot Client VM, there's no
measurable time difference between calling a Check method and writing
the same code inline.)
All that being said, if checking an argument is expensive,
consider using an assertion instead (and don't claim in the
documentation that the argument is checked). Another time to consider
an assertion over a check is when you control all calls to the method
- for example, when the method is private. Since you can guarantee
that all calls to the method are correct, you don't need a check, but
an assertion couldn't hurt.
- Use assertions for preconditions and postconditions.
A precondition is something your method assumes to be true on
entry. Restrictions on arguments are one kind of precondition, but
not the only kind: your method might require that other parts of the
system be in a certain state before it can validly proceed. Once
you've identified a precondition and decided that you don't want to
turn it into a check (presumably because it's too expensive),
asserting it is a good idea.
One kind of precondition is a class invariant - a statement
about a class or its instances that should be true before and after
each of the class's methods. For example, say you are writing a Heap
class that implements a binary heap - a binary tree with the property
that the value stored in each node is no less that the values stored
in its children. This property is a class invariant. (Heaps are
useful for building priority queues, among other things.)
At the beginning of the method that adds a new item to the
heap, you expect that the heap property is true. Indeed, the standard
algorithm for adding an item to a binary heap won't work correctly
unless it's true. So the heap property is a precondition of the add
method. It would be wise to write an isValid method in the Heap class
that checks the property, and place
assert isValid();
at the start of the add method.
Similarly, a postcondition is something that should hold when
the method is finished. One seldom checks postconditions, but
asserting them makes sense. Class invariants are postconditions as
well as preconditions, so it would be a good idea to assert the heap
property at the end of each Heap method as well as at the beginning.
If a method changes its parameters, chances are the method's
postcondition will need access to the original parameter values. You
can write the postcondition by storing copies of the original values
before they're modified. For example, the postcondition of any sort
routine is that the input array be in sorted order, and that it
consist of the same elements as before the sort. While the first
condition does not require the original array, the second one does.
Copying parameters is expensive, so it should happen only
when assertions are enabled. You can achieve that by using a side
effect in an assertion:
void sort(int[] a) {,
int[] old_a = null;
assert (old_a = (int[]) a.clone()) != null;
// do the sort
assert inSortedOrder(a) &&
hasSameElements(a, old_a);
}
The inequality check in the first assertion is there only to
produce a Boolean that is always true; the assertion's real job is to
clone the array. If assertions are disabled, no copying will occur.
- Use assertions for those hard-to-reach places in the middle.
Assertions are great for places in the middle of methods when
part of the job is done. An assertion in the middle of a method will
catch problems sooner than one at the end, and may have less work to
do as well. For instance, part of the process of adding a new item to
a heap involves repeatedly comparing a node's value in the tree with
those of its two children, and then possibly swapping the node with
its larger child. Placing an assertion after this piece of code, to
the effect that the heap property still holds for the parent node and
its children is cheaper than testing the whole heap for validity at
the end, will catch any problems in the code right where they happen,
and acts as useful documentation too.
- Don't use assertions to flag "unreachable" code.
We often write switch statements whose default case we know
cannot happen, and sometimes the compiler forces us to catch
exceptions that we know cannot be thrown. Some suggest using
assertions in these and other allegedly impossible-to-reach places,
but I disagree. Checks are preferred: they will not be disabled and,
by assumption, will not affect performance, since they will never
run. Simply writing a throw statement would be adequate (although
exactly which class of exception should be thrown is not clear), or
we could postulate a Check.impossible() method that throws the
exception and write:
switch (x) {
case 1: ...; break;
case 2: ...; break;
default: Check.impossible();
}
- Use assertions as executable documentation.
Occasionally you may get the impulse to write a brief comment
in the middle of your code to the effect that, for example:
// at this point, x is greater than y
Instead of commenting it, assert it:
assert x > y;
The assertion is as valuable as the comment to the human
reader, and it's machine-checkable as well.
- Treat assertions as tests.
Assertions are the first line of testing - they are subunit
tests. Once you realize this, many of the above guidelines fall out
almost automatically. You disable assertions in production for the
same reasons that you don't ship your test code. Asserting the
postcondition of a public method is much like writing a unit test for
that method. And the importance of midmethod assertions is clear:
they're located in places where you can't otherwise test.
- Assert early and often.
Write assertions as you write your code - or even before, if
you're a devotee of Extreme Programming. That is when the logic's
details are freshest in your mind. And feel free to write plenty of
them, since you can always disable them.
Conclusion
Although assertions are a small addition to Java - just one
new statement - they can have an impact out of proportion to their
size. Like a well-written test, a well-placed assertion will catch
bugs early in the development process, when it's cheapest to fix
them. They serve as helpful documentation to readers of your code.
And since assertions can be disabled at runtime, your program's
performance need not suffer. So don't hesitate to use them.
Assertions are sure to improve the quality of your code.
Author Bio
Jonathan Amsterdam is a senior consulting engineer at DataSynapse,
Inc. Formerly, he was president and founder of Astrel, Inc., a Java
training and consulting firm. He's also an adjunct professor of
computer science at New York University.
amsterdam@cs.nyu.edu
All Rights Reserved
Copyright © 2004 SYS-CON Media, Inc.
E-mail: info@sys-con.com
Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.
|