Vous êtes sur la page 1sur 25

Exception Handling

Kan Torii qoolloop.org (2015/07/29)

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.

Review of Exception Handling Basics


The Usual Mechanism
Ill just take Java as an example. Most of the major object-oriented programming
languages (Java/C++

ii

/Objective-C++/C#/Python/Ruby/Visual Basic/etc.) provide a

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) {

throw new ArithmeticException();


}
return a / b;
}

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();
}
}

In this case, when the ArithmeticException is caught, an IllegalArgumentException is


thrown instead to the caller of root() in the exception handler.
Inheritance in Exception Handling
Now, because were talking about an object-oriented language, the inheritance of classes
is respected for the catch clause.
If ArithmeticException is replaced with its superclass, RuntimeException, the catch
block will catch all instances of the RuntimeException class and its subclasses (including
ArithmeticException).
But, who would do that?

Below are the subclasses of RuntimeException taken from the Java Platform Standard
Ed.8.

AnnotationTypeMismatchException, ArithmeticException, ArrayStoreException, BufferO


verflowException, BufferUnderflowException, CannotRedoException, CannotUndoExcepti
on, ClassCastException, CMMException,CompletionException, ConcurrentModificationEx
ception, DataBindingException, DateTimeException, DOMException, EmptyStackExceptio
n, EnumConstantNotPresentException, EventException, FileSystemAlreadyExistsExcepti
on,FileSystemNotFoundException, IllegalArgumentException, IllegalMonitorStateExcep
tion, IllegalPathStateException, IllegalStateException, IllformedLocaleException,
ImagingOpException, IncompleteAnnotationException,IndexOutOfBoundsException, JMRun
timeException, LSException, MalformedParameterizedTypeException, MalformedParamete
rsException, MirroredTypesException, MissingResourceException, NegativeArraySizeEx
ception,NoSuchElementException, NoSuchMechanismException, NullPointerException, Pr
ofileDataException, ProviderException, ProviderNotFoundException, RasterFormatExce
ption, RejectedExecutionException, SecurityException,SystemException, TypeConstrai
ntException, TypeNotPresentException, UncheckedIOException, UndeclaredThrowableExc
eption, UnknownEntityException, UnmodifiableSetException, UnsupportedOperationExce
ption,WebServiceException, WrongMethodTypeException

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

exceptions would be less troublesome.


