pvillega’s posterous

pvillega’s posterous

Pere Villega  //  Born in Barcelona, living in Dublin, and tagged as geek since youth. Developer in the path to becoming a software architect. I swear this is not a proper blog :)

Feb 16 / 7:25am

Exception Handling

One important aspect of your code is how it handles errors. Long gone are the times of C programming where you had to check global variables or method's return values to detect and identify errors. Exception handling, the new paradigm, is more convenient for the developer, specially the novice one, but it has a downside: is really easy to do it the wrong way. With C-style control error you have a pattern to check for errors. Yes, it's verbose and repetitive, but it's hard to mess with it. With Java, exceptions are much easier to manage and they are enforced by the compiler (at least checked exceptions, the ones you can expect your code to throw back on you) but is so easy to do things like:

  try{

//code

}catch(Exception e){

log.error("Error");

}

A piece of perfectly worthless code. Yes, you catch the exception, but what information is given to identify why it happened? How can you solve the error (in case you have to)? And this kind of code is quite common. Handling exceptions in Java is not as obvious as handling errors in C, but we can generate a list of things to do and things to avoid. That's is my objective in this post.

The basics

One of the most important concepts about exception handling to understand is that there are three general types of throwable classes in Java: checked exceptions, unchecked exceptions, and errors. Checked exceptions are exceptions that must be declared in the throws clause of a method. They extend Exception and are intended to be an "in your face" type of exceptions. A checked exception indicates an expected problem that can occur during normal system operation, like problems with parameters. Unchecked exceptions are exceptions that do not need to be declared in a throws clause. They extend RuntimeException. An unchecked exception indicates an unexpected problem that is probably due to a bug in the code. The most common example is a NullPointerException. Errors are serious problems that are almost certainly not recoverable. Some examples are OutOfMemoryError, LinkageError, and StackOverflowError.

Guidelines to consider

There are some guidelines on how to handle exceptions. They've been told again and again in books, conferences, blogs and whatsoever, but as they are probably ignored by the developers it's not a bad idea to list them again:

  • Throw exceptions early in your methods, so the stack trace is easy to follow
  • Catch exceptions as late as possible, where your program can either meaningfully recover from the exception and continue, or provide the user with specific information.
  • Every error must be reported on screen, to notify the user so it expects misbehaviour of the code. Logs are good to trace the source of the issue, but you won't have people looking at them constantly to detect errors.
  • Don't catch an exception you will throw again, let the method throw it
  • If you won't handle the error, let the application fail. Is better to have an application exception that ignoring there was an error and what caused it.
  • Use a logging system! Output to System.out is slow and unreliable, and is lost eventually.
  • Only log the exception when you handle it. The exception message contains the stack trace so there's no need to log it on each method that receives the error.
  • Add useful information on your log, like values of parameters, any relevant environment variable and so.
  • Don't pack exceptions in a superclass, as it hides information about the real cause of the error. Throwing Exception from a method should be forbidden!

A note about logging systems: If you are using commons-logging or Log4j, the error, warn, info, and debug methods are overloaded with one version that takes only a message parameter, and one that also takes a Throwable as the second parameter. Make sure that if you are trying to log the fact that an exception was thrown, you pass both a message and the exception. If you call the version that accepts a single parameter, and pass it the exception, it hides the stack trace of the exception.

What you must NOT do

The following code hides what happened, so you will never know why your application is misbehaving:

  try{

//code

}catch(Exception e){

}

Next fragment writes on err output, a channel that you will (probably) not check in production and no one will notice the error so it will be ignored while your application crumbles.

  try{

//code

}catch(Exception e){

System.err.println(e);

}

Next code contains a not so uncommon error, adding the error messages into DEBUG level of logging. This is the same issue as the previous cases: when deploying the application you will never know there was an error, so it's useless. Because you don't deploy applications with DEBUG

logging enabled, do you?

  try{

//code

}catch(Exception e){

log.debug("Error!"+e.getMessage());

}

Although the following code is better as we log the issue, we are not notifying the user of the application there's been an error so it will go unnoticed and, even worse, that user won't know why the application starts to behave erratically (and will have "happy" thoughts on ourselves and relatives). We could also add some extra information to the message, like possible sources of the error, to save us time not having to trace all the code.

  try{

//code

}catch(Exception e){

log.error(e.getMessage());

}

This code below uses Exception, a parent class, to throw the error. This hides the real source of the failure making impossible to know what happened and trying to fix it if it's possible.

public void foo() throws Exception {

This use of catch is wrong because it hides the origin of the exception, misleading both developer and user about what happened.

  try{

//code

}catch (NoSuchMethodException e) {

throw new MyServiceException("Blah: " +

e.getMessage());

}

The following fragment is usually wrong (although there may be exceptions), as you are exchanging an exception for an "error value". Is better to ignore that exception so the caller catches it. Also, this code contains e.printStackTrace, a method that its only useful (and not always!) on development if you check the console often. Should be replaced by a log annotation.

  try{

//code

}catch (NoSuchMethodException e) {

e.printStackTrace();

return null;

}

I've never used e.getCause, but it seems some people do, creating code like the fragment below. This is dangerous (and wrong) as the exception hierarchy or the method's throw clauses may change and your code would become useless, but no one will notify you about that.

  try{

//code

}catch (MyException e) {

if (e.getCause() instanceof FooException) {

//code

}

And last a warning: the following code seems correct but it's wrong. The reason is that in a concurrent application (web or even Swing applications as you have all those Swing threads running) both messages can end up far away form each other if other messages are sent between them. Always create one unique log message for each log entry you do to avoid those issues.

 LOG.debug("Using cache policy A");
LOG.debug("Using retry policy B");

One way to do it

It's hard to define a pattern for exception handling. There may be situations where you should log an re-throw an exception for a very good reason (although that's not a common case), and times when you can copy-paste the try-catch block (dont!) because it's repetitive. So more than looking for a pattern just try to avoid the "do not" situations listed above. If you want an example of good exception handling you may look at this code where we are processing a file. The code checks all possible exceptions and acts accordingly:

File prefsFile = new File(prefsFilename);

try

{

readPreferences(prefsFile);

}

catch (FileNotFoundException e)

{

// alert the user that the specified file

// does not exist

}

catch (EOFException e)

{

// alert the user that the end of the file

// was reached

}

catch (ObjectStreamException e)

{

// alert the user that the file is corrupted

}

catch (IOException e)

{

// alert the user that some other I/O

// error occurred

}

Sources

All merits on this post belong to them:

Loading mentions Retweet

Comments (0)