June 9, 2007, 10:28 p.m.

Java Exception Antipatterns

I just read Tim McCune's post Exception Handling Antipatterns. IMO, he missed the biggest one:

Checked exceptions.

The whole concept of patterns is a generalization of what most of us do anyway, which is to look for commonalities in design and implementations and try to figure out what things work and what things didn't. To summarize the post, let me quickly list the names of the patterns:

  1. Log and Throw
  2. Declaring that your method throws java.lang.Exception
  3. Declaring that your method throws a large variety of exceptions
  4. Catching java.lang.Exception
  5. Destructive wrapping (throwing away stacks)
  6. Log and return null
  7. Catch and ignore
  8. Throw within finally
  9. Multi-line log messages
  10. Unsupported operation returning null
  11. Ignoring InterruptedException
  12. Relying on getCause()

Now, let's look for patterns in the patterns. I'll start with a quick description of what each problem looked like to me:

  1. Coder caught an exception, thought it would be a good idea to log it.
  2. Coder doesn't know what all checked exceptions might be used, so just declared that any might.
  3. Coder is calling methods with checked exceptions and doesn't know how to handle them.
  4. Coder considers any sort of failure to perform a task the same and ignores the type.
  5. Coder caught a checked exception and needs to throw a different type. Does it poorly.
  6. Coder caught a checked exception and needs to communicate something upwards, so returns an invalid result.
  7. Coder caught a checked exception and didn't know what to do with it. Makes a poor decision.
  8. Coder makes a cleanup mechanism, but the cleanup mechanism isn't completely clean.
  9. No idea how this is related, but coder logs more than one line at a time.
  10. Coder doesn't know how to implement something, returns null.
  11. Coder caught a checked exception and didn't know what to do with it. Makes a suboptimal choice.
  12. Coder caught a checked exception known to have a nested cause. Assumed the nesting depth.

There are a few stragglers in there, but it seems to me that in almost every case, the real problem is that programmers are made aware of possible error conditions to the point of being forced to actually do something about it in a place where in most cases, nothing should be done about them.

In my opinion, #2 and #3 generally provide the best solutions in application code. If your goal is to let a problem bubble up, then why bother stopping it anywhere? Wrapping exceptions does make sense when you are actually trying to add context to a problem, but in a Knuth-esque premature optimization is the root of all evil tone, I suggest waiting for something to actually become a problem before writing code to make it easier to solve.

#1 and #12 have a very interesting relationship. #1 is what happens when you try to offer context without disturbing the exception chain, and #12 is what happens when you are in the habit of disrupting the exception chain to add context, but still want to do something based on the root cause of a problem. However, it's probably not even the root cause you want. Maybe you want something the root cause caused (when wrapping to add context).

I found #11 particularly humorous. Between the article's suggestion and the comments, it seemed that the right thing to do is catch the exception, reset the interrupted state (for those of you not in the know, anything that throws InterruptedException has already cleared the interrupted state of the thread), and jump out of your loop. Anyone catch the irony? In just about any piece of code, the right thing to do has almost exactly the same effect as not catching InterruptedException.

And this is my point. For someone who likes short lists, I propose the #1 anti-pattern which is almost enforced by the jvm, java standards, and lists such as these:

Writing catch blocks when you don't have a really good idea of what needs to happen when a given exception occurs.

Personally, I rather like #2 for application development. It effectively disables checked exceptions throughout the majority of my programs. That means I write fewer catch blocks which, in turn, means almost every other one in the list doesn't apply. I write less code, and in the end, it still does the right thing.

I do see the theoretical value in checked exceptions. They communicate useful information to callers of an API. They also lead to all of these problems and when used properly, add otherwise unnecessary complexity to lots of code. In practice, testing is generally more useful to me for finding and squashing bugs than checked exceptions.

blog comments powered by Disqus