Using Exceptions

Exceptions vs. Errors

Exceptions are a language mechanism that helps transfer control from one point in the program to another without cluttering the code in between. As always, we should keep a clear distinction between the mechanisms the programming language designers chose to put at our disposal and the proper ways to use those mechanisms to write good code.

In particular, an exception should not be thought of as exactly the same thing as an error, although exceptions are often used to indicate errors. We will use the word “error” to mean a mistake in the code: a programmer error. One reason why the ideas of exceptions and errors are confused is that exceptions are a useful way to stop programs quickly and cleanly when a programmer error is detected.

Exceptions have another use, however. We may in some cases want to use exceptions to handle unusual conditions within the code. These might be “errors” in (that is, misconfigurations of) the environment in which the program is being run rather than mistakes by the programmer. We want the program to be able to handle such unusual conditions and respond accordingly; exceptions are a nice way to handle such unusual conditions fairly cleanly. Without exceptions, the code to handle unusual conditions ends up mixed in with the code that handles normal-case execution of the program. This mixing makes the normal-case code (and hence the code as a whole) harder to understand. With exceptions we can factor out separately the code that handles unusual conditions.

Exceptions are generated either by using the throw statement to throw an object of a subclass of class Throwable, or by using a built-in operation that generates an exception under some condition. Java has a quite a few builtin exceptions that can be generated by standard language constructs. Null values generate a NullPointerException if used as objects, arrays generate an ArrayIndexOutOfBoundsException if the array index is, well, out of bounds, and so on.

Exceptions can also be caught by using a try-catch statement. It has a body defining what code is allowed to generate an exception, then at least one catch clause (and possibly a finally clause). The catch clauses define which exceptions generated by the try body will be caught and define what code is run when the exception is caught. The finally block provides some code that is always run before the try statement finishes, whether or not an exception was generated. The finally block is very useful for performing cleanup work that must happen regardless of how cleanly the try statement completes. For example, it might close files that were opened in the main body of the try.

Here is a small example of using exceptions to separate handling of unusual conditions from normal-case code. This code parses the command line of the program by scanning the argument list from left to right. However, it needs to handle the case in which the user fails to provide a filename to the --file command-line option. The code can be simpler if it doesn't have to check that every index into the array of arguments is in bounds. As the code below demonstrates, it is possible to use try...catch to factor out the handling of that problem in an exception handler:

arglist.java

In a typical command-line parser, there will be multiple option for which a user might forget to supply the corresponding argument. Code in the style above not only avoids cluttering up the normal-case code with error handling, but it even consolidates the handling of multiple errors into one place.

How not to handle unusual conditions

An alternative to using exceptions is to define special return values to indicate unusual conditions. The Java libraries often (unfortunately) follow this strategy. For example, the specification for String.indexOf looks like this:

/**
 * Returns: if the string argument occurs as a substring within this
 *          object, then the index of the first character of the first
 *          such substring is returned; if it does not occur as a
 *          substring, -1 is returned.
 */
public int indexOf(String str)

The problem with the special-return-value strategy is that it's easy to forget to check for the special value, writing code such as this:

    String s1 = s2.substring(s2.indexOf("header:") + 7);

If the string doesn't begin with header:, we'll get a result that includes the 6th and following characters of s2. This doesn't make much sense! If the library designers had instead chosen to throw an exception, client code could look like that above, and the compiler would force clients to remember to check for the case in which “header:” isn't found.

Checked vs unchecked exceptions

Java requires that some exceptions be declared in methods that might generate them. These are called checked exceptions. Checked exceptions force the client to be aware that they might happen and to handle them appropriately. This helps lead to more robust code. Unchecked exceptions include the run-time exceptions like NullPointerException but also subclasses of Error. The compiler will not warn clients if they are ignoring unchecked exceptions.

When should you use each kind of exception? It depends on why the exception is being used:

Specifying partial functions

The coefficient method is an interesting case, because it is a partial function that has no natural result in the case where the requested exponent is negative. There are several alternative ways to deal with this situation, with varying tradeoffs in terms of performance vs. debugging. The alternative least friendly to the client is simply to require that the requested exponent be nonnegative, by giving a requires clause:

/** Returns: the coefficient of the polynomial term with exponent n, or zero
 *           if there is no such term.
 *  Requires: n ≥ 0
 */
double coefficient(int n);

What happens if the client calls coefficient(-1)? This spec doesn't say. Maybe it throws an exception, maybe it goes into an infinite loop, maybe it just returns a wrong answer. If the client makes this call, the code can do anything it likes. But that is okay. The spec is clear that the client must ensure that n is not negative. If the requires clause is violated, it is the client's fault.

A more forgiving version of the same spec uses a checks clause, which is a kind of requires clause. The difference is that the checks clause promises to check that the precondition holds, and to stop the program as cleanly as possible if it is violated. For example, the method might throw an exception that is a subclass of Error, which the client should not try to catch. However, client code that violates the precondition is still wrong code. It is still the client's fault if the precondition is violated.

/** Returns: the coefficient of the polynomial term with exponent n, or zero
 *           if there is no such term.
 *  Checks: n ≥ 0
 */
double coefficient(int n);

A good way to implement checks clauses is by using the assert statement. For example, the implementation of coefficient might check this precondition as follows:

double coefficient(int n) {
    assert n >= 0;
    ...
}

The assert statement may check any boolean condition. If Java is used with assertions enabled (by using the -ea option, which you should enable in your development environment), then the boolean condition is tested at run time. If it evaluates to true, nothing happens. If it executes to false, an error AssertionError is generated and will halt the program at the point where the assertion failed.

It is also even more helpful when writing a checks clause to tell the programmer what exception will be thrown when the check fails, e.g.

//  Checks: n ≥ 0 (assert)

or:

//  Checks: n ≥ 0 (throws NegException)

This may help when debugging. However, if an exception is thrown to indicate an error, the client (caller) should not catch that exception. The exception indicates a problem in the client code that should not just be papered over.

A third way to deal with partial functions is to make them total, so that the customer is never wrong:

/** Returns: the coefficient of the polynomial term with exponent n, or zero
 *           if there is no such term. Throws NegException if n is negative.
 */
double coefficient(int n) throws NegException;

Notice that now the information about the thrown exception is part of the returns clause, and appears in the signature, indicating that this exception is expected behavior in some situations and that the client had better be ready to handle it when it happens. The client can call with any arguments it likes, but the price to pay is writing code to handel the exception. The implementation of coefficient might be exactly the same as when we used a “checks” clause. But with the checks clause, the contract says that the client is in error; with exceptions appearing in the returns clause, the client is always “right”.

Javadoc and exceptions

Javadoc doesn't completely support the clauses we have been describing thus far, though it has been evolving in that general direction. If you want to use Javadoc to generate HTML documentation, you will need to adapt this documentation strategy accordingly. The key is not that you need to have explicitly labeled clauses that Javadoc understands, but that you should know for each thing you write in the comment which clause it belongs to, and include all the information that should be found in the clauses that the spec of your code needs.