What should be considered when writing new exception classes?
Prior to writing a new exception try to reuse existing ones. Code reuse is one of the primary goals of software development. But reuse must be based on semantics, not on the name. So be sure to check the documentation to the exception prior to using it.
As for any class, method or variable, choose a meaningful and precise name for your exception class. It is often useful to remind the developer that an exception is a subclass of another exception by a naming convention, although this is not always possible.
If reusing an exception as is is not an option, consider to subclass an existing exceptions. This makes it easier for clients to catch this kind of exception. Grouping exceptions by sub-classing is always a good idea.
It is very helpful to have an exception class as the root for all exceptions of an application. Subclasses handle different aspects of exception cases of this application. But how deep should this hierarchy be? Michael Feathers states
"Use different classes only if there are times when you want to catch on exception and allow the other one to pass through." Clean Code, p. 109
The exception codes of smartics-exceptions help to follow this rule. You simply have one exception class that distinguishes the particular exception event by the code number. But if you ever feel like checking the code in a catch clause you must write a new exception subclass.
Another reason to write a new exception class is, if the context of the exception event provides useful and specific information.
To have two reasons to create a class is somewhat odd. Therefore you might want to separate the type of an exception to be caught from the information that is provided by the message. Please refer to Separation of Exception Type and Information for details.
Basically there are three categories of exceptions.
Please refer to Consider to use Faults and Contingencies for information on how to design runtime and checked exceptions based on these concepts.
Create a checked exception by inheriting from Exception, if it cannot be prevented by proper use of the API and it describes a situation where clients usually can recover from. Only in this case the user of a method throwing the exception will benefit from catching it. If it's benefit is not obvious or if the situation is recoverable, favor unchecked exceptions.
Checked exceptions
Create an unchecked exception by inheriting from RuntimeException for unrecoverable error situations, where the client to a service cannot do anything to deal with the problem. This kind of exceptions are also often used to indicate precondition violations.
By convention new Error class should not be created. Derive from runtime exception instead.
Never inherit from Throwable directly.
In his article Effective Java Exceptions Barry Ruzek advises to distinguishes between
in an exception design.
Where faults basically are used to signal problems that usually cannot be handled by the client (such as database connection or IO problems), contingency exceptions model business problems (like insufficient funds or overdraft exceptions). Barry Ruzek summarizes
Condition | Contingency | Fault |
---|---|---|
Is considered to be | A part of the design | A nasty surprise |
Is expected to happen | Regularly but rarely | Never |
Who cares about it | The upstream code that invokes the method | The people who need to fix the problem |
Examples | Alternative return modes | Programming bugs, hardware malfunctions, configuration mistakes, missing files, unavailable servers |
Best Mapping | A checked exception | An unchecked exception |
Source: Mapping Java Exceptions, Barry Ruzek.
Make your faults class inherit from AbstractLocalizedRuntimeException and your contingency exception class from AbstractLocalizedException.
For more information about faults and contingency exceptions, please refer to Barry Ruzek's article.
Exceptions should provide information to tell the client of the service what went wrong and how to deal with the problem. This is important for checked exceptions as well as unchecked exceptions.
If not all instances of an exception provide the same information, make two subclasses. Do not let the client of you exception guess which properties are provided.
Do not be tempted by laziness and add a property to the existing exception class that is only meaningful in some cases.
All fields of an exception should be final so that information cannot be changed after the exception is thrown.
There is a Checkstyle rule to check for MutableExceptions.
Exceptions are serializable. Be sure that every property you add to your exception class is also serializable.
Please refer to Testing Serializability for a tip on writing tests on serializability.
The message printed to the log file should be meaningful and to the point. While Joshua Bloch states
"Lengthy prose descriptions of the failure are generally superfluous; the information can be gleaned by reading the source code.
The detail message of an exception should not be confused with a user-level error message [...]. Unlike user-level error message, it is primarily for the benefit of programmers or field service personnel [...]." Effective Java, p. 254
We deem it important that the context of the problem is obvious without knowing the source code.
Therefore we allow to provide information at a number of levels:
For further information on textual presentation of failures you may have a look at chapter 4 of GUI Bloopers, especially Blooper 28: Vague error messages.
Most exceptions - with an exception of certain presentation tier exceptions - are targeted at the service personnel and software developers. Therefore it is important to given them as much information possible to spot and fix the problem timely.
For smart exceptions the keys mentioned above are defined in MessageType. A code properties file may contain the following keys:
1000.title=... 1000=... 1000.details=... 1000.implications=... 1000.todo=... 1000.url=http://www.examples.com/information-system/system-messages/message-1