An Approach to Exception Handling

[In response to this post, also posted here, asking about guidelines different developers have come up with….  I actually started a response on the site, then realized I was typing an entire essay into a little comment box.]

Guideline #1: Never discard exception information.

You might either wrap the exception and throw the wrapper, or log it somehow — but discarding exception information should always set off loud alarm bells.  Likewise, catching an exception and throwing a new exception is bad if you neglect to wrap the original one — that stack trace is valuable!

Putting in a catch clause means you take responsibility for any exceptions that hit it, expected or unexpected.  If a maintenance programmer comes along and edits your code later (but doesn’t notice the black-hole catch clause), they’re going to come after you in the night with a blunt object when they realize how they wasted 4 hours tearing apart their development environment — because a simple-stupid null pointer in their bit of new code was just disappearing into space, and they thought their code simply wasn’t being executed.

Many folks know this, more or less, but they still take shortcuts like this “just to get it running”:

[sourcecode language=’java’]
catch(Exception e) {
// todo

… or with a comment suggesting that they know what the error is, and can ignore it (but they’re catching everything!).  Find a different shortcut; this is a dangerous one. At the very least, print the stack trace to System.err — that way there’ll be a clue somewhere about what’s going on.

Guideline #2: Only throw an exception in an exceptional case

For example, validation of user input shouldn’t throw an exception — malformed input is normal.  A failed attempt to write to a file might, though; likewise for a method receiving illegal parameters.

Guideline #3: Don’t catch an exception unless you can handle it, or have info to add

If you have to clean up resources even if an error is thrown, use a try/finally block.  Just leave out the “catch”:

[sourcecode language=’java’]
try {
// open the connection, grab our data from the interwebs
parsingConn = openParsingConn();
doThings( parsingConn );
finally {
// close even if we got an exception!
safeClose( parsingConn );

An IOException might be thrown, but code closer to the UI will handle that, and we have no info to add.

If we wanted to include the URL or other relevant info, we might catch it and wrap it in a new exception to include that.

And in the calling code, we’d actually catch this and handle it — showing the user a warning that the connection failed and they should check their connection to the internet, auto-retrying in 5 seconds, or whatever.

Guideline #3: Checked exceptions vs. unchecked

This one doesn’t boil down into a simple guideline so easily.  Firstly, every application should have a “top-level” catch for all unhandled exceptions — both checked & unchecked — that may rollback transactions if necessary, log relevant detail, and notify the user.  Remember that different threads will have their own “top-level”, so any run() methods you have should wrap their contents in a try/catch to handle any exceptions.

That’s your safety net where all exceptions will end up if not otherwise handled.

That assumed, you get to decide whether you want to encourage calling code to handle your exception (checked), or if your exception should probably just go into the safety net cleanly.

Most exceptions, particularly early in your app’s life, are caused by coding errors.  I always let these be unchecked — IllegalArgumentException is useful to throw back, or NullPointerException with a useful message (both are unchecked).  I wouldn’t use a checked exception for something like a parsing action; malformed input means coding error (directly, or unfiltered user input) so in most cases there’s no “handling” for that; it’s a bug.  And an input-sanitizing/checking method wouldn’t throw an exception at all — it’s normal to have malformed user input.

So where to use checked exceptions?  I seem to use them pretty much just for unreliable external interfaces, usually IO-based things in a client app context, where recovery or retry is plausible.  It’s important for the calling code to handle the case when IO fails, because it’ll happen; the network will go down, the file will be locked, etc.. There aren’t very many cases where I’d put my foot down and say “that MUST be a checked exception”, though.

To sum up…

The most important consideration is that exception-handling is a communication with other levels of the codebase, and the developers working there.

Too much communication, or useless communication (declaring a long list of exceptions, exposing internal exceptions that can’t possibly be handled in the calling code, and so on), and you’ll be annoying and quite possibly ignored.   “throws Exception” doesn’t say much.

Too little communication (throwing only unchecked exceptions) and the message won’t get across — and some perfectly manageable, predictable exceptions will end as a frustrating, generic error screen for the user.

And no communication at all (swallowing exceptions) is the worst; when everything goes well, no one notices the problem.  When there’s a problem and you’re under the gun… surprise, something ate all your clues!

Feedback & discussion welcome.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.