Notes on exception handling
If a function be advertised to return an error code in the event of difficulties, thou shalt check for that code, yea, even though the checks triple the size of thy code and produce aches in thy typing fingers, for if thou thinkest “it cannot happen to me”, the gods shall surely punish thee for thy arrogance.
Henry Spencer’s 10 Commandments for C Programmers
In the olden days, before Exceptions were invented, we wanted to write code like this:
// Called when the user presses the "triple" button
void onTripleButtonPressed() {
String valueEntered = readValueFromField();
int valueToDisplay = triple(stringToInteger(valueEntered));
displayResult(integerToString(valueToDisplay));
}
int triple(int value) {
return value * 3;
}
Unfortunately, we were forced to write code like this instead:
int onTripleButtonPressed() {
StringHolder valueEntered = new StringHolder();
int resultCode = readValueFromField(valueEntered);
if (isError(resultCode))
return resultCode;
IntHolder valueAsInteger = new IntHolder();
resultCode = stringToInteger(valueAsInteger, valueEntered.value);
if (isError(resultCode))
return resultCode;
resultCode = triple(valueAsInteger);
if (isError(resultCode))
return resultCode;
StringHolder valueToDisplay = new StringHolder();
resultCode = integerToString(valueToDisplay, valueAsInteger.value);
if (isError(resultCode))
return resultCode;
resultCode = displayResult(valueToDisplay.value);
if (isError(resultCode))
return resultCode;
return OK; // :-)
}
private int triple(IntHolder valueAsInteger) {
int result = valueAsInteger.value * 3;
if (isOverflow(result))
return ERROR_OVERFLOW;
valueAsInteger.value = result;
return OK;
}
Yes, we were FORCED to check the result of each and every operation. There was no other way to write reliable software. As you can see:
- Code size is more than three times
- Logic becomes obscure
- Functions are forced to return two values; the intended result AND an error code.
- You always, always, always had to return an error code from all functions.
There was no alternative, until exceptions came along. Then we were finally able to write simple code in a reliable way:
// Called when the user presses the "triple" button
// Exceptions are handled here
void onTripleButtonPressed() {
try {
tryOnTripleButtonPressed();
} catch (Exception e) {
alertUser(e.getMessage());
}
}
// Business logic is handled here
private void tryOnTripleButtonPressed() {
String valueEntered = readValueFromField();
int valueToDisplay = triple(stringToInteger(valueEntered));
displayResult(integerToString(valueToDisplay));
}
In this example, all exception handling is centralized at the point where the GUI gives control to our code. The code that performs what the user really wanted is in another function, which contains exactly the same clean code of the first example.
Thus, the invention of exception handling allows us to cleanly separate code that performs the happy path, and code that handles the many possible exceptional conditions: integer overflow, I/O exceptions, GUI widgets being improperly configured, etc.
What you saw in the previous example is the
Fundamental Pattern of Exception Handling: centralize exception handling at the point where the GUI gives control to our code. No other exception handling should appear anywhere else.
It turns out that the Fundamental Pattern of Exception Handling is the only pattern that we need to know. There are other cases where we are tempted to write a try-catch, but it turns out that we nearly always have better ways to do it.
Antipattern: nostalgia (for the bad old days)
There is an ineffective style of coding that we sometimes see in legacy code:
void onTripleButtonPressed() {
String valueEntered = null;
try {
valueEntered = readValueFromField();
} catch (Exception e) {
logger.log(ERROR, "can't read from field", e);
return;
}
int valueEnteredAsInteger = 0;
try {
valueEnteredAsInteger = Integer.parseInt(valueEntered);
} catch (Exception e) {
logger.log(ERROR, "parse exception", e);
return;
}
int valueToDisplay = 0;
try {
valueToDisplay = triple(valueEnteredAsInteger);
} catch (Exception e) {
logger.log(ERROR, "overflow", e);
return;
}
try {
displayResult(integerToString(valueToDisplay));
} catch (Exception e) {
logger.log(ERROR, "something went wrong", e);
return;
}
}
As you can see, this is just as bad as the olden days code! But while in the old times we had no alternatives, now we do. Just use exceptions the way they were intended to.
Stay up no matter what
Sometimes there is a feeling that we should write code that “stays up no matter what happens”. For instance:
private void tryOnTripleButtonPressed() {
String valueEntered = readValueFromField();
int valueToDisplay = triple(stringToInteger(valueEntered));
try {
sendEmail(userEmail, "You tripled " + valueEntered);
} catch (Exception e) {
logger.log(WARNING, "could not send email", e);
// continue
}
displayResult(integerToString(valueToDisplay));
}
Here we have added an email confirmation; whenever the user presses the button, they will also receive an email. Now we should ask ourselves which of the two:
- is the sending of the email an integral and fundamental part of what should happen when the user presses the button?
- Or is it an accessory part should never stop the completion of the rest of the action?
This is a business decision. If the business decides on 1., then we should remove the try-catch. By applying the Fundamental Pattern, proper logging of the exception will be done elsewhere. We end up with:
private void tryOnTripleButtonPressed() {
String valueEntered = readValueFromField();
int valueToDisplay = triple(stringToInteger(valueEntered));
sendEmail(userEmail, "You tripled " + valueEntered);
displayResult(integerToString(valueToDisplay));
}
Clean code again. Yay!
If the business decides on 2., then we cannot allow sendMail to throw exceptions that might stop the processing of the user action. What do we do now? Do we have to keep the try-catch?
The answer is yes, but out of the way. There are two ways to do this: the easy way and the simple way. If you do it the easy way, you will move the try-catch inside the sendMail function. You will get code like this:
private void tryOnTripleButtonPressed() {
String valueEntered = readValueFromField();
int valueToDisplay = triple(stringToInteger(valueEntered));
sendMail(userEmail, "You tripled " + valueEntered);
displayResult(integerToString(valueToDisplay));
}
private void sendMail(EmailAddress email, String message) {
try {
trySendEmail(email, message);
} catch (Exception e) {
logger.log(WARNING, "could not send email", e);
// continue
}
}
You have moved exception handling for sending mail to a dedicated function (good). However, you still have complicated code tightly coupled to the user action. The sending of email, which is an accessory operation, makes understanding the fundamental operation more difficult (bad!)
What is the simple way, then? In the simple way we eliminate the tight coupling betweeen tripling the number and sending the email. There are several patterns that we might use; a typical choice would be “publish-subscribe”. We set up a subscriber that waits for the “User Pressed the Triple Button” event. In the main onTripleButtonPressed function we don’t know nor care what the subscriber does. It might send email, write logs, compute statistics, or maybe distribute the event to a list of other subscribers. We don’t know nor care! The code looks like this:
private void tryOnTripleButtonPressed() {
String valueEntered = readValueFromField();
int valueToDisplay = triple(stringToInteger(valueEntered));
subscriber.notifyTripled(valueEntered);
displayResult(integerToString(valueToDisplay));
}
In the Simple way, the Fundamental Pattern has been respected: for the subscriber object, the start of the processing is within the notifyTripled method. We have clean and loosely coupled code.
Avoid resource leaking
Another time when we are tempted to use a try-catch in violation of the Fundamental Pattern is when we open some resource that must be closed.
void doSomethingImportant() {
Reader reader = null;
try {
reader = new FileReader("foo bar");
doSomethingWith(reader);
reader.close();
} catch (Exception e) {
if (null != reader) {
reader.close();
}
// report the exception to the callers
throw new RuntimeException(e);
}
}
It is correct that we close the reader in the event that something within doSomethingWith throws an exception. But we don’t want the catch; a finally clause is better:
void doSomethingImportant() throws IOException {
Reader reader = null;
try {
reader = new FileReader("foo bar");
doSomethingWith(reader);
} finally {
if (null != reader)
reader.close();
}
}
This way, we don’t even need to worry about rethrowing the exception to our callers. The code is both correct and clear.
Irritating APIs
Some APIs force us to use more try-catches that we’d like. An example in Java is how to read data from a database using JDBC:
PreparedStatement statement = connection.prepareStatement("...");
try {
ResultSet results = statement.executeQuery();
try {
while (results.next()) {
// ...
}
} finally {
results.close();
}
} finally {
statement.close();
}
Here the problem lies in how the JDBC API is defined. We can’t change it of course, but we should encapsulate all uses of the JDBC API in a single class, so that we don’t have to look at this code anymore. Treat that class as your adapter to JDBC. For instance:
Results results = new Database(connection).read("select * from foo");
// use results
Of course our object is a lot less flexible than the JDBC API. This is expected: we want to decide how we use JDBC and encapsulate that “how” in a single place.
November 24th, 2013 at 08:07
Hi Matteo.
Interesting post, especially the usage of the publish-subscribe pattern to eliminate the tight coupling between tripling the number and sending the email.
I have a question about the following though:
void onTripleButtonPressed() {
try {
tryOnTripleButtonPressed();
} catch (Exception e) {
alertUser(e.getMessage());
}
}
Normally when I see code catching java.lang.Exception, I bring up the following two reasons why I think it should not be done.
The first reason is explained in the Catching Exception antipattern:
https://today.java.net/pub/a/today/2006/04/06/exception-handling-antipatterns.html#catchingException
My other reason why catching java.lang.Exception (or java.lang.Throwable for that matter) is an antipattern is that one of the subclasses of Exception is RuntimeException (http://javajazzle.files.wordpress.com/2011/02/hierarchy_of_java_exceptions.png). So whenever you catch Exception, you are also catching RuntimeException. But runtime exceptions are generally supposed to indicate programming errors.
A correctly working system is generally not expected to encounter runtime exceptions. When a system encounters a runtime exception, you wan to be sure that the system informs you about it, and you then want to go and fix the programming error that caused it.
There is no point in handling a runtime exception programmatically (other than reporting it) because if it is ever encountered, you will fix the error that caused it, so it should never be raised again. If you catch Exception, you run the risk of handling runtime exceptions in the same way as checked exceptions, which is almost certainly incorrect.
So if you really have to catch Exception (this is highly discouraged), you should first catch RuntimeException and rethrow it:
catch(RuntimeException re)
{
throw re
}
catch(Exception e)
{
…some logic…
}
Of course in most systems that are in production, instead of allowing a runtime exception to bubble all the way up, which would bring the system to a halt, we want to catch them at the top of our exception handling structure, and inform interested parties about it.
If we apply the Fundamental Pattern of Exception Handling, there is only one point where exceptions are handled, so it makes sense to catch runtime exceptions as well, but we want to handle them differently from checked exceptions. I guess pointing this out was out of scope for your post.
What are your thoughts on the two reasons I mentioned for not catching Exception?
November 25th, 2013 at 10:25
Hello Philip,
thanks for your comment. I don’t share your concern about catching Exception. RuntimeExceptions might happen for all kinds of reasons, like NullPointerExceptions which are certainly due to logic errors, but also OutOfMemoryError or even applicative errors that the developer chose to declare as unchecked exceptions.
I think they should be handled uniformly; whenever an exception is thrown, the fact is that we cannot do what the user asked us to do. We log appropriately and present an appropriate error message to the user. Even logic errors are a fact of life; I want the system to handle them as gracefully as possible.
Exceptions should be exceptional; for instance in my example above, there should probably be a check that the string actually contains a parseable integer, so that no ParseException is ever thrown. Most exceptions should be prevented by checking that an operation can be performed before we perform it. If we do this, then anything that is thrown should either be something that we didn’t think of, like a hole in our validation checks, or something that we cannot possibly do anything about, like our database server is unreachable or we’re out of memory. I don’t think you can gain much by catching exceptions selectively then.
By the way: the pattern that I called the “Fundamental Pattern of Exception Handling” is not something I invented, of course. I learned about it when I worked with the MacApp application framework, circa 1990 :-)
November 26th, 2013 at 17:59
Hi Matteo, while I certainly agree with you here, I think you are considering only the “easy” case.
In all examples here, exception are raised for “exceptional” cases, either bugs in our code or a failure of our environment (mail server down).
I’ll wait until you wrote something about exceptions used for flow control. (http://c2.com/cgi/wiki?DontUseExceptionsForFlowControl)
:)
November 30th, 2013 at 13:27
>RuntimeExceptions might happen for all kinds of reasons, like NullPointerExceptions which are certainly due to logic errors, but also OutOfMemoryError or even applicative errors
No RuntimException can happen due to OutOfMemoryError because the latter extends Error:
java.lang.Object <== java.lang.Throwable <== java.lang.Error <== java.lang.VirtualMachineError <== java.lang.OutOfMemoryError.
So when you catch Exception, you run no risk of catching any Error.
While RuntimeException should not normally be caught because it indicates a programming error, Error should not normally be caught because it is a serious problem and most of the time and abnormal condition.
Philip
November 30th, 2013 at 20:38
Willie Wheeler, author of ‘Spring in Practice’ has a name for the second concern I have with catching Exception: ‘Mask via broad catch, and misreport’ (see Wheeler’s answer to StackOverflow question: http://stackoverflow.com/questions/6591470/checked-vs-unchecked-exceptions-in-service-layer).
Philip
November 30th, 2013 at 21:48
One way in which Nat Pryce, author of Growing Object Oriented Software – Guided by Tests, stressed the kind of error that RuntimeExceptions are designed to report, was by blogging (http://nat.truemesh.com/archives/000643.html) that RuntimeException be renamed to StupidProgrammerException. Another way he did that was by suggesting (http://www.natpryce.com/articles/000739.html) that checked exceptions due to programming errors be converted in a dedicated Defect exception extending RuntimeException.