Saturday, February 26, 2011

check or uncheck exceptions

In Java there are basically two types of exceptions: Checked exceptions and unchecked exceptions. C# only has unchecked exceptions. The differences between checked and unchecked exceptions are:
  1. Checked exceptions must be explicitly caught or propagated as described in Basic try-catch-finally Exception Handling. Unchecked exceptions do not have this requirement. They don't have to be caught or declared thrown.
  2. Checked exceptions in Java extend the java.lang.Exception class. Unchecked exceptions extend the java.lang.RuntimeException.
There are many arguments for and against both checked and unchecked, and whether to use checked exceptions at all. I will go through the most common arguments throughout this text. Before I do so, let me just make one thing clear:
Checked and unchecked exceptions are functionally equivalent. There is nothing you can do with checked exceptions that cannot also be done with unchecked exceptions, and vice versa.
Regardless of your choice between checked and unchecked exceptions it is a matter of personal or organisational style. None is functionally better than the other.


A Simple Example

Before discussing the advantages and disadvantages of checked and unchecked exceptions I will show you the difference in the code they make. Here is a method that throws a checked exception, and another method that calls it:

public void storeDataFromUrl(String url){
        try {
            String data = readDataFromUrl(url);
        } catch (BadUrlException e) {
            e.printStackTrace();
        }
    }

    public String readDataFromUrl(String url)
    throws BadUrlException{
        if(isUrlBad(url)){
            throw new BadUrlException("Bad URL: " + url);
        }

        String data = null;
        //read lots of data over HTTP and return
        //it as a String instance.

        return data;
    }

As you can see the readDataFromUrl() method throws a BadUrlException. I have created BadUrlException myself. BadUrlException is a checked exception because it extends java.lang.Exception:

public class BadUrlException extends Exception {
        public BadUrlException(String s) {
            super(s);
        }
    }

If storeDataFromUrl() wants to call readDataFromUrl() it has only two choices. Either it catches the BadUrlException or propagates it up the call stack. The storeDataFromUrl() listed above catches the exception. This storeDataFromUrl() implementation propagates the BadUrlException instead:

public void storeDataFromUrl(String url)
    throws BadUrlException{
        String data = readDataFromUrl(url);
    }

Notice how the try catch block is gone and a "throws BadUrlException" declaration is added instead. Now, let's see how it looks with unchecked exceptions. First I change the BadUrlException to extend java.lang.RuntimeException instead:

public class BadUrlException extends RuntimeException {
        public BadUrlException(String s) {
            super(s);
        }
    }

Then I change the methods to use the now unchecked BadUrlException:

public void storeDataFromUrl(String url){
        String data = readDataFromUrl(url);
    }

    public String readDataFromUrl(String url) {
        if(isUrlBad(url)){
            throw new BadUrlException("Bad URL: " + url);
        }

        String data = null;
        //read lots of data over HTTP and
        //return it as a String instance.

        return data;
    }

Notice how the readDataFromUrl() method no longer declares that it throws BadUrlException. The storeDataFromUrl() method doesn't have to catch the BadUrlException either. The storeDataFromUrl() method can still choose to catch the exception but it no longer has to, and it no longer has to declare that it propagates the exception.


Checked or Unchecked?

Now that we have seen the difference in code between checked and unchecked exceptions, let's dive into the arguments for and against both.
Some Java books(*) covering exceptions advice you to use checked exceptions for all errors the application can recover from, and unchecked exceptions for the errors the application cannot recover from. In reality most applications will have to recover from pretty much all exceptions including NullPointerException, IllegalArgumentExceptions and many other unchecked exceptions. The action / transaction that failed will be aborted but the application has to stay alive and be ready to serve the next action / transaction. The only time it is normally legal to shut down an application is during startup. For instance, if a configuration file is missing and the application cannot do anything sensible without it, then it is legal to shut down the application.
(*) Suns Java Tutorial does for one.
My advice to you is to use either only checked exceptions or only unchecked exceptions. Mixing exception types often results in confusion and inconsistent use. Of course you should be pragmatic. Do what makes sense in your situation.
Below is a list of the most common arguments for and against checked and unchecked exceptions. An argument in favor of one type of exceptions is usually against the other type (pro-checked = con-unchecked, pro-unchecked = con-checked). Therefore the arguments are only listed as either in favour of checked or unchecked exceptions.


  1. Pro Checked Exceptions:
    Compiler enforced catching or propagation of checked exceptions make it harder to forget handling that exception.
  2. Pro Checked Exceptions:
    Unchecked exceptions makes it easier to forget handling errors since the compiler doesn't force the developer to catch or propagate exceptions (reverse of 1).
  3. Pro Unchecked Exceptions:
    Checked exceptions that are propagated up the call stack clutter the top level methods, because these methods need to declare throwing all exceptions thrown from methods they call.
  4. Pro Checked Exceptions:
    When methods do not declare what unchecked exceptions they may throw it becomes more difficult to handle them.
  5. Pro Unchecked Exceptions:
    Checked exceptions thrown become part of a methods interface and makes it harder to add or remove exceptions from the method in later versions of the class or interface.


