iWombat.com Exception Handling

 

Contents


Purpose | Contents |

This document is meant to address the issues of Errors, Exceptions, and Exception Handling. There is a great deal of confusion over the topic of Errors and Exceptions, especially with respect to Checked versus unchecked exceptions and when to use them. This document is targeted at the Java software engineering community.


Overview and Definition of Terms | Contents |


There has been a great deal of confusion over the distinction between Errors and Exceptions in some cases no distinction has been made. In an attempt to clarify, the following definitions are offered:

Exception: An abnormal condition or state of execution that may be distinctly identified and dealt with.
Error: A condition or state of an application or utility that requires a message from the application and "actor" intervention.
Furthermore, as a part of the iWombat.com environment certain types of Errors must be logged, while Exceptions do not necessarily carry that requirement. Typically an Exception can be caught and perhaps handled gracefully or, if not, will perhaps result in an Error. Additionally Error messages should be internationalized to I18N standards, Exceptions do not have this requirement.  According to the definitions above, Errors cannot result in Exceptions however, Exceptions can and often do generate Errors.

In general, errors can be thought of as unhandled or unrecoverable exceptions.


The Java programming language's unfortunate use of the terms Error and Exception in the class structure tends to add even more confusion of the subject. In general the class Error is usually ignored and Exceptions are classified as one of the following two:
Checked Exception: An exception that inherits from java.lang.Exception. These exceptions must be explicitly trapped (try and catch block) by the caller of a method. Checked Exceptions are declared in a method's throws clause.
Runtime Exception: An exception that inherits from  java.lang.RuntimeException. These exceptions do not need to be explicitly trapped and are usually not declared in the throws clause.




Types and Scope of Exceptions | Contents |

There are two base classes of iWombat.com exceptions WombatException (checked exception) and WombatRuntimeException (runtime exception).
Note: These classes have yet to be included in the open-source package

Each package should implement its own package-appropriate exceptions that extend either of the base classes as is appropriate to the purpose of the exception.


Checked Exceptions vs. Runtime Exceptions | Contents |

In general, throwing Checked Exceptions should be rare, since it requires a strict contract between the caller and callee for exceptional (non-normal) behavior. It also has some profound impacts on how abstraction layers must be build to incorporate not only the class heirarchy, but the exception hierarchy as well. Typically, Checked Exceptions need only be thrown if the operation is fairly atomic, such as allocating or creating a resource, and the operation has some amount of likelihood of being gracefully handled by the caller.  If the action is non-atomic, or unrecoverable a Runtime Exception should be thrown.

Also, runtime exceptions should not be propagated up the call-stack by rethrowing the Checked Exception.

Checked Exception Rule of thumb: If an Checked Exception cannot be handled by the immediate caller. The checked exception should be re-thrown as a "layer-appropriate" runtime exception.

                public String getSomeProperty() {                         
                try {
                HelperClass helper = someHelperClass.getResource(resourceId);
return helper.getProperty();
} catch(HelperClassException e) {
                    return someHelperClass.createNewResource(resourceId);
}
                }

public String getSomeProperty() throws MyCheckedException{              
                try {
                HelperClass helper = someHelperClass.getResource(resourceId);
return helper.getProperty();
} catch(HelperClassException e) {
                    throw new MyCheckedException("Couldn't get the resource " + e.getMessage(), e);
}
                }

The last example assumes that the getSomeProperty() is an atomic operation and failure has some reasonable chance of recovery by the caller.


Working with the Base Classes | Contents |

You'll find the two base classes mentioned in Figure-1 in the com.iwombat.foundation package. There are several features available for exploiting in these classes.

1) Originating Throwable. Both of these classes contain behavior for capturing the original Throwable encountered and rethrown. You should make use of this feature by either including a constructor that included the throwable. Or setting the throwable (via. setThrowable() ) immediatly after creating a derived class.

2) Tracing and Debugging. Both of these classes are linked into the Logging infrastructure and have varying log behaviors based on the level of debugging specified at runtime. Form your exception messages apropriately to aid in debugging and tracing.

Location of Exceptions | Contents |

Exceptions should be located in the same directory and package as the classes and interfaces that throw them. Each package defining a major component should have its own set of exceptions that are exclusively thrown from any interface. For instance, no package should be throwing a SQLException to any external client. Rather a package appropriate exception should be thrown instead. These exceptions should be located in the same directory as the highest-level component interface that could potentially throw it.


Handling Exceptions | Contents |

In general there are two possible outcomes for handling an exception:

  1. Recover and retry
  2. Log and abort
With the possible exception of the presentation layer, fully 95% of the time #2 is the likely outcome for any exception encountered. Any implementation around outcome #1 should be scrutinized carefully. Graceful exception recovery carries huge implications.  As a result, any code that attempts a graceful recovery from an exception should immediately be submitted for peer review. This is not to say that there can be no graceful recovery, simply that a lot of thought needs to go into any such solution.

Note: An all-to common third option, print the stack and continue, is NOT an acceptable way to handle an exception.

 

The Exception Handling Hall of Shame | Contents |


Example 2 - SEP (Somebody Else's Problem)
  public String someMethod() throws Throwable {
// do a pile of stuff here without a try/catch block
}

Example 2 simply cops out of handling any checked exceptions and passes the responsibility on to the caller. The caller is even less likely to be able to recover gracefully than the someMethod() method. This method should have a try block and rethrow any exceptions as runtime exceptions if it can't recover gracefully.

Example 3 - Dump the Stack trace and continue

  public String someMethod() {
XMLDecoder decoder = null;
Config conf = null;
try 
{
String myconfig = "http://config.iwombat.com/config.xml";
URL configDoc = new URL(myconfig);
URLConnection configConnect = configDoc.openConnection();

decoder = new XMLDecoder(configConnect.getInputStream());
conf = (Config)decoder.readObject();

} catch (MalformedURLException e) {
e.printStackTrace();
System.out.println("We died reading a URL");

} catch (IOException e) {
e.printStackTrace();
System.out.println("We died reading an object");

} finally {
decoder.close();
}
}

Example 3 simply dumps a stack trace and continues along as if nothing bad ever happened. This is potentially worse than that of example 2. It does have a nicely implemented finally block though.


Example 4 -  Handle everything and bail

  public String someMethod() {
XMLDecoder decoder = null;
Config conf = null;
try {
String myconfig = "http://config.iwombat.com/config.xml";
URL configDoc = new URL(myconfig);
URLConnection configConnect = configDoc.openConnection();

decoder = new XMLDecoder(configConnect.getInputStream());
conf = (Config)decoder.readObject();

catch (Throwable t) {
throw new MyRuntimeException("Got an exception");

finally {
decoder.close();
}
}

Example 4 attempts to treat every exception exactly the same, even runtime exceptions. This example removes any meaningful context for tracing the exception that was originally thrown. While the least destructive of all the "hall of shame" examples it is still very bad practice.