Professional Documents
Culture Documents
Contents
Background............................................................................................................................ 2
Review of Exception Handling Basics ................................................................................. 2
The Usual Mechanism ...................................................................................................... 2
Inheritance in Exception Handling .................................................................................. 3
The Problem with the Standard Exception Hierarchy ................................................... 4
Hierarchy without Polymorphism? .................................................................................. 5
The Requirements ................................................................................................................. 5
Determining How to Process the Exception .................................................................... 6
Information .................................................................................................................... 6
Warning .......................................................................................................................... 7
Recovered Error ............................................................................................................. 7
Fatal Error ..................................................................................................................... 7
Present the Error to the User........................................................................................... 8
Pass Additional Information to the Caller....................................................................... 9
Representing the Cause of the Error for the Developer ............................................... 10
The Three-tiered Exception Handling Architecture .......................................................... 11
The Library Layer ............................................................................................................ 11
The Logic Layer ................................................................................................................ 11
Conversion of Exceptions ............................................................................................ 12
Recovery ....................................................................................................................... 14
Other Languages ......................................................................................................... 17
Dictionaries for Attributes .......................................................................................... 20
The Presentation Layer .................................................................................................. 21
Messages and Errors ................................................................................................... 21
Generating Messages .................................................................................................. 22
Summary ............................................................................................................................. 23
Background
Ive been asked a few times in the past about how I would handle exceptions. Explaining
exception handling is a tedious task, so in the spirit of reuse, Im going to write it down
here. Now, all I have to do is send a link.
One of my first bosses at work jokingly said,
You dont have to pay programmers. Theyll do it for free.
Dont get me wrong. He is a nice guy. I still see him even after weve both left the company.
Its just that he had a bad sense of humor. But, it wasnt far from the truth. Many of us
enjoy programming. If we didnt have to earn a living, Im sure many of us would do it in
our spare time for free.
Unfortunately, there are some areas that dont get focused as much as others. One such
area is the handling of exceptions. I actually think that these are the areas that
distinguish professional-likei engineers from amateur-like people. Many people find joy
and are productive in writing the regular path of a program, but once they get into
perfecting the system, they drop in performance. Some even resist doing it. Many of us
get paid to do it.
Not only does exception handling lack attention, its also confusing the way it is
presented. Exception handling mechanisms standard in programming languages dont
provide a full conceptual framework to work on. Even the greatest people in the
standards committee seem to try to avoid the work, and just copy a mechanism from
another language.
ii
similar mechanism.
Functionsiii (methods) in Java are allowed to throw objects to the caller.
public static double divide(double a, double b) {
if (b == 0) {
This function throws an instance of the ArithmeticException class, when the second
argument of the function is 0. Such an instance is called an exception. (In Java, its called
a throwable.) It makes the caller know that something unusual has happened, and the
function cant process the request. In this case, the caller has passed in some bad value.
(The / operator already throws an ArithmeticException when dividing by zero, but Ive
made it explicit.)
On the other hand, the caller can be written to receive the exception:
public static double root(double a, double b, double c) {
double numerator = - b + Math.sqrt(b * b - 4 * a * c);
double divisor = 2 * a;
try {
return divide(numerator, divisor);
} catch (ArithmeticException e) {
throw new IllegalArgumentException();
}
}
Below are the subclasses of RuntimeException taken from the Java Platform Standard
Ed.8.
What exception handling routine would be common for all these classes, but not others?
You might as well catch all exceptions (including the others) in one clause. Defining the
RuntimeException doesnt seem to be very useful.
The Problem with the Standard Exception Hierarchy
This problem actually shows the difficulty in designing for object-oriented programming.
For us intelligent human beings, it seems just so natural that we have
ArithmeticException as a subclass of RuntimeException, because an arithmetic
exception occurs during runtime. But, this is like trying to categorize species based on
DNA in a framework that was invented before the DNA was discovered.
Contrary to what many of us have been taught, inheritance in OOP isnt really an is a
relationship. If you have a Penguin class that only swims, you cant have it as a subclass
of the Bird class that can fly. Inheritance in OOP is achieved, when the subclasses have
the functions and variables that the superclass has. In other words, a subclass needs to
behave like the superclass (maybe in an abstract way).
Now, what is behavior for an exception class? The only function really intended to be
overridden by subclasses is the getLocalizedMessage(), but Im going to debunk this
function later on. The subclasses dont really have any useful behavior. They might as
well all be immediate subclasses of Exception (or Throwable). Or, whats more, they
might as well be the same class with integer attributes to determine their type. Then,
we wouldnt need so many catch clauses for exception handling, and conversion of
It is very much the kind of code that is disliked by OOP purists. Its the way we
implement polymorphism, when we dont have control over the implementation of the
calleeiv.
Usually, polymorphism is achieved by defining functions on the callee. For exception
handling, the handling needs to be defined by the caller, because each caller needs a
different handling process.
The Requirements
Exception handling in object-oriented languages is messy, because we are trying to do
four different things with one object.
One is to try to determine how to process the exception (in the catch clauses).
And, another is to represent the cause of the exception for the developer (for
debugging).
Definition
Information
Warning
Recovered Error
Fatal Error
Warning
This kind of log implies that there might be a problem in the system. The function,
however, was able to do whatever it needs to do. Such warnings may be logged in the
following situations:
etc.
As with information logs, such information should not be passed to the caller with the
use of exceptions.
Recovered Error
For a system to continue to be reliable in the event of an error, it is preferable for a
function to restore the state of the system to the point when the function was called. For
example, if a function writes to a file, it should keep the original file, so that the file can
be restored, when an error occurs. If the function uses a database, it should use
transactions, so that the database can do the recovery for you. Whatever the function
modifies should be put back to its original state.
A recovered error can be propagated to the caller with the use of exceptions.
Fatal Error
In some cases, a function may not be able to restore the systems state. Or, the system
might already be in an inconsistent state. Many assertions indicate fatal errors.
Fatal errors can be propagated to the caller with the use of exceptions. In some cases,
the system may be in such a hopeless state, that it may choose to crash to avoid any more
damage. This can also be a design decision, if recovery is too expensive considering the
likelihood of an error. In many implementations, OutOfMemoryError causes crashes, be
it unintentionallyv.
In any case, as a rule, errors should be logged as well as thrown, since they could be lost
during propagation.
the user could tell how to recover from the error, and
The user doesnt need to know the details about the cause of the error, especially if it
includes some implementation detail. For instance, the user will feel helpless, when (s)he
is told that a file cannot be written to, if the user has no control over the permissions of
the file. If the user doesnt even know such a file exists, mentioning the file will just cause
confusion.
This is where a kind of encapsulation (information hiding) should come into play. Im not
talking about public/protected/private and the like. What we need to do is to hide the
implementation details and give the user some useful feedback. We need to translate the
errors occurring in the implementation to something that is readable by a non-developer.
In a very simple application, such as a file move utility, the translation could be
performed as followsvi.
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.DirectoryNotEmptyException;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
if (i_args.length != 2) {
System.err.println(
"java Move <source> <destination>n" +
"tMoves file <source> to <destination>");
System.exit(-1);
}
if (!sourceFile.exists()) {
System.err.println("ERROR: Source does not exist.");
System.exit(-1);
}
if (!sourceFile.isFile()) {
System.err.println("ERROR: Source is not a file.");
System.exit(-1);
}
try {
Files.move(sourceFile.toPath(),destinationFile.toPath(),
REPLACE_EXISTING);
} catch (DirectoryNotEmptyException exception) {
System.err.println("ERROR: The destination is a non-empty
directory.");
System.exit(-1);
} catch (IOException exception) {
System.err.println("ERROR: Could not read/write file, because of
I/O error.");
System.exit(-1);
} catch (Throwable thrown) {
System.err.println("ERROR: " + thrown);
System.exit(-1);
}
}
}
In this example, exceptions are caughtvii and then printed to the screen. See how the
messages provide some context to the error in human-readable form. If the program just
showed the raw error message thrown from Files.move(), the user might be shown a
cryptic message such as:
Unexpected error: java.nio.file.NoSuchFileException: noneExist
Since the caller knows why File.move() is being called, it can provide more helpful
messages. In the source code above, the only case when the message in the exception is
shown directly is when the exception (Throwable) was not anticipated.
Pass Additional Information to the Caller
Now, if you agree that error messages should be generated by the caller, you should agree
that getMessage() and getLocalizedMessage() in the Throwable class and its subclasses
are pretty much impractical. It forces you to assign a message to the exception, when
you have the least information. It may be useful for developers, but for the general user
the message will be too nerdy. Its ok for the exception instance to have that kind of
information for tracing on the debugger, but it isnt something that should be shown on
the UI.
Whats necessary is for the exception to hold information that can be used eventually to
generate the error message. Such information may be the file name, URL, etc.
Although the standard exception classes do not have additional information for the caller,
we can add member functions for new classes. For example, in this case, it could be
beneficial if the exceptions held the name of the file that caused the exception.
Representing the Cause of the Error for the Developer
The stack trace in an exception is very useful information for debugging. It has the
location of where the error occurred. It has the order of calls made until that function
was executed. So, the developer is tempted to show that information on the screen, when
an error happens. However, this should not be the case, since the average user is not a
programmer and will understandably be confused. It also reveals the internals of the
software (including the bugs), which is generally avoided with proprietary work.
Since you cant use exceptions for information level and warning level logging, the best
place to store information for debugging is the log. The log can have information other
than whats included in an exception. Information can be logged with parameters that
invoked the program. The values of the arguments to a function would also be beneficial
for reproducing the problem.
Because exceptions can get lost, this information should be recorded when the exception
is raised, not when it is caught. For example, you would need to extinguish exceptions in
a destructor, a function may throw an exception in a finally clause, and there may be
cases when an innocent developer forgot to fill in the catch clause. If you define exception
classes to automatically log information in the constructor, you wouldnt forget to do it.
The log may be accessible on the UI as a hidden feature. Some products allow terminals
to be connected to it, so that the log can be retrieved. If the device is connected to the
internet, there might be a button to send the log to the manufacturer encrypted and
compressed. With a web service, the log is always accessible to the developers.
categories.
The class hierarchy above shows how I would organize the fatal and the recovered
exceptions in most of the languages that I use. The UnrecoveredException class
represents unrecovered exceptions that happen in a function. These are exceptions that
mean that the state was not recovered to the state before the function was called. The
RecoveredException represents all exceptions that were recovered. There also should be
a FatalError class, which should usually result in termination of the program. Since we
have the java.lang.Error class in Java, we can subclass this for the FatalError class.
Conversion of Exceptions
In the Logic layer, the file move function we saw previously would be written in the
following wayviii.
package org.qoolloop.sample.exceptionhandling;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.DirectoryNotEmptyException;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
/**
* Move a file.
*
* @param i_sourceName path of the file to move
if (!sourceFile.exists()) {
throw new RecoveredException_NoSuchFile(null, sourceFile);
}
if (!sourceFile.isFile()) {
throw new RecoveredException_SourceNotFile(i_sourceName, null);
}
try {
Files.move(sourceFile.toPath(),destinationFile.toPath(),
REPLACE_EXISTING);
} catch (DirectoryNotEmptyException c_exception) {
throw new RecoveredException_DirectoryNotEmpty(c_exception,
destinationFile);
} catch (IOException c_exception) {
throw new UnrecoveredException_IO(c_exception, sourceFile,
destinationFile);
} catch (Throwable c_thrown) {
throw new FatalError("Unexpected Throwable", c_thrown);
}
}
}
See how the exceptions thrown from Files.move() are translated to exceptions defined in
our
program.
Here,
RecoveredException_NoSuchFile,
FatalError has not been subclassed. If an error is not documented or not put in the
throws clause, there is no point in defining it as a subclass of FatalError, since the caller
will not be catching that specific class anyway. Subclassing of java.lang.Error is usually
done to provide information for debuggingix. However, this can be done with messages
and logs. These can include a lot more information than the name of the class.
As a rule, exceptions in a callees module should be converted to exceptions defined in
the callers module unless they are system wide exceptions. This is because of the
encapsulation (information hiding) principle. In addition to the functions, exceptions also
comprise the API of the module. If we expose the exceptions of the libraries that we are
using in the API, we will lose the ability to change the implementation.
In reality, it is a little too time consuming to define exceptions for each package. As a
compromise, we might define exceptions that are shared between a group of packages,
such as those that share a common package higher in the hierarchy.
Recovery
Now, lets see a function that calls this move function.
package org.qoolloop.sample.exceptionhandling;
import java.io.File;
import org.qoolloop.exception.UnrecoveredException;
import org.qoolloop.logging.Logger;
/**
* Save to file.
*
* @param i_filename name of file to save to.
* @throws RecoveredException_CannotWriteToDirectory Cannot specify directory for
i_filename.
* @throws RecoveredException_IO An I/O exception occurred during file
manipulation.
* @throws RecoveredException_CannotWriteToFile Cannot write to i_filename for some
reason.
* @throws RecoveredException_IllegalStorageManipulation Files that should not have
try {
try {
actuallySaveToFile(temporaryFile);
} catch (UnrecoveredException c_exception) {
temporaryFile.delete();
throw c_exception;
}
} catch (UnrecoveredException_IO c_exception) {
if (temporaryFile.exists()) {
throw c_exception;
} else {
throw new RecoveredException_IO(c_exception,
temporaryFile);
}
}
try {
try {
FileManipulation.move(temporaryFile.getAbsolutePath(),
destinationFile.getAbsolutePath());
} catch (UnrecoveredException c_exception) {
temporaryFile.delete();
throw c_exception;
}
} catch (RecoveredException_NoSuchFile | RecoveredException_SourceNotFile
| RecoveredException_DirectoryNotEmpty c_exception) {
throw new
RecoveredException_IllegalStorageManipulation(c_exception, temporaryFile);
} catch (UnrecoveredException_IO c_exception) {
if (destinationFileExisted) {
throw c_exception;
} else {
destinationFile.delete();
if (destinationFile.exists()) {
throw c_exception;
} else {
throw new RecoveredException_IO(c_exception,
c_exception.getFilesCausingException());
}
}
}
}
2.
If it succeeds, the temporary file is renamed to the name specified by the i_filename
argument with the FileManipulation.move() function.
In
the
beginning
of
the
function,
the
argument
values
are
logged
with
The next part calls actuallySaveToFile(), where data is stored to the temporary file, and
the last part calls FileManipulation.move(), so that the temporary file becomes the
output of this makeAFile() function.
In the last two parts, the functions are surrounded by two nested try-catch blocks. This
would be the typical structure for exception handling. The inner block handles the
recovery, and the outer block handles the conversion of exceptions. Many people say that
sharing a common procedure between exception handlers is not possible without defining
functions, but it is also possible by nesting try-catch blocks. This is possible because of
the class hierarchy that we defined.
The two calls to actuallSaveFile() and FileManipulation.move() are wrapped in a trycatch
block
that
catches
UnrecoveredException_IO
and
converts
it
to
try {
throw i_exception;
} catch (const UnrecoveredException_IO &c_exception) {
if (fs::exists(i_temporaryFile)) {
throw c_exception;
} else {
throw RecoveredException_IO(c_exception, i_temporaryFile);
}
}
}
/**
* Save to file.
*
* @throws RecoveredException_CannotWriteToDirectory Cannot specify directory for
i_filename.
* @throws RecoveredException_IO An I/O exception occurred during file manipulation.
* @throws RecoveredException_CannotWriteToFile Cannot write to i_filename for some
reason.
* @throws RecoveredException_IllegalStorageManipulation Files that should not have been
touched were manipulated by an external entity during processing.
* @throws UnrecoveredException_IO An I/O exception occurred, and storage may not have
been recovered.
*/
void MakeFile::saveToFile(const String &i_filename) const
{
m_logger.arguments("MakeFile", __func__, i_filename);
fs::path destinationFile(i_filename);
bool destinationFileExisted = fs::exists(destinationFile);
if (fs::is_directory(destinationFile)) {
throw RecoveredException_CannotWriteToDirectory(nullptr, destinationFile);
}
try {
actuallySaveToFile(temporaryFile);
} catch (const UnrecoveredException &c_exception) {
remove(temporaryFile);
throwConvertedExceptionFromActuallySaveToFile(c_exception, temporaryFile);
}
try {
FileManipulation::move(temporaryFile, destinationFile);
} catch (const UnrecoveredException &c_exception) {
fs::remove(temporaryFile);
throwConvertedExceptionFromMove(c_exception, destinationFileExisted,
destinationFile);
} catch (const RecoveredException &c_exception) {
throwConvertedExceptionFromMove(c_exception, destinationFile);
}
}
See that the exception conversion part of the exception handlers has been extracted to
functions outside of MakeFile::saveToFile()xii. In effect, this takes out the clutter from
the source code, so that the reader can focus on the regular path first.
Dictionaries for Attributes
By defining RecoveredException and UnrecoveredException, we could determine how we
would recover from an error based on these types.
Further, we subclassed exceptions for two reasons:
to determine how to convert the exception to another type (in catch blocks),
we could use catch blocks as if statements that branched based on exception class
type,
and, in type-safe languages the compiler would warn us, if we tried to get attributes
of the wrong class.
However, with languages without type-safety, we dont have the compiler checks. We
could just as well:
Using integers or enum attributes have the benefit of being able to use switch statements
for conversion of exceptions (unless its Python). It does have a slight disadvantage of not
being able to subclass further.
Dictionaries are useful for generating messages for the UI. For instance, by default,
Python has a string formatting feature that is written as follows:
message = u"Syntax error in line {lineNumber} of the file
'{filename}'".format(**exceptionAttributes)
Such feature can be implemented in other languages too. Libraries based on ICU, such
as AngularJS for Javascript, have this functionality and much more.
Modal: A window (dialog box) is shown to display the message, and the user cannot
interact with the software unless (s)he closes the window.
Modeless: A window is shown to display the message, which may be hidden by other
windows. This allows the user to interact with the software without closing the
window with the message.
Asynchronous: The thread continues running while the message window is open.
These are concepts that are similar, but different in what is stopped. One stops the user.
The other stops the thread. Care should be taken when choosing how to deliver a
message. If we use modal or synchronous messages unnecessarily, we would end up with
a frustrating piece of software.
There are also different kinds of users to which a message would be directed.
Administrator: The user who is responsible for maintenance of the software. Usually,
it is enough to log any kind of exception, but sometimes, if the exception is serious,
an alert must be sent to the administrator. The alert is usually not made through
the UI (maybe by email or SMS), because the admin is not always interacting
directly with the software. The administrator will become the immediate user, if that
Once you choose the method, it should be obvious how to do the programming. It should
very rarely be the case that a raw exception message is shown by throwing the exception
directly to the UI framework. In other words, the Presentation layer has the work of
converting the exceptions that it caught into the message of choice.
Generating Messages
Now, this is another occasion where subclassing of exceptions has its benefits. As was
implied in the examples above, each exception instance can a have pieces of information
related to the exception. The most typical is the name of a file. The information will
become the building blocks for creating a message to the user.
Since we dont have time learning a new UI framework, lets consider a command line
program that shows messages on the console. See how the messages for
System.err.println() are constructed using the information in the exception instances.
try {
instance.saveToFile(filename);
} catch (RecoveredException_IllegalStorageManipulation c_exception) {
System.err.println("ERROR: The following file was manipulated
and caused execution to stop: " + c_exception.getAbsolutePathOfFileCausingException());
System.exit(-1);
} catch (RecoveredException_CannotWriteToFile c_exception) {
System.err.println("ERROR: Could not write to the following
temporary file: " + c_exception.getAbsolutePathOfFileCausingException());
System.exit(-1);
} catch (RecoveredException_IO c_exception) {
System.err.println("ERROR: Could not create file, because of I/O
error: " + c_exception.getAbsolutePathsOfFilesCausingException());
System.exit(-1);
} catch (RecoveredException_CannotWriteToDirectory c_exception) {
System.err.println("ERROR: Cannot write to a directory: " +
c_exception.getAbsolutePathOfDirectoryCausingException());
System.exit(-1);
} catch (UnrecoveredException_IO c_exception) {
System.err.println("ERROR: Could not complete request due to an
I/O error. The following files may have been affected: " +
c_exception.getAbsolutePathsOfFilesCausingException());
System.exit(-1);
}
}
When you see the example above, you would probably realize that this is where
internationalization
should
come
into
play.
This
is
not
an
article
about
Summary
Based on some observations about exceptions, I have explained an exception handling
framework that I am intending to implement in future projects. Its features can be
summarized as follows:
It is a three-tiered architecture with the Library layer, the Logic layer, and the
Presentation layer.
Each group of packages converts exceptions in the libraries that it uses to exception
that are defined in the group.
Exceptions are divided into three categories, and they are defined as subclasses for
RecoveredException, UnrecoverdException, and FatalError.
Messages stored in exception instances are used for debugging purposes and are not
meant for showing on the UI.
Exception instances hold attributes that are used to compose messages on the UI.
Messages shown on the UI are generated in the Presentation layer using attributes
in the exception. This is where localization happens. Input errors do not necessarily
involve exceptions.
I have to call these people professional-like, because there are people, who arent
paid, but produce work that can be labeled professional.
i
C++ takes a slightly different (and more reliable) approach without the finally
clause (RAII), but the catch clause is the same. Im not going to talk about the
finally clause in this article. Some languages allow throwing non-objects, but I dont
believe many developers use that feature for work.
ii
Although Java does not have any functions that are not methods belonging to a
class, Ill use the word function, since it is more general and applies to other
languages.
iii
iv
vi
If youve ever tried using Files.move(), or any other standard function, you will also
discover that not all the exceptions are listed in the API document. The
java.nio.file.NoSuchFileException is just one example. Its lumped into one superclass,
java.lang.IOException. The API document does need to be improved.
vii
ix
FatalError itself subclasses java.lang.Error, so that it can log the error message.
In the case of Java, I intend to use annotations for some of this exception handling,
but I havent come to that yet.
x
A function with side effects is a function that does more than just calculate values.
They may change member fields in a class instance. They may save data in a file. They
might communicate through the network. Functions with side effects should never be
used in assertions.
xi
If you dont like using catch blocks this way, you can, of course, use if statements
instead.
xii