Thursday Dec 20, 2007

Exception Handling Best Practices Part 1

Today I'll talk about my experiences with exceptions in the Java programming language. It's a longer post, but it contains a full set of my personal best practices when it comes to handling those annoying error cases in your code - software development would be even more fun if you'd only code for the single everything-is-working-normally case! For some of you the things noted here might seem trivial and obvious, but there are many APIs out there that really mess with exceptions.

The following rules are based on Java exceptions, but they are valid with any exception system in a programming language that models exceptions as classes with inheritance, has the concept of chaining exceptions and makes a difference between checked and unchecked exceptions. So here's my advice:

  1. Do not throw (checked) exceptions that the user of the API can't do anything about, eg. those seldom exceptions like "oh my god, the world is suddenly rotating in the other direction!" If the client code can't handle that kind of exception and fix the problem or go through a workaround, don't throw a checked exception. It will just mess up the code but not improve the logic or robustness of that piece of software.

    Instead you can use unchecked exceptions, ie. inherit from RuntimeException and let some bottom layer in your code (or your framework like the servlet engine for example) catch it, so that your application doesn't crash and the error can be logged.

    When you want to make a distinction like this, it's useful to have separate terms for the two concepts: This interesting article introduces Contingency (exceptions as part of your design) and Faults (unexpected exceptions that should be unchecked). It goes into more detail about this point and talks about fault barriers in your code and when it makes sense to log exceptions.

  2. Do not use your base exception class for "unkown" exception cases. This is actually a follow-up from the first advice above. If you model your own exception hierarchy, you will typically have an exception base class (eg. MyAPIException) and several specific ones that inherit from that one (eg. MyAPIPathNotFoundException). Now it is tempting to throw the base exception class whenever you don't really know what else to throw, because the error case is not clear or very seldom. It's probably a Fault and thus you would start mixing it with your Contingency exception class hierarchy, which is obviously a bad thing.

    One of the advantages of an exception base class is that the client has the choice to catch the base class if he does not want to handle the specific cases (although that's probably not the most robust code). But if that exception is also thrown in faulty situations, the client can no longer make a distinction between one-of-those-contingency-cases and all-those-unexcpected-fault-cases. And it obviously brakes your explicit exception design: there are those specific error cases you state and let the client know about, but then there is this generic exception thrown where the client cannot know what it means and is not able to handle it as a consequence.

  3. Each API level of your software should pass on only those exceptions that are tied to the semantics of that API. For example, throwing something like a specific (non-runtime) OracleConnectionException in your SQL client API is annoying if that API also works with other databases. The point in this is that throwing such exceptions from the underlying implementation breaks the advantages of the abstraction that the higher-level API provides. Make sure you wrap them as good as possible into your API's own exception(s). Fortunately, Java's exception chaining is perfect for that: simply add the original exception as cause of your own exception and it will be visible in the stack trace without loosing potentially important debugging information.

  4. When wrapping or logging exceptions, add your specific data to the message. During debugging of errors, the exception type and it's basic message, eg. "Connection failed", often is too unspecific to do anything about it. You might say, hey, just start the debugger and look at the variables. But that doesn't work always, because sometimes you don't have a Java debugger available (eg. on a production machine) or you simply don't have the code for that library. Even if debugging is possible, it often takes a lot of time to locate the exception throwing location in the code and step into it in the right moment (imagine it happens only once in a loop with 1000 iterations and you don't know in which one....).

    The solution is quite simple: every time you throw an exception, there should be at least one piece of dynamic information added to the exception and/or log message. For example, if we have some kind of user profile library that works on top of an HTTP client that uses a Socket API for the underlying network connection, these messages could like this:
    User Profile Library API: Could not upload user image 'file:///home/steve/photo.jpg'
    HTTP API: PUT on 'http://localhost:8080/images/upload' failed
    Socket API: TCP connection to IP '127.0.0.1' Port '8080' failed, port is closed
  5. Don't throw exceptions in methods that are likely to be used for the exception handling itself. This follows straight from the two previous advices: if you throw or log exceptions, you are typically in exception handling code, because you wrap a lower-level exception. If you add dynamic data to your exceptions, you might access methods from the underlying API. But if those methods throw exceptions, your code becomes ugly. Let's look at an example:
    try {
        ...
    } catch (GeneralException e) {
        log.error("doing something with thing '" + thing.getID() + ' failed", e);
    }
    Now if thing.getID() throws an exception, probably also GeneralException, we must change the code to something like this:
    try {
        ...
    } catch (GeneralException e) {
        try {
            log.error("doing something with '" + thing.getID() + ' failed", e);
        } catch (GeneralException e2) {
            log.error("could not call thing.getID() while " +
    "trying to handle an exception (" + e2.getMessage() + ")", e);
        }
    }
    That is unreadable and ugly code - it is probably more prominent then your actually logic between try and catch. So try to make those basic information accessible without throwing exceptions.

In a follow-up post I will continue this topic and talk about control flow patterns regarding exceptions in data oriented APIs.

Comments:

[Trackback] These are the web's most talked about URLs on Sat 22nd Dec 2007. The current winner is ..

Posted by purrl.net |** the web's most interesting news **| on December 22, 2007 at 08:05 PM CET #

Sorry, but your DEsign make it really hard to read your article. For example the light gray on light gray in the code examples is really hard to read.

Posted by Michael Zehrer on December 22, 2007 at 08:15 PM CET #

I vote-for-checked-exceptions (http://cafe.elharo.com/java/voting-for-checked-exceptions/)

The people who object to checked exceptions mostly seem not to know the difference between checked and unchecked.

Posted by DH on December 23, 2007 at 05:44 AM CET #

[Trackback] Merry Christmas everyone! Blogversations Size Is The Enemy - A response to Codebase size isn't the

Posted by Trumpi's blog on December 24, 2007 at 06:13 PM CET #

Let me add that it is always good to resuse existing exception types like InvalidArgumentException instead of defining new exceptions types for common cases, because this will blow up your code base. Defining new exceptions and exception hierarchies should only be done if you have a defined error model and if you have additional information that is important for the exception case.

Posted by Alexxx on December 30, 2007 at 12:20 AM CET #

Post a Comment:
  • HTML Syntax: Allowed