Thursday, October 21, 2010

Exceptions From Within

How do you properly raise and handle exceptions? This seems like a straightforward design problem. Accompanying exception handling in your code are questions about depth. For example, function a() raises an exception. Function b() calls a(). Should b() handle the exception? Or, should the exception continue outward in the call stack? The distance in the call stack, between the exception being raised, and the exception being caught, or handled, is up to the developer. This includes raising exceptions from within the exception handler itself.

Are these concerns a matter of coding-style? Or, can we establish a pattern that helps us decide where to raise and handle exceptions? Defining a generic exception handling pattern is hard to do because anything has the potential to raise an exception - intended or otherwise. From a functional perspective, exception handling is all about preventing unexpected events from disrupting your program. From a coding-style perspective, exception handling is all about knowing exactly where any given exception originated. Both are hard to do.

Picture the low-level functions in your code. The atomic functions that don't call anything else. These functions will, at one point or another, raise exceptions. The exceptions raised here will propagate upward in the call stack. Think of an exception as signal that notifies the rest of your application something went wrong. The signal is received at each point in the call stack. The only way an exception will stop propagating upward is if a handler for that exception type lives at that point.

Now, picture a higher-level function. A function that calls other, low-level functions. If this higher-level function knows about the exceptions that might be raised by the functions it calls, it can define a handler for them. This is the how exceptions are handled from a context outside that of which they are raised. All the handler needs to know about are the types of exceptions that might be raised. This is a key exception-handling concept - they are classes and therefore represent a type of thing that can go wrong. Any number of problems may cause an IOError to be raised. The handler only cares that it is an IOError. Anything else will continue upward in the call stack.

An exception handler has two components. The first, a try block, attempts to execute something. The second, an except block, conditionally runs if the first block fails. Is it plausible for us to raise exceptions from within the try block? Imagine an exception handler that handles a TypeError. If the try block itself raises a TypeError, the except block will handle it and the exception signal will stop propagating.

Raising exceptions from within the try block can reduce code verbosity. If I call a function that may raise an exception, you call that function inside a try block. Inside that same try block, you may call another function that doesn't raise an exception. For instance, the function might not return what you expected. In this case, you want your except block to run. It wont, however, because no exception was raised by the function call. Rather than alter the low level function, you can check the result and explicitly raise the exception that enables the except block.

Although this use method of exception handling is useful, it doesn't always feel right. Exceptions are indeed better suited for propagating outward in the call stack instead of being raised and handled in the same statement. Another drawback is that you are potentially blocking exception handlers even higher up in the call stack that might take a different approach to handling the same type of exception.

No comments :

Post a Comment