Exceptions always seemed weird to me. Why would I want to catch an exception, when with proper coding they shouldn’t occur in the first place? I felt at first like exceptions were designed into the language for someone else to use, and even when I did want to make use of them I found them awkward and cumbersome.
After resisting exceptions for a while, I started to come around. It started to become clear that even if code is perfect (which it rarely is) there is still a pile of network and system related errors that can occur. While one way to handle these errors is to add return codes to all functions that happen to touch the outside world, a perhaps cleaner way is to throw an exception. All of the sudden exceptions became cool.
Knowing that exceptions have a time and place in a program, I started designing all kinds of complex exception handling machinery. My code was destine to handle all exceptions with grace and ease, and easily recover. An exception handler would in turn try another body of code that could throw an exception, which would enter a wait state before retrying and catching another exception and so on. It was quite a mess, and the more I developed these techniques, the more I became acutely aware that this wasn’t really the right way to do things either. Applications simply can’t be able to recover from every possible scenario.
A few years and a couple of paradigm shifts later I’m finally starting to see what the point of catching an exception is, why they are useful, and what to do when one occurs. The bottom line is the point of an exception is to limit damage when something unexpected happens in your code, get back to a known state if possible, or abort if not. Exceptions do not make your code bullet-proof, however they do a great job of mending wounds and keeping the system up, despite unexpected occurrences. Nowadays my exceptions follow a pretty rigid two-phase pattern, restoring state and restoring flow control, and this works reasonably well.
The most immediate goal of an exception is to return the application to a known state. This involves performing any necessary maintenance on modified data structures, managing any unmanaged resources that need to be released, and deleting any temporary files that had happened to be created during the execution of the routine.
I’ve been doing a lot of server work lately, and this usually consists of another try block to close any open database connections, and an iteration through a list of temporary files. As certain methods execute in my program that use intermediate files they keep a list of created temporary files, and my error handling routine iterates through this list and deletes any files still in existence.
In other cases I end up deleting items from a
HasMap structure, where the value of the entry points to an incompletely initialized object. Either way, after this part of the exception routine is done, the application should be in a state identical to before it entered the try block.
Restoring Flow Control
After restoring the application’s state, the next problem is where to transfer flow control to. I’ve found that exceptions usually break down into two pretty clean categories.
Temporary exceptions occur when servers go down, or network requests fail. They are often transient, and upon retrying might succeed. For these kinds of exceptions I usually implement some sort of retry loop, with a fixed number of retries allotted based on the timing requirements of the function I’m in.
Permanent exceptions involve events that are not likely to ‘fix themselves’ if given time. File permissions issues, logic errors, and most other non-IO exceptions fall into this category. When I encounter this kind of exception the show is basically over. If the exception occurs in a reasonably compartmentalized task, as is often the case, the entire program doesn’t need to cease execution, we after all did restore it to a known state, but we do need to abort the current task and present a failure message to the user.
When a temporary exception times out after a certain number of retries it too becomes a permanent exception.
In this model it’s actually really reasonable to throw in a hierarchical approach to exceptions as well. It’s common in the Java world to catch an exception, only to throw another one. For example a class named
MagicMoneyWorker might throw
MagicMoneyWorkerException exceptions as a result of network timeouts when performing remote procedure calls. It has wrapped the more low-level exception with a high level one.
The reasons for doing this fit well with the object oriented approach to programming. The
MagicMoneyWorker class probably knows best how to handle a network timeout. It knows how to preserve it’s inner state, and recover, and probably even has some retry and reconnect logic built in to it. By the time it’s done handling the exception, if an error occurs, it’s a permanent exception and it means that the task at hand failed. This needs to be handled now by the caller function.
Wrapping exceptions in this manor allows you to keep the primitive exceptions hidden, and preserves the level of abstraction you’re operating with. If you’re writing a function that deals with
MagicMoneyWorkers on a high level, you don’t want to have to dive into the bowels of the implementation. It makes much more sense for an exception with a matched level of abstraction to be thrown, and for your function to act on this exception.
Simply my Personal Approach
I doubt this approach will work well for all systems or applications, but I’ve found it works reasonably well for my development lately. Throughout my computer science education there hasn’t been much focus on how to handle exceptions. I think they are considered more of an engineering problem, because the code we deal with is so algorithmic in nature, and brushed under the rug as something you’ll pick up over time. Developing a personal framework for how to handle an exception, and giving a rigid procedure, purpose, and ordering to the process has tremendously helped hone my skills in developing real world applications. If you don’t have a clear idea of how to deal with exceptions in your own projects, I would definitely recommend spending some time to mediate on them and figure out what the goals of catching an exception are for you.