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:
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.
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.
Example -1 Checked vs. Runtime Exceptions:
Incorrect
public String getSomeProperty() throws HelperClassException {
HelperClass helper = someHelperClass.getResource(resourceId);
return helper.getProperty();
}
Correct
public String getSomeProperty() {
try {
HelperClass helper = someHelperClass.getResource(resourceId);
return helper.getProperty();
} catch(HelperClassException e) {
throw new MyRuntimeException("Couldn't get the resource " + e.getMessage(), e);
}
}
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.
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.
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.
In general there are two possible outcomes for handling an exception:
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.