Each of the arguments also have counter arguments which will be discussed as I go through the argument in the following sections.

Argument 1 (Pro Checked Exceptions):

Compiler enforced catching or propagation of checked exceptions makes it harder to forget the handling of that exception.

Counter-argument:

When being forced to catch or propagate many exceptions developers risk acting sloppily, and just write

try{
   callMethodThatThrowsException();
catch(Exception e){
}

and thus effectively ignore the error.

Argument 2 (Pro Checked Exceptions):

Unchecked exceptions makes it easier to forget handling errors since the compiler doesn't force the developer to catch or propagate exceptions.

Counter-argument 1:

It's not any worse than the sloppy exception handling tendency when being forced to handle or propagate checked exceptions.

Counter-argument 2:

On a recent larger project we decided to go with unchecked exceptions. My personal experience from that project is this: When using unchecked exceptions any method can potentially throw exceptions. Thus I was always reasonably conscious about exceptions no matter what parts of the code I was working on. Not only when checked exceptions were declared.
In addition many of the standard Java API methods that do not declare any checked exceptions may still throw unchecked exceptions like NullPointerException or InvalidArgumentException. Your application will still need to handle these unchecked exceptions. You could argue that the fact that there are checked exceptions makes it easy to forget handling the unchecked exceptions because they are not declared.

Argument 3 (Pro Unchecked Exceptions):

Checked exceptions that are propagated up the call stack clutter the top level methods, because these methods need to declare throwing all exceptions thrown from methods they call. That is. the declared exceptions are aggreated up the methods in the call stack. Example:

public long readNumberFromUrl(String url)
    throws BadUrlExceptions, BadNumberException{
        String data = readDataFromUrl(url);
        long number = convertData(data);
        return number;
    }

    private String readDataFromUrl(String url)
    throws BadUrlException {
       //throw BadUrlException if url is bad.
       //read data and return it.
    }

    private long convertData(String data)
    throws BadNumberException{
        //convert data to long.
        //throw BadNumberException if number isn't within valid range.
    }

As you can see the readNumberFromUrl() needs to declare throwing both the BadUrlException and the BadNumberException that are thrown from the readDataFromUrl() and converData() methods. Imagine how many exceptions would need to be declared at the top level methods of an application with thousands of classes. This can make checked exception propagation a real pain.

Counter-argument 1:

The exception declaration aggregation rarely happens in real applications. Often developers will use exception wrapping instead. Here is how that could look:

public void readNumberFromUrl(String url)
    throws ApplicationException{
        try{
            String data = readDataFromUrl(url);
            long number = convertData(data);
        } catch (BadUrlException e){
            throw new ApplicationException(e);
        } catch (BadNumberException e){
            throw new ApplicationException(e);
        }
    }

As you can see the readNumberFromUrl() method now only declares throwing ApplicationException. The exceptions BadUrlException and BadNumberException are caught and wrapped in a more general ApplicationException. This way exception wrapping avoids exception declaration aggregation.
My personal opinion is, that if all you do is to wrap the exception and not provide any extra information, why wrap it at all? The try-catch block is just extra code that doesn't do anything. It would be easier to just make the ApplicationException, BadUrlException and BadNumberException be unchecked exceptions. Here is an unchecked version of the above code:

public void readNumberFromUrl(String url){
        String data = readDataFromUrl(url);
        long number = convertData(data);
    }

It is still possible to wrap unchecked exceptions if you should want to. Below is a wrapping edition of the unchecked code. Notice how the readNumberFromUrl() method does not declare throwing the ApplicationException even if it throws it.

public void readNumberFromUrl(String url)
        try{
            String data = readDataFromUrl(url);
            long number = convertData(data);
        } catch (BadUrlException e){
            throw new ApplicationException(
                "Error reading number from URL", e);
        } catch (BadNumberException e){
            throw new ApplicationException(
                "Error reading number from URL", e);
        }
    }