Hierarchy without Polymorphism?
So, tons of exceptions have been declared without need for real polymorphism. Why? Its
because, something similar to polymorphism is being implemented by the caller.
Does a sequence of catch clauses remind you of something?

} catch (ExceptionA exception) {

} catch (ExceptionB exception) {

} catch (ExceptionC exception) {

Its a sequence of if statements in disguise.

if (throwable instanceof ExceptionA) {


ExceptionA exception = (ExceptionA) throwable;

} elseif (throwable instanceof ExceptionB) {


ExceptionB exception = (ExceptionB) throwable;

} elseif (throwable instanceof ExceptionC) {


ExceptionC exception = (ExceptionC) throwable;

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).

Another is to eventually present the exception to the user (getMessage(),


getLocalizedMessage()).

Another is to control the information passed to the caller (adding attributes by


subclassing).

And, another is to represent the cause of the exception for the developer (for
debugging).

Determining How to Process the Exception


The way we log information provides a good hint for the way we can handle exceptions.
Although there are many schools for the classification of log information, I will show one
that seems to make sense from an exception handling point of view.
Level

Definition

Information

Nothing has been found to be unexpected, but information about


progress of the program will be logged anyway.

Warning

The function was able to perform the expected procedure, but it


is likely that there is something wrong.

Recovered Error

An error occurred. The function was not able to perform its


process, but the function recovered the state of the system to the
point when the function was called (as if the function was not
called).

Fatal Error

An error occurred, and the system is in an unexpected or


inconsistent state.

Lets look at each of these one by one.


Information
This kind of log is just used to record information about the progress of some procedure.
It doesnt necessarily imply any problem, although a human reader may be able to detect
something from the log. Such information should not be passed to the caller with the use
of exceptions, since raising an exception causes loss of the returned value and disruption
in control flow.

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:

the function was passed unusual argument values;

there was unexpected data in a file;

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.

Present the Error to the User


Many implementations use exceptions to propagate errors up until the user interface
(UI). Here, it would be best if:

the user could tell how to recover from the error, and

the user could tell how to avoid the error.

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;

public class Move {


public static void main(String [] i_args) {
System.out.println("length: " + i_args.length);
System.out.println("source: " + i_args[0]);
System.out.println("destination: " + i_args[1]);

if (i_args.length != 2) {
System.err.println(
"java Move <source> <destination>n" +
"tMoves file <source> to <destination>");
System.exit(-1);
}

String sourceName = i_args[0];


String destinationName = i_args[1];

File sourceFile = new File(sourceName);


File destinationFile = new File(destinationName);

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.

The Three-tiered Exception Handling Architecture


As a conclusion to the observations above, I use a three layer approach to exception
handling. Below, I will explain a concise version of one that I am intending to use in my
next project. Please be aware that it is not exactly the same as what I have in my source
code, since I like to do something different each time, and because I cant show you any
proprietary stuff.

The Library Layer


The lowest layer is the Library layer. This is the layer for third-party libraries and the
languages standard library. Since we usually dont have control over how exceptions are
defined in these libraries, we would need to catch those exceptions and convert them to
our own set of exceptions in the layer that uses these librariesthe Logic layer.
The Logic Layer
The Logic layer is where we implement the application logic for our software. It calls
functions in the Library layer and combines them to provide useful functionality for the
user.
In the Logic layer, we can define our exceptions according to the Fatal / Recovered

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;

public class FileManipulation {

/**
* Move a file.
*
* @param i_sourceName path of the file to move

* @param i_destinationName the new path for the file


*
* @throws RecoveredException_NoSuchFile The source did not exist.
* @throws RecoveredException_SourceNotFile The source was not a file.
* @throws RecoveredException_DirectoryNotEmpty The destination was a non-empty
directory.
* @throws UnrecoveredException_IO There was an I/O exception, and storage may not
have been recovered.
*/
public static void move(String i_sourceName, String i_destinationName)
throws RecoveredException_NoSuchFile,
RecoveredException_SourceNotFile,
RecoveredException_DirectoryNotEmpty, UnrecoveredException_IO
{
File sourceFile = new File(i_sourceName);
File destinationFile = new File(i_destinationName);

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,

RecoveredException_SourceNotFile, and RecoveredException_DirectoryNotEmpty are


subclasses of RecoveredException, since nothing permanent has happened till the point
they are thrown.

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;

public class MakeFile {

/**
* 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

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.
*
*/
public void saveToFile(String i_filename) throws
RecoveredException_CannotWriteToDirectory, RecoveredException_IO,
RecoveredException_CannotWriteToFile, UnrecoveredException_IO,
RecoveredException_IllegalStorageManipulation
{
m_logger.arguments(i_filename);

File destinationFile = new File(i_filename);


boolean destinationFileExisted = destinationFile.exists();
if (destinationFile.isDirectory()) {
throw new RecoveredException_CannotWriteToDirectory(null,
destinationFile);
}

File temporaryFile = FileManipulation.createTempFile("MakeFile", ".tmp");


// throws RecoveredException_IO

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());
}
}
}
}

The overall flow of the makeAFile() function is:


1.

It creates a temporary file and stores data in it with the function


actuallySaveToFile().

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

m_logger.arguments(). If this is put in a catch block, logging arguments can be performed


only when an exception occursx.
The next part of the function checks whether the specified file name (i_filename) points
to a directory. This is checked, because the FileManipulation.move() called later will fail
if the destination is a directory. A RecoveredException is thown immediately, since
nothing permanent has been done yet.
FileManipulation.createTempFile() is a function that creates a temporary file. It just
wraps the java.io.File.createTempFile(), so that it throws exceptions defined in the
package, instead of the exceptions standard in the language. If a RecoveredException
were to be thrown from this function, it can be propagated to the caller of makeAFile(),
since makeAFile() hasnt made any permanent modifications.

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

RecoveredException_IO, if recovery succeeds. It isnt always the case that an


UnrecoveredException results in another UnrecoveredException being thrown. If
recovery is possible, it can be converted to a RecoveredException.
One cautionary point with this approach is that, as a rule, you shouldnt forget to catch
RecoveredExceptions in functions with side effects xi . Usually, when an exception is
thrown midway in a function, there is some permanent change done. So, even if the callee
raised a RecoveredException, it doesnt mean the caller can rethrow that exception
without performing any recovery.
In all of these cases, the constructor of RecoveredException and FatalException will
record its creation to the log.
Other Languages
Exception handlers can take up quite a lot of space. This tends to obscure the regular
path of a function and makes the source code difficult to read.
The reason why I used nested exceptions handlers above is because Java has a unique
feature to help check whether exceptions handlers are missing. Other languages dont
have such feature, so we dont have to confine our exception handling to within a single
function. Below is the same example, but in C++:

static void throwConvertedExceptionFromActuallySaveToFile(const UnrecoveredException


&i_exception, const fs::path i_temporaryFile) {

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);
}
}
}

