 |
Home > Detail: The Exceptional Exception


| A StickyMinds.com Original |
 | |  |  The Exceptional Exception
 By Tod Golding

  
 Summary: So much more than a bucket for your errors, exceptions can be a valuable tool that lets you communicate to your clients not only that there is a problem but why and where the code failed. |  |  |

|
|
 | If I were an exception, I might be feeling a bit unappreciated. Don't get me wrong, I think exceptions are mostly a well-understood construct. Developers seem to easily grasp the fundamentals of throwing and catching exceptions, but at the same time—even though the mechanics of exceptions are well understood—their power and value often go untapped.
Time and time again I come across code in which developers are taking a fairly shortsighted view of exceptions. For this group of developers, exceptions end up being little more than a big bucket where they can send all of their errors.
The code in figure 1 provides a good example of what I'm referring to. In this example, I have created a method to update a customer's bank account. This somewhat simplified method accepts a customer ID and an amount that will be used to increment the customer's account balance.
At first glance, this method seems relatively straightforward. So, what's wrong with it? Well, technically speaking, nothing. The method will achieve its purpose and will certainly capture and log errors if they occur.
However, even though this code works, it does little to tap into the power and expressiveness of exceptions. If you're using this approach, you're basically saying, "I have a big block of code I hope executes, and if for some reason it fails, I'll put a try/catch around it to log the error." From my perspective, that is far too narrow a use of exceptions.
Imagine for a moment you are an exception. There you are with your uniform on, pacing the sideline, chomping at the bit to go into the game to catch, process, and forward a wide variety of highly expressive error conditions to clients near and far. You just can't wait for the coach to send you in. Then, when your number is finally called, all you're asked to do is catch the most basic exception, log an error, and throw yourself over the fence. That's got to be humiliating.
I think one reason we end up undervaluing exceptions is because we all have a tendency to take a somewhat self-centered view of our own code. When we write methods, we're not always thinking about what the client might want to do when our code fails. Instead, it's easy to fall into the trap of just assuming clients always expect our code to succeed, and when it doesn't, we'll just log a fatal error.
With this viewpoint, it's easy to see why no one would make much of an effort to throw interesting exceptions. If the error is fatal, why should I care about what the client may or may not want to do after the method exits?
This argument falls apart quickly, however, when you star thinking about unit testing. Imagine you have to write unit tests for the code in figure 1. When you call the UpdateBalance() method in this example, a variety of things could go wrong. For example, you may want to have tests that pass in different user accounts—some with update privileges and some without. How can you differentiate between this security failure and the multitude of other failures that could happen within this method? It's simple—you can't.
To be fair, this isn't exactly news. I think most developers would look at the code in figure 1 and recognize the need for more granular exceptions. In fact, I think most people looking at this code could easily list a number of custom exceptions they might want to throw and end up with something resembling the code shown in figure 2. Unfortunately, there are too many times when we simply don't implement these exceptions.
Making your methods throw more granular errors is the easy part. The more difficult part is figuring out how you want to propagate these errors back to the client. This is especially significant when your exceptions must be propagated throughout a multilayered architecture.
Suppose you have a typical n-tier architecture with client, business logic, and data access layers. With this architecture, you'll want to be sure any layer-specific exceptions are not allowed to cross the boundaries. For example, you don't want the "SQLException: Foreign Key ACCOUNT_ID constraint violation" message that was generated in the data access layer propagated all the way back to the end-user.
So, when you design a system, you'll want to have a clear exception policy that allows you to define how each layer in your architecture will propagate errors to its clients. For example, if you call a method in the data access layer, you should know precisely what family of exceptions can be thrown by any public method in that layer. If any other form of exception comes out of that layer, something went wrong.
The code in figure 2 provides an example of this policy in action. Here, the UpdateAccount() method in my client layer is calling into the service layer of my architecture. So, each exception coming out of that layer should be part of my ServiceException family of exceptions classes.
This approach can simplify the testing of each layer, and it can reduce the complexity of each client's catch block. It also simplifies my logging policy. Whenever you catch one of your custom exceptions, you know it has been logged. However, if you didn't have this custom exception and you were only able to catch a generic Exception object, you'd essentially be in the dark about what might have already been done with this error. Should it get logged? You really have no way of knowing.
This example gets to the real heart of the issue. Exceptions are not meant merely to catch errors—they are supposed to be the tool that lets us establish a clear error-handling protocol between clients and the methods they call. We cannot view each exception condition as if it exists in a vacuum. Every time we catch an error and decide to throw an exception, we have to ask ourselves how that exception fits within the overall exception-processing scheme we have adopted.
My broader concern is that exceptions seem to get rolled into the simplistic domain of "error handling" and, as such, some developers implement their exception strategy with this same granularity and narrowed perspective. Exceptions can tell us much more than "did I succeed or did I fail?" They represent an opportunity for us to tell clients why and where our code has failed.
I guess that's why I'm feeling so bad for the exception these days. He's a great language construct and, given the opportunity, he has a lot to offer—if you can see him as something more than a simple error handler. {end}
How are you currently using exceptions to establish specific error-handling policies within your own systems?
Join the conversation below or start a new one in the Reader Comments section.
About the Author Tod Golding is the founder of Blue Puma Software, a technical consulting company that provides software training, mentoring, and development services. He has twenty years of experience as a software developer, lead architect, and development manager for organizations engaged in the delivery of large-scale commercial and internal solutions. With an extensive background leveraging .NET, J2EE, and Windows DNA technologies, Tod has become equally skilled with C#, Java, and C++. He has worked and consulted at a variety of companies, including Microsoft and Borland. Tod is the author of Professional .NET 2.0 Generics and a contributing author of the XML Programming Bible.
Back to Top
|