Saturday, October 29, 2011

Making Exceptions

The idea of exceptions goes back before Java, but Java embraced the idea more than any language before it.

The fundamental problem exceptions solve is how to send error information back to the caller of a function / method / whatever. Before exceptions were prevalent, there were a couple of preferred solutions (among others). First, the function could return an error value instead of a "normal" answer; for example, a function that normally returns a non-negative number could return -1 if there was some problem. Second, the function could set some reserved global variables with error codes and messages and so on. Both solutions have their own problems. Among them is that the human doing the programming has to remember to check for error conditions. And, because humans are often lazy, rushed, or sloppy, it's easy for those checks to not get done.

Java provides two kinds of exceptions, and one of them, called checked exceptions, cannot be ignored. If a method "throws" a checked exception back to its caller, the calling code must either deal with the exception or throw it on up to its caller. Either option requires the programmer to explicitly do something: either write some code to deal with the exception, or have the method declare that it throws the exception. The programmer is forced to confront the possibility of an error. Overall, this leads to more robust code.

Let's say we have a method for opening a file that can throw java.io.IOException, which is a checked exception. The method definition could look like this:

public java.io.File open (String name) throws java.io.IOException {
  // ...
}

Any code that calls the method must deal with the possibility of an IOException popping out. It can just pass the exception on, throwing it itself:

public void doSomethingWithAFile() throws IOException {
  // ...
  // ... create a FileOpener object with our open method in it ...
  java.io.File myFile = fileOpener.open (theFileName);
  // ...
}

The other option is to handle it itself. This involves a "try-catch" block.

public void doSomethingWithAFile() {
  // ...
  try {
    java.io.File myFile = fileOpener.open (theFileName);
    // ...
  } catch (java.io.IOException exc) {
    // ... do something here, like logging or recovering ...
  }
  // ...
}

The code that could throw exceptions goes into the code block after "try". After the try block comes a "catch" block for the exception that should be caught there. It's fine for a catch block to throw some other exception; in fact, that's a well-known tactic, and a good idea a lot of the time.

I said Java provides two kinds of exceptions. One is checked, and the other is unchecked exceptions. The difference is that you don't need to explicitly deal with an unchecked exception; by default, if you don't catch it, it automatically propagates up to your code's caller, and so on up and possibly out of the JVM (which would then stop execution). Methods that throw unchecked exceptions don't even have to declare that they throw them.

Let's add a line to our "open" method to make sure the name passed in isn't null. If it is, we'll throw the unchecked IllegalArgumentException. Since that exception is unchecked, it doesn't have to be mentioned in the method definition.

public java.io.File open (String name) throws java.io.IOException {
  if (name == null) {
    throw new IllegalArgumentException ("null name");
  }
  // ...
}

Calling code can still catch IllegalArgumentException, but it doesn't have to in order to compile and run.

Unchecked exceptions obviously make code less safe, that is, more prone to stopping completely when something goes wrong. So why have them? Why aren't all Java exceptions checked?

Unchecked exceptions are supposed to be used for situations that are not "recoverable". A programmer who opts for throwing a checked exception is saying that calling code may be able to deal with the error that led to the exception, or should at least try to. On the other hand, throwing an unchecked exception implies that there isn't much calling code can do, usually, so it's expected to just let execution stop. Calling code can, of course, still catch unchecked exceptions, but it's not required.

Having explained all that, the majority of Java developers (myself not included) seem to hate checked exceptions. Historically they were overused, and forced developers to deal with a lot of conditions that are either really unrecoverable, or that aren't important to deal with at all. Prime example:

package java.io;

public class FileInputStream extends InputStream {
  // ...
  public void close() throws IOException {
  // ...
  }
  // ...
}

A FileInputStream is used to read binary data from a file. When you're done reading from the stream, you need to close it so that the system can release resources associated with the file. The close() method throws a checked exception, and you almost always don't care if you can't close a stream once you're through with it. Dealing with this exception leads to all sorts of unnecessary wacked-out try-catch gymnastics. (Java 7 promises to help, but still.)

Unfortunately, some in the Java community overreacted and abandoned checked exceptions completely. For example, the Spring and Hibernate libraries mostly throw unchecked exceptions, even for conditions that warrant checked exceptions. The argument is that it makes programmers' jobs easier. No doubt you can write less code because you don't need try-catch blocks or "throws" clauses on your own methods, but the temptation to ignore the errors returns, and the code overall is less robust.

What's called for is a more balanced approach. Use checked and unchecked exceptions judiciously, balancing convenience with robustness.