Extending on the previous [exception.app] rule, don't be afraid to create masses of different exceptions beneath a project and within a package.  A single exception type for an application does not give enough granularity for package callers to determine what action to take when something goes wrong.

For example, imagine catching ThereWasSomeKindOfProblemException.  It is impossible without parsing the error message string to determine what change of program flow should occur.

Tip: the approximate rule is to create a new base exception for each significant package in the application, then extend this higher in the package tree as more specific exception types are required.

For example, if there was a report package, there would be a ReportException from which all exceptions beneath that point extended.

SystemModuleException
|
+--ReportException
|  |
|  +--ReportValidationException
|  +--ReportPermissionException
|  +--ReportGenerationException
|
+--SoapException
|  |
:  :

The above example allows callers to differentiate between a user not having access to a report, versus a user not entering enough information to generate the report.  Better still, framework code choose to catch and rethrow the root ReportException if it needs to for exception filtering - see the [exception.rethrow] rule for more information.

So make your exceptions as specific as possible and callers will be able to make an informed decision if they need to deal with them or not.  FailedLoginServiceException is a lot more descriptive than a plain ServiceException which it might extend.  A FailedLoginServiceException would allow calling code to recover from a login issue in a consistently elegant way, and propagate other ServiceExceptions to the code that deals with them.

Of course the downside is that you may need to create a lot of exception types, so try and reuse any existing that are specific and don't cause inter-package dependencies in two directions.

Similarly throws declarations should be as specific as possible, listing all of the subclasses of ServiceException that might be thrown, unless all subclasses of ServiceException could be thrown.  The developer must at the very list javadoc all of the situations where the ServiceException subclasses might be thrown.

An example code below appears to throw a WeatherException, which is the base class of all the weather exceptions.  

/**
 * Checks that the weather webservice is still alive.
 * @throws WeatherException When something unexpected goes wrong
 * @return true if still alive, false if not
 */
public boolean checkHeartBeat() throws WeatherException
{
    :

This code would be better with a little more javadoc on the kinds of exceptions that can occur.

/**
 * Checks that the weather webservice is still alive.
 * @throws WeatherHttpClientCommunicationException all HTTP comms errors
 * @throws WeatherCredentialsException Username or password not correct
 * @throws WeatherServiceException Backend WSDL changed?
 * @throws WeatherException When anything else unexpected goes wrong
 * @return true if still alive, false if not
 */
public boolean checkHeartBeat() throws WeatherException
{
    :

The example is commented to the extreme to illustrate how the user might need to know different kinds of error situations, but in practice there are only ever one or two.  The rest can be placed in javadoc at class level, or documented in the exception class itself.

blog comments powered by Disqus