static void throwConvertedExceptionFromMove(const RecoveredException &i_exception, const


fs::path &i_temporaryFile) {
try {
throw i_exception;
} catch (const RecoveredException_NoSuchFile &c_exception) {
throw RecoveredException_IllegalStorageManipulation(c_exception,
i_temporaryFile);
} catch (const RecoveredException_SourceNotFile &c_exception) {
throw RecoveredException_IllegalStorageManipulation(c_exception,
i_temporaryFile);
} catch (const RecoveredException_DirectoryNotEmpty &c_exception) {
throw RecoveredException_IllegalStorageManipulation(c_exception,
i_temporaryFile);
}
}

static void throwConvertedExceptionFromMove(const UnrecoveredException &i_exception, bool


i_destinationFileExisted, const fs::path &i_destinationFile) {
try {
throw i_exception;
} catch (const UnrecoveredException_IO &c_exception) {
if (i_destinationFileExisted) {
throw c_exception;
} else {
fs::remove(i_destinationFile);
if (fs::exists(i_destinationFile)) {
throw c_exception;
} else {
throw RecoveredException_IO(c_exception,
c_exception.getFilesCausingException());
}
}
}
}

/**
* 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);
}

fs::path temporaryFile = FileManipulation::createTempFile("MakeFile", ".tmp"); //


throws RecoveredException_IO

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),

and, to store information in the exception instance, so that it can be propagated to


the callers.

Subclassing for these cases was effective in these cases, because:

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:

use an integer or enum attribute to represent the exception subtype of a


RecoveredException or UnrecoveredException,

and use a dictionary to add attributes to the exception instance.

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)

where exceptionAttributes is a dictionary of key/value pairs for the exception. This


statement assigns a string to the variable message with values for {lineNumber} and
{filename} taken from exceptionAttributes. A feature like this is useful in translation,
since the order of the attributes may show up in a different order when the locale changes.
In Japanese, it would be as follows:
message = u"'{filename}'{lineNumber}
".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.

The Presentation Layer


Messages and Errors
Although I implied that exceptions will be converted to messages on the UI, most of the
messages we see often should not involve exceptions. This is partly because errors in
user input should be detected (validated) at the time of entry, not while the request is
being processed. These errors dont need to involve exceptions.
Having said that, there are cases where exceptions do lead to messages on the UI. I will
explain a little on how this can be done.
Before that, I need to remind you of two modes of messages and two ways of handling
errors.

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.

Synchronous: The thread is stopped until the message window is closed.

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.

Immediate user: The user interacting with the software.

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

person starts interacting with the software.

Others: There may be other kinds of users depending on the software.

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.

public static void main(String[] args) {


if (args.length != 1) {
System.err.println("java MakeFile <filename>");
System.exit(-1);
}

String filename = args[0];

MakeFile instance = new MakeFile();

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

internationalization, so Ill only briefly explain my view on this.


Java has a getLocalizedMessage() for all its exceptions (Throwable). This is supposed to
allow translation of error messages for each exception. Because only a few exceptions get
to become messages on the UI, it would be a waste of time to translate all these exception
messages.
getLocalizedMessage() also tends to be impractical because there is not enough context
to explain the exception at the location it is raised. For instance, the write() function of
the java.io.FileOutputStream class has no idea what kind of file it is writing to, so the
message string for the exception that it throws cannot mention it in any meaningful way.
On the other hand, the function that processes the request from the user does have such
information.
Outsourcing the translation of short messages tends to produce very poor results. Time
should be spent choosing only the strings that show up on the UI, and more time should
be spent explaining them to the translator. As was shown above, these messages belong
to the Presentation layer and not in the exception.

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 for debugging will be stored in the log.

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.

Implementation of exception handling and internationalization tend to be postponed


until later in development. Since their implementation involves getting deep into the
source code, it turns out to be quite some work if done late. I hope most of the readers
find this before they start coding. If not, they should start as soon as possible.

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

Google External Polymorphism for similar concepts.

Despite conventional wisdom not to catch OutOfMemoryError, there are cases


when such error is recoverable, as in the case when a function tries to allocate a very
large amount of memory.
v

vi

Input arguments are prefixed with i_.

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

In production code, this function above needs to be a little more complicated to


accommodate the situations that Files.move() cannot handle. I will not be delving too
much into the details here, since the purpose of this document is to explain the overall
way of thinking, not the specific cases.
viii

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