When it comes to bad Java practices, top of my list would have to be catching exceptions like this:
try { // stuff - anything really } catch (Exception e) { // maybe log it if you're lucky }
This is usually done because the "stuff" throws more than one kind of checked exception, and the code to handle each case is the same. So what's wrong with it? The biggest problem is that this not only catches the checked exceptions you're interested in, but also all RuntimeException
s. The vast majority of RuntimeException
s are only thrown from buggy code, and catching them is rarely a good idea. The above catch handles all kinds of exceptions the same way, regardless of whether it was caused by a bug or an ordinary failure that can happen from time to time when a program is running (e.g. a network failure). This only obscures bugs.
Consider the case where a bug causes a NullPointerException
to be thrown - perhaps the most common type of bug in Java. If the catch clause simply logs the exception message (not uncommon), then you have to check the log to have any idea why your app is misbehaving. And if you don't log the stack trace (again, not uncommon), you still won't have a clue what caused it. A much better option is to simply let RuntimeException
s go, which produces a screeching halt in your app and, in most cases, an immediate stack trace dump telling you exactly what happened. The details of what happens when you don't catch a RuntimeException
at all will vary depending on the environment you're running in, but it's always safe to do so. For example, servlet containers usually output the stack trace in the HTTP response when a servlet throws a RuntimeException
.
A good principle of catching exceptions is to only catch the those specific exceptions that you can actually deal with. E.g.
try { // stuff that throws AException // stuff that throws BException } catch (AException e) { // deal } catch (BException e) { // deal }
This way just deals with the exceptions that you can sensibly recover from and lets any RuntimeException
s that indicate bugs to be thrown to the caller. If the code to handle both AException
and BException
is the same, you could always move it to a method.
"But what if the code throws, like, 10 different kinds of checked exceptions?". I've found it's actually pretty rare to have a block of code that throws more than about 4 unrelated checked exceptions. Some of the reflection APIs are pretty bad with this, however. It can be a pain to enter basically identical catch clauses for every one. A good alternative is:
try { // stuff that throws AException // stuff that throws BException // stuff that throws CException // stuff that throws DException } catch (RuntimeException e) { throw e; } catch (Exception e) { // deal with all checked exceptions in the same way }
This is actually not a bad way to get the best of both worlds (although I usually find it clearer to just handle each checked exception individually).
I think in hindsight, the inheritance hierarchy of exceptions is backwards. Instead of the RuntimeException
extends Exception
extends Throwable
hierarchy, it should have been CheckedException
extends UncheckedException
extends Throwable
. That would allow catching all checked exceptions in a single catch clause, while not obscuring unchecked exceptions thrown by buggy code.
In a similar vein, you almost never want to declare a method with throws Exception
. This is basically throwing away the whole checked exception system and saying "this method can throw any kind of exception". This tends to make exception handling in the caller painful, and may give the caller no good option but to declare throws Exception
itself. Before long, you end up with all your methods declared as throwing Exception
and you have little indication of what exceptions you might actually have to deal with at runtime. (There is an argument to made that checked exceptions are a bad idea altogether, but that subject will have to wait for another day.)
There are a few cases where you might have a legitimate reason to catch Exception
, or Error
, or Throwable
. Here are some of them:
- When implementing
Runnable
. ReportingRuntimeException
s thrown fromRunnable.run
is a bit of a pain in Java versions 1.4 and earlier. It's often easier just to catch them yourself and log the entire stack trace somewhere it can't be missed. Java 5 has a nicer mechanism for doing this, however. - When calling some sort of plug-in module. In this case, a bug in the plug-in doesn't mean the whole application needs to abort. You can catch the exception, notify the user, unload the plug-in and move on.
- When dealing with poorly designed APIs that throw
Exception
.
No comments:
Post a Comment