|
Assertions are an important new feature of version 1.4. Their use is invaluable during the development and testing phase. During development, you may notice that a certain method fails during testing and you want to know if it is failing due to invalid arguments or if your implementation of the method is flawed. For that you could use either some conditional checks or some debugging messages. However, once the problem is corrected, you need to remove these conditions and debugging messages, since you do not need them when the code goes for final deployment. With assertions, you are spared from all this rework. You can enable or disable them as per your convenience. However, as with every good thing in life, you should not use assertions just because you can. Sometimes assertions are inappropriate. The following sections summarize these typical situations.
When not to use the assertions
Assertions are a complementary mechanism to aid debugging and testing. Your code should behave the same irrespective of whether the assertions are enabled or disabled. For that reason, the use of assertions is discouraged in certain situations.
Do not use assertions when the assert expressions can cause a side effect
The assert statements are not guaranteed to always run. For instance, if you disable assertions, they won’t run. Therefore, you do not want your code to depend on execution of assertions. In other words, assertions should have no side effects, which can produce adverse behavior in the code once they are disabled. The program should not rely on any computations done within an assertion statement. For instance, the following code demonstrates how the expressions in assertions can cause the adverse side effect:
public class Car() {
int mileage = 30;
public int getMileage() {
assert modifyMileage(); // method call in assert expression
// statements;
return mileage;
}
private boolean modifyMileage () {
mileage = mileage + 10;
return true;
}
}
The internal state of class Car, its mileage variable will be changed when the assertions are enabled. The getMileage() method will return different values depending on whether the assertions are enabled or disabled. You do not wish these kind of inconsistent behavior and therefore avoid expressions in assert which modify the state of the class in any way.
Do not use assertions for validating arguments to the public methods
Assertions should also not be used to validate information supplied by a client of the code. A typical examples of inputs from the client are either the arguments of public methods or command line arguments to the main() method.
The public methods expose the programming interface of a class. The client of these methods may call them with invalid arguments. Usually you need to enforce the constraints on the argument within the method itself. Since assertions are not guaranteed to be executed all the time (for instance, they may be disabled), you should not validate the method arguments in the expressions of the assert statement. If you do, then the arguments will not be validated if the assertions are disabled. For instance, if you have a public method for a Car class which gives mileage based on its make, the following code asserts that the client cannot call the method with a null or an empty make value.
public int getMileage(String make) {
assert (make != null && make != “”); // Not a good practice
// statements;
return mileage;
}
There are two problems when the arguments of public method are checked with assert.
1. If assertions are disabled, the arguments won’t be checked. The client of your method can send illegal arguments. For instance, if null is sent as an argument to getMileage(), it will not be checked. Ideally, your code should behave consistently irrespective of assertions, which will not be the case here.
2. When the assertions are enabled, the getMileage() with throw an AssertionError for null and empty arguments. But the stack trace of AssertionError provides limited information about the cause of any failure to your clients. So clients probably won’t get a clue why the method failed.
|
|
In public methods, you should provide specific cause of failure to the client so that it can correct the problem. If the arguments are incorrect, you should throw specific exceptions with debugging messages. The specific exceptions such as IllegalArgumentException, NullPointerException or IndexOutOfBoundsException can give a hint as to what exactly is wrong with the arguments. |
Do not catch the AssertionError error
Errors represent fatal conditions, which should not have occurred. Therefore, when they occur, you are not supposed to handle them in your program. The same is true about the AssertionError. When it is thrown, it indicates that your assumptions are wrong somewhere in the code. It will give you a hint in the stack trace where exactly your assumptions are wrong.
AssertionError is a subclass of the Throwable, so it can be caught in a catch clause. But you should never do that. The AssertionError occurs when a critical assumption is wrong. If you catch it, it may go undetected until something else fails based on this assumption. It would be disastrous to continue the execution without correcting the problem.
Assertions are very useful as a debugging tool. You can use them for finding out bugs during development and verifying your assumptions about the code.
Use to verify the flow control
You should use assertions to confirm that the cases, which are never supposed to happen, are indeed not happening. This is especially true for critical assumptions about the flow of control in the program. You can explicitly confirm that certain locations in the code are not executing by:
assert false : "This line should never be reached.";
In the following code, we assume that that the execution never reaches the else part as the fire alarm’s status should always be on.
private void fireAlarm() {
if (alarmStatus == ”On”) {
// statements
}
else
assert false : "Code red! The fire alarm is off.";
}
The assertion in else is to confirm this assumption. The statement assert false would cause the AssertionError to be thrown immediately if you ever do reach the else.
Use in a switch statement with no default case
A switch statement with no default case is an ideal candidate to use assertions. We saw earlier that the default statement is optional in a switch statement. If you are confident that the switch argument will always match one of the values from its cases, you will not need a default case. However, if you want to confirm your assumption, write a default case with assert false;
For instance, in the following switch, the status of a fire alarm should either be 0(“on”) or 1(“blowing”) at all times. You may not have a default statement as you assume that the status is never other than 0 or 1. To confirm your understanding, add the default statement with the assert false;
switch(status) {
case 0 : System.out.println(“on”);
break;
case 1 : System.out.println(“blowing”);
break;
default: assert false : "Code red! The fire alarm is malfunctioning.";
}
If the default case is matched, it will throw the AssertionError and you will end up with a successful detection of a bug.
Use in post-conditions and non-public preconditions.
As we have seen earlier, the programming-by-contract is a popular methodology to write robust object-oriented applications. It specifies the behavior of the methods as if it is a contract. The method declares its contract saying what all it is going to provide (the post-conditions) and what all it expects for proper execution (the pre-conditions). The assertion facility can be used to check that the methods comply with their contract.
The pre-conditions define assumptions for the proper execution of a method when it is invoked. As discussed earlier, preconditions on public methods should not be checked using assertions. The preconditions for non-public methods can be checked at the start of method execution. For instance, a private method getBallot() can check if the supplied ballot number is nonzero as:
private Ballot getBallot(int number) {
assert (number != 0) : “Ballot number cannot be zero”;
// statements
}
The post-conditions define the assumptions about the successful completion of a method. The post-conditions in any method can be checked by assertions executed just before exiting from the method. For example, if the method getNewBallot() guarantees that a brand new ballot will be provided after its completion, we can check this post-condition using an assertion as:
private Ballot getNewBallot() {
// statements
assert (Ballot.status == “unused ) : “Invalid Ballot status”;
}
|
|
The method contract can be limitedly verified with assertions. Programming-by-contract suggests that every method should comply with its contract (verified with pre and post conditions). Assertions provide excellent way of checking the pre and post conditions in a non-public method. But they are not useful in case of public methods. Since the pre conditions and post conditions of public methods should be always checked, you should not use assertions for verifying the public method’s contract. Therefore, you can only enforce the Programming-by-contract methodology partially with assertions. |