Monday, February 14, 2011

Chapter 31: Assertions

You know you’re not supposed to make assumptions, but you can’t help it when you’re writing code. You put them in comments:

if (x > 2 && y) {
// do something
} else if (x < 2 || y) { // do something } else { // x must be 2 // do something else } You write print statements with them: while (true) { if (x > 2) {
break;
}
System.out.print("If we got here " +
"something went horribly wrong");
}

Added to the Java language beginning with version 1.4, assertions let you test your assumptions during development, without the expense of writing exception handlers for exceptions that you assume will never happen once the program is out of development and fully deployed.
Starting with exam SCJP 1.4 and continuing through to the current exam SCJP 6, you’re expected to know the basics of how assertions work, including how to enable them, how to use them, and how not to use them.

Assertions Overview

Suppose you assume that a number passed into a method (say, methodOne()) will never be negative. While testing and debugging, you want to validate your assumption, but you don’t want to have to strip out print statements, runtime exception handlers, or if/else tests when you’re done with development. But leaving any of those in is, at the least, a performance hit. Assertions to the rescue! Check out the following code:

private void methodOne(int num) {
if (num >= 0) {
useNum(num + x);
} else { // num must be < 0 // This code should never be reached! System.out.println("Oooops! num is a negative number! " + num); } } Because you’re so certain of your assumption, you don’t want to take the time to write exception-handling code. And at runtime, you don’t want the if/else either because if you do reach the else condition, it means your earlier logic (whatever was running prior to this method being called) is flawed. Assertions let you test your assumptions during development, but the assertion code basically evaporates when the program is deployed, leaving behind no overhead or debugging code to track down and remove. Let’s rewrite methodOne() to validate that the argument was not negative: private void methodOne(int num) { assert (num>=0); //throws an AssertionError if this test isn't true
useNum(num + x);
}

Not only do assertions let your code stay cleaner and tighter, but because assertions are inactive unless specifically “turned on” (enabled), the code will run as though it were written like this:

private void methodOne(int num) {
useNum(num + x); // we've tested this;
// we now know we're good here
}

Assertions work quite simply. You always assert that something is true. If it is, no problem. Code keeps running. But if your assertion turns out to be wrong (false), then a AssertionError is thrown right then and there, so you can fix whatever logic flaw led to the problem.
Assertions come in two flavors: really simple and simple, as follows:

Really simple:

private void doSomething() {
assert (y > x);
// more code assuming y is greater than x
}

Simple:

private void doSomething() {
assert (y > x): "y is " + y + " x is " + x;
// more code assuming y is greater than x
}

The difference between the two is that the simple version adds a second expression, separated from the first (boolean expression) by a colon, this expression’s string value is added to the stack trace. Both versions throw an immediate AssertionError, but the simple version gives you a little more debugging help while the really simple version simply tells you only that your assumption was false.

Tip: Assertions are typically enabled when an application is being tested and debugged, but disabled when the application is deployed. The assertions are still in the code, although ignored by the JVM, so if you do have a deployed application that starts misbehaving, you can always choose to enable assertions in the field for additional testing.
Assertion Expression Rules


Assertions can have either one or two expressions, depending on whether you’re using the “simple” or the “really simple.” The first expression must always result in a boolean value! Follow the same rules you use for if and while tests. The whole point is to assert aTest, which means you’re asserting that aTest is true. If it is true, no problem. If it’s not true, however, then your assumption was wrong and you get an AssertionError.

The second expression, used only with the simple version of an assert statement, can be anything that results in a value. Remember, the second expression is used to generate a String message that displays in the stack trace to give you a little more debugging information. It works much like System.out.println() in that you can pass it a primitive or an object, and it will convert it into a String representation. It must resolve to a value!

Exam Tip: If you see the word “expression” in a question about assertions, and the question doesn’t specify whether it means expression1 (the boolean test) or expression2 (the value to print in the stack trace), then always assume the word “expression” refers to expression1, the boolean test. For example, consider the following question:
An assert expression must result in a boolean value, true or false?
Assume that the word ‘expression’ refers to expression1 of an assert, so the question statement is correct. If the statement were referring to expression2, however, the statement would not be correct, since expression2 can have a result of any value, not just a boolean
.


Enabling Assertions

If you want to use assertions, you have to think first about how to compile with assertions in your code, and then about how to run with assertions enabled. Both require version 1.4 or greater, and that brings us to the first issue: how to compile with assertions in your code.

Identifier vs. Keyword

Prior to version 1.4, you might very well have written code like this:
int assert = getInitialValue();
if (assert == getActualResult()) {
// do something
}

Notice that in the preceding code, assert is used as an identifier. That’s not a problem prior to 1.4. But you cannot use a keyword/reserved word as an identifier, and beginning with version 1.4, assert is a keyword. The bottom line is this:
You can use assert as a keyword or as an identifier, but not both.

Tip: If for some reason you’re using a Java 1.4 compiler, and if you’re using assert as a keyword (in other words, you’re actually trying to assert something in your code), then you must explicitly enable assertion-awareness at compile time, as follows:
javac -source 1.4 com/geeksanonymous/TestClass.java

You can read that as “compile the class TestClass, in the directory com/geeksanonymous, and do it in the 1.4 way, where assert is a keyword.”


Use Version 6 of java and javac

As far as the exam is concerned, you’ll ALWAYS be using version 6 of the Java compiler (javac), and version 6 of the Java application launcher (java). You might see questions about older versions of source code, but those questions will always be in the context of compiling and launching old code with the current versions of javac and java.

Compiling Assertion-Aware Code

The Java 6 compiler will use the assert keyword by default. Unless you tell it otherwise, the compiler will generate an error message if it finds the word assert used as an identifier. However, you can tell the compiler that you’re giving it an old piece of code to compile, and that it should pretend to be an old compiler! Let’s say you’ve got to make a quick fix to an old piece of 1.3 code that uses assert as an identifier. At the command line you can type
javac -source 1.3 OldCode.java

The compiler will issue warnings when it discovers the word assert used as an identifier, but the code will compile and execute. Suppose you tell the compiler that your code is version 1.4 or later, for instance:

javac -source 1.4 NotQuiteSoOldCode.java

In this case, the compiler will issue errors when it discovers the word assert used as an identifier.

If you want to tell the compiler to use Java 6 rules you can do one of three things: omit the -source option, which is the default, or add one of two source options:

-source 1.6 or -source 6.

If you want to use assert as an identifier in your code, you MUST compile using the -source 1.3 option.

Running with Assertions

Once you’ve written your assertion-aware code (in other words, code that uses assert as a keyword, to actually perform assertions at runtime), you can choose to enable or disable your assertions at runtime! Remember, assertions are disabled by default.

Enabling Assertions at Runtime

You enable assertions at runtime with
java -ea com.test.TestAssertions

or

java -enableassertions com.test.TestAssertions

The preceding command-line switches tell the JVM to run with assertions enabled.

Disabling Assertions at Runtime

You must also know the command-line switches for disabling assertions,
java -da com.test.TestAssertions

or

java -disableassertions com.test.TestAssertions

Because assertions are disabled by default, using the disable switches might seem unnecessary. Indeed, using the switches the way we do in the preceding example just gives you the default behavior (in other words, you get the same result regardless of whether you use the disabling switches). But...you can also selectively enable and disable assertions in such a way that they’re enabled for some classes and/or packages, and disabled for others, while a particular program is running.

Selective Enabling and Disabling

The command-line switches for assertions can be used in various ways:
• With no arguments (as in the preceding examples) Enables or disables assertions in all classes, except for the system classes.
• With a package name Enables or disables assertions in the package specified, and any packages below this package in the same directory hierarchy (more on that in a moment).
• With a class name Enables or disables assertions in the class specified.

You can combine switches to, say, disable assertions in a single class, but keep them enabled for all others, as follows:

java -ea -da:com.test.TestAssertions

The preceding command line tells the JVM to enable assertions in general, but disable them in the class com.test.TestAssertions. You can do the same selectivity for a package as follows:
java -ea -da:com.test...

The preceding command line tells the JVM to enable assertions in general, but disable them in the package com.test, and all of its subpackages! You may not be familiar with the term subpackages, since there wasn’t much use of that term prior to assertions. A subpackage is any package in a subdirectory of the named package.

Using Assertions Appropriately

Not all legal uses of assertions are considered appropriate. As with so much of Java, you can abuse the intended use of assertions, despite the best efforts of Sun’s Java engineers to discourage you from doing so. For example, you’re never supposed to handle an assertion failure. That means you shouldn’t catch it with a catch clause and attempt to recover. Legally, however, AssertionError is a subclass of Throwable, so it can be caught. But just don’t do it! If you’re going to try to recover from something, it should be an exception. To discourage you from trying to substitute an assertion for an exception, the AssertionError doesn’t provide access to the object that generated it. All you get is the String message.

So who gets to decide what’s appropriate? Sun. The exam uses Sun’s “official” assertion documentation to define appropriate and inappropriate uses.

Don’t Use Assertions to Validate Arguments to a Public Method

The following is an inappropriate use of assertions:
public void doSomething(int x) {
assert (x > 0); // inappropriate !
// do things with x
}

A public method might be called from code that you don’t control (or from code you have never seen). Because public methods are part of your interface to the outside world, you’re supposed to guarantee that any constraints on the arguments will be enforced by the method itself. But since assertions aren’t guaranteed to actually run (they’re typically disabled in a deployed application), the enforcement won’t happen if assertions aren’t enabled. You don’t want publicly accessible code that works only conditionally, depending on whether assertions are enabled.
If you need to validate public method arguments, you’ll probably use exceptions to throw, say, an IllegalArgumentException if the values passed to the public method are invalid.

Exam Tip: If you see the word “appropriate” on the exam, do not mistake that for “legal.” “Appropriate” always refers to the way in which something is supposed to be used, according to either the developers of the mechanism or best practices officially embraced by Sun. If you see the word “correct” in the context of assertions, as in, “Line 3 is a correct use of assertions,” you should also assume that correct is referring to how assertions SHOULD be used rather than how they legally COULD be used.


Do Use Assertions to Validate Arguments to a Private Method

If you write a private method, you almost certainly wrote (or control) any code that calls it. When you assume that the logic in code calling your private method is correct, you can test that assumption with an assertion as follows:
private void doMore(int x) {
assert (x > 0);
// do things with x
}

The only difference that matters between the preceding example and the one before it is the access modifier. So, do enforce constraints on private methods’ arguments, but do not enforce constraints on public methods. You’re certainly free to compile assertion code with an inappropriate validation of public arguments, but for the exam (and real life) you need to know that you shouldn’t do it.

Don’t Use Assertions to Validate Command-Line Arguments

This is really just a special case of the “Do not use assertions to validate arguments to a public method” rule. If your program requires command-line arguments, you’ll probably use the exception mechanism to enforce them.

Do Use Assertions, Even in Public Methods, to Check for Cases that You Know Are Never, Ever Supposed to Happen

This can include code blocks that should never be reached, including the default of a switch statement as follows:
switch(x) {
case 1: y = 3; break;
case 2: y = 9; break;
case 3: y = 27; break;
default: assert false; // we're never supposed to get here!
}

If you assume that a particular code block won’t be reached, as in the preceding example where you assert that x must be either 1, 2, or 3, then you can use assert false to cause an AssertionError to be thrown immediately if you ever do reach that code. So in the switch example, we’re not performing a boolean test—we’ve already asserted that we should never be there, so just getting to that point is an automatic failure of our assertion/assumption.

Don’t Use Assert Expressions that Can Cause Side Effects!

The following would be a very bad idea:
public void doSomething() {
assert (modifyThings());
// continues on
}
public boolean modifyThings() {
y = x++;
return true;
}

The rule is, an assert expression should leave the program in the same state it was in before the expression! Think about it. assert expressions aren’t guaranteed to always run, so you don’t want your code to behave differently depending on whether assertions are enabled. Assertions must not cause any side effects. If assertions are enabled, the only change to the way your program runs is that an AssertionError can be thrown if one of your assertions (think: assumptions) turns out to be false.

Previous Chapter: Chapter 30 - Common Exceptions and Errors

Next Chapter: Quick Review - Chapters 27 to 31

1 comment:

© 2013 by www.inheritingjava.blogspot.com. All rights reserved. No part of this blog or its contents may be reproduced or transmitted in any form or by any means, electronic, mechanical, photocopying, recording, or otherwise, without prior written permission of the Author.

ShareThis

Google+ Followers

Followers