Exception Usage | |
Author: Greg Hinkle (ghinkle at sapient.com) Version: $Revision: 1.4 $($Author: ghinkl $ / $Date: 2003/06/26 18:56:35 $) Created: March 2003
|
| |
|
This document is designed to help developers understand and
utilize the exception framework of Carbon. This will include utilizing the
exceptions supplied with the Framework as well as creating new business and
technical exceptions specifically for a project.
|
|
There are two types of exceptions in Java,
Exception and RuntimeException .
These are handled differently by the Java Compiler and the Java Language
Specification, in that the former is considered a "checked" exception
and the latter is considered a "runtime" exception. "Checked
exceptions" are regarded as a specific part of an interface and include
compile-time checks to ensure code compatibility. When it is possible that a
method may throw a "checked exception", that method must
declare it in the throws clause. Runtime exceptions, by contrast, can
be thrown from any method and will simply propagate up the stack until they are
caught.
The importance of the above discussion is clear from a
design perspective and comes into play when determining what the interface for
a class should be. Should a class declare that it throws certain exceptions?
What about exceptions that are thrown by libraries used within the class?
|
|
| |
The concept of exception levelization is based on the
belief that encapsulation is a useful ideal. Encapsulation is the inclusion of
one implementation within another so that the included functionality is not
apparent. In theory, this simplifies the usage of the ecapsulating
service, while allowing future modifications to that service so that its
implementation may be changed.
A good example of the idea of encapsulation is the
Configuration Service. The primary implementation of the Configuration Service
is based upon file storage and therefore uses the file I/O APIs of the SDK. Some
of these APIs throw FileNotFoundException for example
and therefore might be a candidate for what the Configuration Service would
throw to indicate failure. The problem with this, is that the external interface
of configuration would be tied to a file-based implementation and would require
familiarity with that underlying implementation for the client programmer to
understand the problem. With an encapsulated model, this underlying implementation
is hidden behind a façade, and might instead return a
ConfigurationNotFoundException . This would, for example,
then allow later support for a database-backed configuration storage system in
which an empty JDBC result might also be returned as a
ConfigurationNotFoundException .
|
One of the best practices used in the Framework Core is the
concept of creating an abstract package level exception that can serve as a base
class for many specific exceptions in the same package. One of the primary
benefits of this design is that it allows client code to catch exceptions at
various levels of granularity, including ones that span across many service calls.
|
|
As described above, checked exceptions contribute to the
declaration of an interface whereas runtime exceptions are automatically passed
up the stack until they are caught or they reach the top. The primary decision
to use checked versus runtime exceptions comes down to whether you want to force
client code to deal with an exception situation or not. By default, most new
exceptions should be checked exceptions as you can often use existing runtime
exceptions when they are needed. Checked exceptions, since they are part of the
interface, should be of a type that makes sense to their condition. They should
also include all relevant information in a manner that allows the client
to access that data.
If you are throwing an exception for an abnormal condition
that you feel client programmers should consciously decide how to handle, throw
a checked exception.
Included runtime exceptions
The Framework ships with a few Runtime exceptions that will
likely be used often on project teams. These exceptions are explained below are
most often the only runtime exceptions that are needed on a project.
InvalidConfigurationException
- This exception is designed to be thrown when there is a configured value
that is not valid. This could happen to any service that bases its information
on data from the configuration service and is generally considered a serious
condition. This exception signifies that data from the configuration service
must be altered to be valid before the current mechanism or function point
will work.
InvalidParameterException
- This exception is often used in pre-condition checks that may take place
at the entry point to a service. It is designed to be thrown when a parameter
to the current method was of an invalid type or had an invalid value.
|
|
Exceptions are Java objects that are very similar to any
other object in the system. They are polymorphic and can have data that relates
to them. One of the biggest mistakes in creating an API is when not enough time
is focused on modeling the data of exceptions. When there is a failure to
retrieve a Configuration, the exception should include where it was searched for
and what name was searched for, and not as a big string message. They should be
internal String variables that are accessible by client code.
|
|
Below you will find an example abstract exception.
Example Abstract Exception
import org.sape.carbon.core.exception.BaseException;
import org.sape.carbon.core.util.classify.SeverityEnum;
public abstract class AbstractConfigurationException extends BaseException {
public AbstractConfigurationException(Class sourceClass, String message) {
super(sourceClass, message);
}
public AbstractConfigurationException(
Class sourceClass, String message, Throwable cause) {
super(sourceClass, message, cause);
}
public SeverityEnum getSeverity(){ return SeverityEnum.TRACE;}
}
Below you find an extension of the abstract exception.
Notice the rich interface to this exception that may allow someone to better
deal with the exception and perhaps debug it easier.
Example Specific Exception
import org.sape.carbon.core.exception.BaseException;
import org.sape.carbon.core.util.classify.SeverityEnum;
public class DataInvalidConfiguration extends AbstractConfigurationException {
String configurationDocument, invalidDataKey;
Object invalidDataValue;
public DataInvalidConfiguration(
Class sourceClass,
String configurationDocument,
String invalidDataKey,
Object invalidDataValue,
Throwable formattingFailure) {
super(sourceClass,
"Failure to store configuration document [" +
configurationDocument + "] because key [" +
invalidDataKey + "] had an invalid value.",
formattingFailure);
this.configurationDocument = configurationDocument;
this.invalidDataKey = invalidDataKey;
this.invalidDataValue = invalidDataValue;
}
public String getDocumentName() {
return this.configurationDocument;
}
public String getInvalidDataKey() {
return this.invalidDataKey;
}
public Object getInvalidDataValue() {
return this.invalidDataValue;
}
public SeverityEnum getSeverity(){ return SeverityEnum.INFO;}
}
|
|
| |
Exceptions can typically be split between whether the occur due to a
technical failure or the violation of a business rule. These two types
of exceptions can have very different usage patterns and it is valuable
to have a different mechanisms for them. While both business and
technical packages can benefit from the encapsulation provided by
levelization, there are different ways this can be accomplished.
|
Technical services are typically not valuable in their own right, but
rather makes sense as part of a system that provides busines value.
In these cases, the technical services are usually encapsulated
behind the business services using them. In Carbon, a best practice
has been to allow users to pass in a Source Class to the
exception when creating one. This is because it is often useful
to see technical service failures categorized by where they happen
rather than what service they are. Technical service failures
are more often related to the way in which they are used.
|
|
With business services, this capability is less meaningful as a
business failure is often traceable directly back to the implementing
business code. In this case, filtering the tracking of these exceptions
is best done simply by the exception class itself. When building
business exceptions, you can just hardcode the exception class
in the call to the superclass constructor.
Example business exception
import org.sape.carbon.core.exception.BaseException;
import org.sape.carbon.core.util.classify.SeverityEnum;
public class AbstractAccountModuleException extends BaseException {
AccountId accountId;
public AbstractAccountModuleException(
AccountId accountId,
String message,
Throwable levelizedFailure) {
super(getClass(),
"Business rule failure on accoung [" +
accountId + "] due to [" +
message + "].",
levelizedFailure);
this.accountId = accountId;
}
public AccountID getAccountId() {
return this.accountId;
}
public SeverityEnum getSeverity(){ return SeverityEnum.WARNING;}
}
The example exception above is a module-level abstract business exception.
Subclasses of this exception would be created for specific business service
failures, but would not need to supply a source class.
|
|
| |
Exceptions are a primary concept in an application and
provide the capability to track and understand what is failing in the system.
They may alert administrators to serious errors or they may point out common
mistakes that users are making with a user interface. Either way, there is often
a lot to gain from being able to track and gather information on exceptions and
a primary mechanism for this would be the logging of those exceptions. In order
to filter out the interesting information from useless and expected exceptions,
the exception system uses the same filtering mechanism as the primary logging
service. Exceptions therefore have both a source and a severity that
are based on Class object of the calling code and the
org.sape.carbon.core.classify.SeverityEnum class respectively.
The default exception delegate will then attempt to log the
information of all exceptions to the default logging service utilizing the
source and severity information as standard filter data. This allows the logging
system to route and filter exceptions just like any other log message.
|
Below is a description of the various Severity levels
supported in the basic SeverityEnum of the Carbon
Framework. It should be noted that technical services are never logged at
a level higher than info. These services are designed merely to be the backend
to other business services and therefore do not want to be handled in a manner
more severe than the business problem at hand. It is very important to follow
this guideline and be consistent and careful about how severity levels are
assigned. A badly placed severity can significantly damage the ability for log
files to serve as a good debugging tool.
For further details on logging severities
please refer to
Logging
- Fatal
- Should be used when the system encounters a critical
error which affects the accuracy, integrity or capability of the
system as a whole, not just the current request/transaction.
- Error
- Should be used when the system encounters an error which
it could not handle and was forced to terminate the current operation,
but concurrent and future requests should not be affected.
- Warn
- Should be used when the system encountered a recoverable
error, which should non-the-less be logged as its occurrence is
significant.
- Info
- Should be used for moderately detailed information needed
to debug system problems.
- Debug
- Should be used for all interesting periodic events so
that anyone browsing the logs should be able to see the amount and
kind of processing occurring in the system.
- Trace
- Should be used for high verbosity debug logging that
could significantly affect the performance of the system.
|
|
|