|Octopull/Java: Exceptional Java|
|Home||C++ articles||Java articles||Agile articles||Bridge lessons|
It must be approaching twenty years since I visited the computer science department of the local university and on one of the notice board spotted a chart for the next decade of development in the industry. The feature of this that I remember was that every odd year predicted "the end of Fortran" and every even year "the end of Cobol". As the author (and I) expected, these languages are still thriving far beyond the end of that chart.
While to work in the software industry is to be exposed to constant change there is much that is constant in spite of that change. While we are regularly exposed to new tools (technologies, languages, techniques, etc) our existing knowledge and tools remain stubbornly useful. This is especially true when we distinguish the fundamental ideas from work-arounds for a specific tool.
For some years I've been very happy with the use of exceptions in C++ programs  . Recently I accepted a position at a company working primarily in Java, and consequently had to address the problems being encountered by developers using this language.
Before I go on to describe the problems encountered by my new colleagues, let me revisit the "received wisdom" of the Java development community. This is represented clearly by the following quote from "Thinking in Java"  (similar views are expressed by other sources):
Keep in mind that you can only ignore
RuntimeExceptionsin your coding, since all other handling is carefully enforced by the compiler. The reasoning is that a
RuntimeExceptionrepresents a programming error:
An error you cannot catch (receiving a null reference handed to your method by a client programmer, for example).
An error that you, as a programmer, should have checked for in your code (such as
ArrayIndexOutOfBoundsExceptionwhere you should have paid attention to the size of the array).
It is sensible to use
to report programming errors - the availability of a call-stack
aids reporting them meaningfully. The fact that they can be
handled far up the call stack makes implementing an
application-wide policy for handling them easier than trying to
do so at every point an error is detected. (Examples of
policies from different types of application domain are: to
abort the current operation, to restart the subsystem, or to
terminate the process.)
However, the "received wisdom" very clearly directs a
developer towards using ordinary, checked exceptions (i.e.
those not derived from
) in the design of an application. The use of
is discouraged: "programming errors" do occur; but - beyond
having a policy for dealing with them when detected - we should
not be creating a design to cater for them! (Trying to cater
for bugs only leads to hard to test code that is, itself, a
breeding ground for bugs.)
The developers that I joined had attempted to apply this guidance and run into a number of problems. However, because of pressure to "just write the code" no attempt had been made to formulate a workable design policy. Letting developers struggle on independently can waste a lot of time over the course of a project - so I called a meeting of the programmers on the team I was working with to discuss the problems they were having with exceptions.
We'll be examining more of the problems they described in the rest of this article - this section deals only with those that bear directly on the above theory (that checked exceptions should be used for everything that isn't a programming error). Before proceeding, I want to make it clear that this development group isn't the only one to experience these problems. They are in good company - as I confirmed at the ACCU Spring conference last year. It seems that this theory doesn't work in practice.
The relevant problems described fall into three categories:
Consider the example of a factory method that is
responsible for creating objects. From the point of view of
the client code there is no obvious reason why it should
fail - so the interface doesn't have a throws clause. From
the point of view of an implementation that retrieves
objects from a database it is necessary to handle
. For the sake of this discussion let us assume that these
reflect something catastrophic - like connectivity to the
database being lost.
we are considering are not the result of programming
errors. Accordingly, we are exhorted not to propagate them
as unchecked exceptions. On the other hand we cannot handle
them locally (except by the unhelpful expedient of
returning a null reference). This leaves two options:
either adding a throws clause to allow the factory method
to propagate an
or throwing our own exception (normally one that "wraps"
the original exception).
Either approach places a burden on the client code - which will generally be in no better position to handle the error than the factory method itself. (Clearly, this is an iterative arguement; but, somewhere far up the call stack there will be some code that manages the error.)
The strategy of "wrapping" exceptions prior to propagating them, alluded to in the last section, has the unfortunate side effect of making it difficult to detect the distinction between different problems programmatically. Essentially, one ends up with the situation that all that the programmer can be sure of is that "something went horribly wrong". Admittedly, that is often enough but it occasionally limits options. For example, how can one decide if is it worth retrying the operation that failed?
The alternative strategy (of allowing the original
exceptions to propagate) can lead to a similar loss of
information. Writing multiple, nearly identical, catch
blocks is tedious and a potential source of the familiar
maintenance issues caused by "cut and paste". Sooner or
later, someone just writes "
catch (Exceptions e)
" - forgetting the (usually unintended) side effect that
this also catches
Depending upon whether exceptions are allowed to
propagate or are "wrapped" two things can happen. Either,
an increasing and incoherent set of exceptions begin to
appear in the throws clauses of methods dependent on others
- until developers gets fed up with this insanity and
". Or, nearly every method has code to catch exceptions
propagated from the methods it depends upon, "wrap" them in
a new exception that makes sense within its interface and
throw the new exception.
The theory sounds bad enough but, in practice, there is another thing that happens (although no developer admits to doing this intentionally). That is to consume an inconvenient exception by catching and ignoring it.
If these problems that occur in practice are not enough to
justify considering alternatives there is a further difficulty:
you may be coding to a function signature that doesn't allow
you to throw any checked exceptions. This can happen when you
are implementing an interface that you don't control (for
In C++ I use exceptions to report problems that it is unreasonable to expect the immediate client code to deal with. In particular, the example of the factory function described above seems to satisfy these criteria: The part of the system that knows how to deal with loss of the database connection is likely to be many layers away from a factory function that retrieves objects from a database.
The problems related in the last section suggest that this
approach isn't working - so either the approach is wrong or it
is being implemented incorrectly. There are many differences
between C++ and Java but there are also lots of similarities
between them. Specifically, there are enough similarities in
the exception handling mechanisms that I'd expect to use Java
for the same things that I'd use C++ exceptions for.
What the problems identified above illustrate is the ways in which Java checked exceptions are not like C++ exceptions. Declaring a checked exception places an obligation on the caller of a method to do something explicit with the exception - and that is precisely what isn't desired. What is desired is to transfer program flow in an orderly manner to some point far up the call stack.
There is something in Java that looks far more like my
familiar C++ exceptions than Java's checked exceptions: Java's
unchecked exceptions. To me they looked like the answer - it
doesn't take much thought to conclude that approaching the
above scenario by wrapping the
in an unchecked exception causes none of the above
I outlined this approach at the meeting, and there was general consensus that it made sense, but a number of concerns were expressed: mostly regarding the choice that exists between the two exception-handling mechanisms. One of the senior team members agreed to use his notes from the meeting to draft a guideline "exceptions strategy" paper. This would be reviewed at a subsequent meeting when everyone had had an opportunity to think about it. (It is a good idea to review such solutions a few days later - it is very easy to be seduced by an attractive idea and overlook a killer issue during a brainstorming session.) The current version of this paper is included below.
Later on that day I was approached by one of the more thoughtful team member who was concerned that while he couldn't see anything wrong with what I was suggesting he couldn't find any books - or reference material on the internet - that agreed with it. I'm always pleased to be approached like this as I can be wrong, and much of the value of such meetings is lost if people don't think and research for themselves.
In this case, I view this as a reflection of the immaturity of the Java community - the hype surrounding the language often gets in the way of recognising an issue and finding a solution. It took the C++ experts from 1990 (when they were introduced as "experimental" in the ARM  ) to 1997 (when the C++ standard library was revised to specify its behaviour in the presence of exceptions) to get a consensus on how to use exceptions.
I knew from my experience with C++ that there are ways to write code that works in the absence of the compiler prompting the developer to deal with exceptions. Indeed, I knew the fundamental ideas used in managing exceptions in C++ could also be applied to Java: I presented a translation of them at the ACCU conference 2001  . (Anyway, it isn't the first time I've disagreed with authority - and it surely won't be the last!)
There was one more significant problem that had been
observed in a number of existing systems. In these, it had been
found to be difficult to handle the errors reported via
exceptions effectively. This was believed to be a result of
every type of failure reported being reported using the same
exception type - albeit with different message text. (If this
sounds to you like the
package and ubiquitous
then you won't be surprised that this was mentioned.) The
problem was actually worse than with the
package since many parts of the system delivering differing
types of functionality threw this same exception, and there was
no equivalent of the well established (and fixed) set of
values to deal with.
To address this we concluded that there needed to be guidance covering the choice of the specific exception to use. By requiring any exceptions specified in throws specifications to belong to the package that propagates them we hoped to discourage this habit. And, by checking conformance to the guidelines as part of the class design review, we encouraged a careful consideration of the contract between client code and implementation.
Following the review meeting the team adopted the suggested policy document. (It was updated to clarify it then and a couple of times later, but has remained close to the original discussion.) It reads as follows:
There is at present no clear-cut policy for Java Exception Handling within any of the current OPUS Java systems. This has caused inconsistencies in the use of Exception Handling and these have resulted in problems.
This document addresses the use of two categories of exceptions: checked exceptions and unchecked exceptions.
Checked exceptions provide a mechanism for ensuring that the caller of a method deals with the issue they report. (Either by explicitly handling the exception, or by propagating it.)
Unchecked exceptions should only be considered for "long-distance" exception propagation. (To enable reporting of fairly catastrophic events within the system.)
To support these options all exceptions raised within the system will be subclasses of either
Exception) or of
RuntimeException). These provided the facility to wrap exceptions.
It is the responsibility of the Class Designer to identify issues that would result in a checked exception being thrown from a class method. Those reviewing the class design check that this has been done correctly. Exception specifications are not changed during implementation without first seeking agreement that the class design is in error.
Exceptions that propagate from public methods are expected to be of types that belong to the package containing the method.
Within a package there are distinct types of exception for distinct issues.
If a checked exception is thrown (to indicate an operation failure) by a method in one package it is not be propagated by a calling method in a second package. Instead the exception is caught and "translated". Translation converts the exception into: an appropriate return status for the method, a checked exception appropriate to the calling package or an unchecked exception recognised by the system. (Translation to another exception type frequently involves "wrapping".)
Empty catch-blocks are not used to "eat" or ignore exceptions. In the rare cases where ignoring an exception is correct the empty statement block contains a comment that makes the reasoning is behind ignoring the exception clear.
This policy seems to have worked well both in terms of being followed without much difficulty by the original team members and for induction of new team members. Subsequently, other teams have also adopted it. To that extent it has been a success. However, it isn't the end of problems with the use of exceptions.
The remaining problems are much more manageable and fall into two categories:
Catching exceptions at too low a level - rather than allowing them to propagate, they are caught by a piece of code that doesn't have sufficient context to deal with them effectively; and,
Catching too general a range of exceptions - for
example, rather than catching
separately, and handling each, there is a single catch
that then uses
to identify the exception type. Sometimes such code fails
to take account of the possibility of
- and "eats" them.
These problems are not as widespread as those reported originally - indeed the majority of developers on the project are unaware of them. Both of these issues reflect poor coding technique and can be addressed by educating developers. This education could have occurred seamlessly as part of the project had we instituted code reviews; but I chose to postpone introducing them in favour of other process changes.
Java developers are rightly encouraged to use unchecked exceptions with caution. However, the current wisdom is too extreme. Unchecked exceptions in Java correspond to exceptions in C++, Smalltalk and C# - and shold be used in the same way: sparingly.
It seems that I'm not the only one to have doubts about this. Shortly after I wrote the first draft to this article Kevlin Henney pointed out that Bruce Eckel had opened discussion on the same point  . An early draft of this article has been I also opened a discussion of this issue on JavaLobby  .
The safe path to C++ exceptions(EXE Dec.'99)
As "Here be Dragons" http://www.octopull.co.uk/c++/dragons/
Thinking in Java
ISBN 0130273635 Prentice-Hall Inc 2000
The Annotated C++ Reference Manual
ISBN 0-201-51459-1 Addison Wesley 1990
Exception Safe Java
Does Java need Checked Exceptions?
Copyright 2001, 2002 Alan Griffiths