Every Java developer naturally encounters situations in her or his programming life, where the question arises: Why do I have to deal with this checked exception? Wouldn’t it it much less work, if that exception had been designed as a runtime exception?

Creating a String from a byte array with the default and always supported character encoding UTF-8 is one example:

try {
 ... = new String(myByteArray, "UTF-8");
}
catch(final UnsupportedEncodingException e) {
  throw new IllegalStateException("Will never happen.", e);
}

Dealing with URLs may also be sometimes annoying:

try {
  final String iKnowThisIsAValidUrl = ...;
  final URL url = new URL(iKnowThisIsAValidUrl);
  ...
}
catch (final MalformedURLException e) {
  ...
}

Exceptions are useful. They detach the location, where the problem has been encountered, from the location, where it is handled. But shouldn’t we just use runtime exceptions, since checked exceptions are just annoying?

Many authors consider checked exceptions as a design failure (e.g. Bruce Eckel and Rod Waldhoff) or discuss their use (e.g. Rod Johnson, Neal Gafter, and – very interesting read: Brian Goetz).

What makes checked exceptions useful?

Catch what you cannot see

If an API provides a method without documentation and without declaring runtime exceptions, developers can hardly determine, how this interface will react, when leaving the happy path. Even a source code analysis may not be helpful, since runtime exceptions can bubble up from anywhere underneath. You cannot catch what you cannot see.

Even if the runtime exceptions are declared and/or documented, the compiler and IDE may not help you much using the API. If you are using a method that declares a checked exception, your development environment will urge you to deal with it.

Thus, checked exceptions can make API use easier, since they make the intended usage explicit: If the check is useful to be noticed and handled by the caller.

But what are good rules to design exception handling for a library or application?

Faults and Contingency Exceptions

In his article Effective Java Exceptions Barry Ruzek advises to distinguishes between

  1. Fault
  2. Contingency

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

ConditionContingencyFault
Is considered to beA part of the designA nasty surprise
Is expected to happenRegularly but rarelyNever
Who cares about itThe upstream code that invokes the methodThe people who need to fix the problem
ExamplesAlternative return modesProgramming bugs, hardware malfunctions, configuration mistakes, missing files, unavailable servers
Best MappingA checked exceptionAn unchecked exception

Source: Mapping Java Exceptions, Barry Ruzek

Sometimes exceptions are thrown because preconditions are not met. This is caused by passing invalid arguments to the method call. This is usually a kind of programming error and therefore something to be handled as a fault. It may also be a form of validation problem. If this call is initiate by the system, this is a system problem (e.g. inconsistent data) and usually a fault. If the call is initiated by a user input, this is defined as a contingency. The user is informed of the validation problems and may give it another try.

Technical and Business Exceptions

Dan Bergh Johnsson defines technical and business exceptions in his article Distinguish Business Exceptions from Technical:

TermDefinition
Technical ExceptionProblem that prevents us from using the application.
Business ExceptionProblem that we get informed of and therefore prevents us of misusing the application.

This definition matches that of faults and contingencies. A technical exception is a fault, a business exception a contingency. I favor the terms fault and contingency, because I think it is too easy to get distracted by the term technical, where someone might think of IOException or SQLException and therefore associates it with the resource layer. The term business is easily associated with the business layer only. While this is a clear misinterpretation not intended by the author, the term fault is neutral. A fault, a defect that can cause an error, may occur on any layer. A contingency either.

My Contingency is your Fault

Is it a failure to have e.g. IOExceptions declared as checked exceptions? I think not. From a libraries perspective an attempt to read from a stream that fails, is a contingency the client code has to deal with. Having the compiler check that is really a good idea, since this problem cannot be overlooked.

Is it a good idea to let the IOException bubble up the call line within an application? I think not. The contingency has to be handled inside the library. If this is not possible, the API designer may want to map the contingency exception to an unchecked or checked exception. Within an application, one would usually see the problem as a fault and therefore propagate it as an unchecked exception. All intermediaries are now free to handle it or not. The application may deal with it in the presentation layer, where the problem is displayed to the client.

Context Information

Exception chaining in this situation is usually a good thing. It allows you to add context information to the caught exception that is not available at lower layers (Contexts and Dependency Injection may give an alternative solution to this problem). It is somewhat laborious:

try {
  ...
}
catch (final OtherException e) {
  throw new MyException(e, some, additional, info);
}

But it makes the problem clear for the people who have to analyze it. If OtherException is a checked exception, adding context information cannot be overlooked. If it is an unchecked exception it may. In an application, where all faults are rooted in a single unchecked MyFaultException, you may deal with these problems where it best fits, without needing to declare checked exceptions on every method. If there is context you may want to propagate you can always use:

try {
  ...
}
catch (final MyFaultException e) {
  throw new MySpecificFaultException(e, some, additional, info);
}

Plenty of declared Exceptions?

In theory there can be plenty of exceptions occur within a component. Each of them has to be documented as part of the API. Having them all rooted in one parent exceptions makes the catching easier, but they still have to be declared individually. As long as dealing with them, checked or unchecked, makes sense from the client’s point of view.

void checkPortfolio(Customer customer, CompanyShare share)
  throws NullPointerException,
         DatabaseException,
         SecurityException,
         ...
         ShareNotPartOfPortfolioException {
...
}

In practice this is not a great deal. If it is a fault, its a runtime exception which may be declared by its parent exception type. If a method is doing just one thing, there is often just a few exception types that are relevant to the client.

void checkPortfolio(Customer customer, CompanyShare share)
  throws NullPointerException,
         FaultException,
         ShareNotPartOfPortfolioException {
...
}

The client is usually not interested in the actual fault. Knowing that a fault has been encountered is all that is relevant to the client. Therefore the client deals with the FaultException only. Declaring and documenting it allows developers to keep possible problems in mind to decide to deal with it or not.

Conclusion

Runtime and checked exceptions have both their advantages, serving different requirements. Faults and contingency exceptions should be considered in designing exception handling. These concepts can be easily applied to smartics Exceptions. You may have a look at Best Practices Designing Exceptions or read the article What is smartics-exceptions all about? for an introduction to designing exceptions with smartics Exceptions.


Link

Link

Posts

Tagcloud

Loading tagcloud ...