|
|
|
|
With the recent release of J2SE 1.4, the Java language now includes a simple
assertion facility. What is an assertion? Simply put, an assertion is a
boolean expression that a developer declares to be true during
program execution. The new assertion facility is simple to use and can greatly
increase software reliability by allowing developers to easily declare
acceptable program behavior to help detect bugs early in the development
process.
Assertions are declared by using a new Java language keyword,
assert. An assert statement takes one of two forms.
assert expression1
;
assert expression1
:
expression2
In both forms, expression1
is the boolean expression
that the developer declares to be true during program execution.
If expression1
is not a boolean expression a
compile-time error will occur. In the second form
expression2
is an expression that will be evaluated into
a String expression and passed as a message to the assertion
facility. Here are a few examples of using the assert keyword:
assert size() > 0;
assert ref != null;
assert age > 0.0 && age < 130.00: "Invalid age";
If assertions are enabled and expression1
evaluates to
true, program execution will continue as usual. If it's
false an AssertionError will be thrown. If
expression2
is specified in the statement it will be
passed as the message to the AssertionError. Assertions may be
disabled at runtime, effectively removing any performance hit due to their use
at runtime.
AssertionError is a subclass of Error rather than
RuntimeException, because according to
JSR 41, it will "discourage
programmers from attempting to recover from assertion failures". Also, by not
extending from RuntimeException it is not necessary for a
developer to document the circumstances that may cause a method to throw an
AssertionError with the javadoc @throws tag, since
this is typically an implementation detail.
Once assert statements have been added to your code, it will no longer compile
under any pre J2SE 1.4 compiler. By default a J2SE 1.4 compiler will not
compile code that contains assert statements. To compile a source file that
contains assert statements one must pass the "-source 1.4" switch
to the compiler. The following compiles a file named Foo.java, which contains
assert statements:
javac -source 1.4 Foo.java
By disabling assertions at runtime, it will not cause the assertion bytecode to be removed from class files. For those who are concerned about reducing class loading time the "conditional compilation" idiom can be used to remove all traces of assert statements from bytecode. This can be done as follows:
public class SomeClass {
public static final boolean ASSERTS_ENABLED = false;
. . .
public void foo() {
if (ASSERTIONS_ENABLED) {
assert someCondition();
}
}
}
By default, the Java runtime system also does not support the assertion
facility. Two command line switches exist to allow one to enable or disable
assertions at runtime. The -enableassertions, or
-ea, switch will enable assertions and the
-disableassertions, or -da, will disable assertions.
Assertions can be selectively enabled or disabled at the package or class
level.
Another set of command line switches exist to enable or disable system class
assertions (i.e. classes within rt.jar) at runtime. The
-enablesystemassertions, or -esa, switch exists to
enable asserts in all system classes and the
-disablesystemassertions, or -dsa, switch exists to
disable asserts in all system classes.
If you wanted to enable assertions in all classes except system classes you would write:
java -ea <MainApp>
If you wanted to enable assertions for all classes except those in the
com.ociweb.app.common package, you would write:
java -ea -da:com.ociweb.app.common <MainApp>
If you wanted to enable assertions in all classes except
com.ociweb.app.common.Foo you would write:
java -ea -da:com.ociweb.app.common.Foo <MainApp>
The following methods have been added to java.lang.ClassLoader:
public void clearAssertionStatus();
Clears any assertion settings associated with a ClassLoader. This
sets the default assert status for the ClassLoader to
false and discards any current assertion settings. This may be
useful to clear any assert status that was set from the command line.
public void setClassAssertionStatus(String className, boolean enabled);
Allows setting of assertion status on a per-class basis.
public void setDefaultAssertionStatus(boolean enabled);
Sets the default assertion status for all classes loaded by a
ClassLoader.
public void setPackageAssertionStatus(String packageName, boolean enabled);
Allows setting of assertion status on a per-package basis.
It is worth noting that although methods can be invoked at any time, it will only affect classes loaded after the invocation. In other words, a class's assertion status is set once and for all when that class is loaded.
One of the most significant benefits of Java's new assertion facility is the ability to disable them at runtime. By disabling assertions at deployment, the program will get a performance boost due to the assert statements not having to be evaluated. While it is a good idea to disable assertions at deployment, it is possible to enable assertions at any time by restarting the application with the appropriate command line switch. By not removing assert statements from code before deployment, bugs which could not be duplicated in a testing environment can be found.
Since assertions may be disabled at runtime, assert statements should never call any method that may affect the state of the system. Assert statements should only invoke methods that test a condition. Take a look at the following code:
public class Mail {
private Address sendToAddress;
private Address senderAddress;
. . .
public void send() {
assert validateAddress();
// Code which sends the mail follows
. . .
}
private boolean validateAddress() {
if (!sendToAddress.isValid()) {
// This code causes a side effect if assertions are enabled.
if (senderAddress.isValid()) {
sendToAddress = senderAddress;
} else {
return false;
}
} else {
return true;
}
}
. . .
}
The code above contains side effects due to the assert statement, because the
validateAddress() method may change the value of the
sendToAddress variable. If this code is run with assertions
disabled, it may cause undesired side effects.
Valid arguments that may be passed to a public method are considered to be part of that method's contract. Therefore, it is not proper to use assertions to validate the arguments passed to a method. Unfortunately, I believe this will be where most developers will use assertions. Lets face it; it's much easier to write:
public void setName(String name) {
assert name != null : "name cannot be null";
this.name = name;
}
than:
public void setName(String name) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
}
I suggest developing a utility class such as the following that will enable you to write one liner checks without improperly using the assertion facility.
public class ArgChecker {
private ArgChecker() { } // Prevent instantiation.
public static void checkNotNull(Object ref, String message)
throws NullPointerException {
if (ref == null) {
throw new NullPointerException(message);
}
}
public static void checkCondition(boolean expression, String message)
throws IllegalArgumentException {
if (!expression) {
throw new IllegalArgumentException(message);
}
}
}
Now one can simply write:
public void setName(String name) {
ArgChecker.checkNotNull(name, "name cannot be null");
this.name = name;
}
A precondition is something that a method assumes to be true on
invocation. As mentioned above, it is improper use of the assertion facility
to validate arguments passed to public methods, but it is perfectly valid to
use assertions to test other conditions before execution. A postcondition is a
condition that is assumed to be true after a method is finished
executing.
public class Car {
private boolean started;
private boolean hasFuel;
private boolean parked;
public void drive() {
assert started : "Must start car before driving it";
assert hasFuel : "Must have fuel to drive car";
// Code to drive a car follows
}
public void exitCar() {
// Do work necessary to exit car
assert parked : "Cannot exit car until you put it in park";
}
}
In the above example, a precondition to the drive() method would
be the started and hasFuel conditions. Without being
started or without fuel, it is going to be impossible to drive a car. In the
above example, a postcondition to the exitCar() method would be
parked condition. If a car is exited before being put in park,
trouble is bound to happen.
A good use of assertions is placing them in your code where assumptions about program behavior exist. If the execution of an else clause in an else-if condition implies a condition it should be changed to use an assert statement to test the condition. For example:
if (x == 1) {
. . .
} else if (x == 2) {
. . .
} else { // x == 3
. . .
}
The last comment should be replaced with:
assert x == 3;
Another good use of assertions is placing them in switch statements without a default case. For example:
switch(genderType) {
case Gender.FEMALE:
. . .
break;
case Gender.MALE:
. . .
break;
}
It is assumed that one of the two case statements will always be executed. To add more confidence to this assumption simply add the following default case to the switch construct:
default:
assert false : "Invalid genderType";
One more good use of assertions is by placing them in parts of code assumed to be unreachable. For example:
for (. . .) {
if (. . .) {
return;
}
}
// Execution will never reach this point.
The final comment should be replaced with:
assert false;
Assertions are a welcome addition to the Java language. Although simple to use, they can have a major impact on software quality. By using assertions properly, they can help catch bugs early in the development cycle and serve as documentation about acceptable program behavior for other developers that read your code. Since assertions may be disabled at runtime, there is no concern about taking a performance hit due to their use. So use them early and often in the development process and remember to always be assertive.
Object Computing, Inc (OCI) has been providing educational services to clients, industries and universities since 1993. We offer one of the most comprehensive distributed Object Oriented training curricula in the country. These curricula focus on the fundamentals of OO technology; with close to 40 workshops in OOAD, Java, XML, C++/CORBA and Unix/Linux.
For further information regarding OCI's Educational Services programs, please visit our Educational Services section on the web or contact us at training.
|
|
|