Tuesday, March 1, 2011

Assertions in java

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) {
S
} else if (n % 3 == 1) {
S
} 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.


There are a few situations where you should not use assertions:

  1. Do not use assertions for argument checking in public methods.
    Argument checking is typically part of the published specifications (or contract) of a method, and these specifications must be obeyed whether assertions are enabled or disabled. Another problem with using assertions for argument checking is that erroneous arguments should result in an appropriate runtime exception (such as IllegalArgumentException, IndexOutOfBoundsException, or NullPointerException). An assertion failure will not throw an appropriate exception.
  2. Do not use assertions to do any work that your application requires for correct operation.
    Because assertions may be disabled, programs MUST NOT assume that the boolean expression contained in an assertion will be evaluated. Violating this rule has dire consequences. For example, suppose you wanted to remove all of the null elements from a list names, and knew that the list contained one or more nulls. It would be wrong to do this:
    // Broken! - action is contained in assertion
    assert names.remove(null);
            
    The program would work fine when asserts were enabled, but would fail when they were disabled, as it would no longer remove the null elements from the list. The correct idiom is to perform the action before the assertion and then assert that the action succeeded:
    // Fixed - action precedes assertion
    boolean nullsRemoved = names.remove(null);
    assert nullsRemoved;  // Runs whether or not asserts are enabled
            
    As a rule, the expressions contained in assertions should be free of side effects: evaluating the expression should not affect any state that is visible after the evaluation is complete. One exception to this rule is that assertions can modify state that is used only from within other assertions.
There are many situations where it is good to use assertions:

  1. Internal Invariants
    Before assertions were available, many programmers used comments to indicate their assumptions concerning a program's behavior. You should now use an assertion whenever you would have written a comment that asserts an invariant:
    if (i % 3 == 0) {
     ...
    } else {
     if (i % 3 == 1) {
      ...
     } else {
      assert i % 3 == 2 : i;
      ...
     } 
    }        
            
    Another good candidate for an assertion is a switch statement with no default case. The absence of a default case typically indicates that a programmer believes that one of the cases will always be executed. The assumption that a particular variable will have one of a small number of values is an invariant that should be checked with an assertion:
    default:
     assert false : suit;
            
    If the suit variable takes on another value and assertions are enabled, the assert will fail and an AssertionError will be thrown. An acceptable alternative is:
    default:
     throw new AssertionError(suit); 
            
    This alternative offers protection even if assertions are disabled, but the extra protection adds no cost: the throw statement won't execute unless the program has failed. Moreover, the alternative is legal under some circumstances where the assert statement is not. If the enclosing method returns a value, each case in the switch statement contains a return statement, and no return statement follows the switch statement, then it would cause a syntax error to add a default case with an assertion. (The method would return without a value if no case matched and assertions were disabled.)
  2. Control-Flow Invariants
    Place an assertion at any location you assume will not be reached. The assertions statement to use is:
    assert false;        
            
    Code now reads:
    void foo() {
     for (...) {
      if (...) return;
     }
     assert false; // Execution should never reach this point!
    }
            
  3. Preconditions, Postconditions, and Class Invariants
    While the assert construct is not a full-blown design-by-contract facility, it can help support an informal design-by-contract style of programming.
    Do not use assertions to check the parameters of a public method. An assert is inappropriate because the method guarantees that it will always enforce the argument checks. It must check its arguments whether or not assertions are enabled. Further, the assert construct does not throw an exception of the specified type. It can throw only an AssertionError.
    You can, however, use an assertion to test a nonpublic method's precondition that you believe will be true no matter what a client does with the class.
    You can test postcondition with assertions in both public and nonpublic methods.
    A class invariants is a type of internal invariant that applies to every instance of a class at all times, except when an instance is in transition from one consistent state to another. A class invariant can specify the relationships among multiple attributes, and should be true before and after any method completes. For example, suppose you implement a balanced tree data structure of some sort. A class invariant might be that the tree is balanced and properly ordered.
    The assertion mechanism does not enforce any particular style for checking invariants. It is sometimes convenient, though, to combine the expressions that check required constraints into a single internal method that can be called by assertions. Continuing the balanced tree example, it might be appropriate to implement a private method that checked that the tree was indeed balanced as per the dictates of the data structure:
    // Returns true if this tree is properly balanced
    private boolean balanced() {
     ...
    }
            
    Because this method checks a constraint that should be true before and after any method completes, each public method and constructor should contain the following line immediately prior to its return:
    assert balanced(); 
            
    It is generally unnecessary to place similar checks at the head of each public method unless the data structure is implemented by native methods. In this case, it is possible that a memory corruption bug could corrupt a "native peer" data structure in between method invocations. A failure of the assertion at the head of such a method would indicate that such memory corruption had occurred. Similarly, it may be advisable to include class invariant checks at the heads of methods in classes whose state is modifiable by other classes.
In order for the javac compiler to accept code containing assertions, you must use the -source 1.4 command-line option as in this example:
javac -source 1.4 MyClass.java
     
This flag is necessary so as not to cause source compatibility problems. NOTE, in Java 5.0 the compiler accepts assertions BY DEFAULT:
javac MyClass.java     
     
In both Java 5.0, as in Java 1.4 assertions are disabled by default at runtime. You need explicitly to turn them on. At a runtime you can check if the program is running with enabled assertions using the following code:
boolean assertsEnabled = false;
assert assertsEnabled = true;  // Intentional side-effect !!!
// Now 'assertsEnabled' is set to the correct value
     

No comments:

Post a Comment

Chitika