Counter-argument 2:

Another commonly used technique to avoid exception declaration aggregation up the call stack of an application is to create an application base exception. All exceptions thrown in the application must be a subclass of the base exception. All methods throwing exceptions need only declare to throw the base exception. As you know a method throwing Exception may also throw any subclass of Exception. Here is how that could look:

public long readNumberFromUrl(String url)
    throws ApplicationException {
        String data = readDataFromUrl(url);
        long number = convertData(data);
        return number;
    }

    private String readDataFromUrl(String url)
    throws BadUrlException {
       //throw BadUrlException if url is bad.
       //read data and return it.
    }

    private long convertData(String data)
    throws BadNumberException{
        //convert data to long.
        //throw BadNumberException if number isn't within valid range.
    }


    public class ApplicationException extends Exception{ }
    public class BadNumberException   extends ApplicationException{}
    public class BadUrlException      extends ApplicationException{}

Notice how the BadNumberException and BadUrlException are no longer declared thrown nor caught and wrapped. They are subclasses of the ApplicationException so they will get propagated up the call stack.
My opinion is the same as with exception wrapping: If all methods in the application just declares throwing the ApplicationException (base exception), why not just make the ApplicationException unchecked and save some try-catch blocks and throws ApplicationExceptions clauses? 

Argument 4 (Pro Checked Exceptions)

When methods do not declare what unchecked exceptions they may throw it becomes more difficult to handle them. Without declaration you cannot know which exceptions the method may throw. Thus you may not know how to handle them properly. Except of course, if you have access to the code and can see there what exceptions may be thrown from the method.

Counter-argument:

In most cases you cannot do anything about the exception except showing an error message to the user, write a message to the log, and/or rollback the transaction etc. No matter what exception occurs you will in many situations handle it the same way. Because of this applications often have a few central and general pieces of error handling code. Therefore it is not so important to know exactly what exceptions may be thrown.

Argument 5 (Pro Unchecked Exceptions)

Checked exceptions declared on methods become part of a the class or interface contract. This makes it harder to add new exceptions to the method later without breaking the contract.

Counter-argument

This is not a problem if the method uses a base exception. New exceptions can be thrown at will if the method declares throwing the base exception. The only requirement is that the new exceptions thrown are subclasses of the base exception.
Again, what is the value of having all methods that may throw exceptions declare throwing the same base exception? Does it enable you to handle the exceptions any better than if you knew the methods might throw an unchecked exception?


Summary

I used to be in favor of checked exceptions but recently I have begun to change my mind. Personalities like Rod Johnson (Spring Framework), Anders Hejlsberg (father of C#), Joshua Bloch (Effective Java, item 41: Avoid unnecessary use of checked exceptions) and others have made me rethink the real benefit of checked exceptions. Lately we have tried using unchecked exceptions on a larger project, and they have worked out just fine. The error handling is centralized in a few classes. Here and there we have had to do local error handling instead of propagating the exception to the main error handling code. But it is not in very many places. Our code has become somewhat more readable now that there aren't try-catch blocks all over the code. In other words, there are a lot less no-benefit catch-rethrow try-catch blocks in the code than with checked exceptions. All in all I would recommend using unchecked exceptions. At least give it a try on a project. I have summarized the reasons below:
  • Unchecked exceptions do not clutter the code with unnecessary try-catch blocks.
  • Unchecked exceptions do not clutter the method declarations with aggregated exception declarations.
  • The argument that you easier forget to handle unchecked exceptions is not valid in my experience.
  • The argument that it is harder to know how to handle undeclared exceptions is not valid in my experience.
  • Unchecked exceptions avoids versioning problems altogether.
You or your project will have to make your own decisions about whether to use checked or unchecked exceptions, or both. Here is a list of resources that also discusses the decision between checked and unchecked exceptions.

No comments:

Post a Comment