The trouble with a lot of example code covering exceptions is that the examples are often cases in which you shouldn’t be using an exception in the first place. Consider the classic known as “Someone’s trying to divide by zero” – here’s the C# version:
result = dividend / divisor;
catch (DivideByZeroException ex)
and here's the Ruby version:
// Ruby (works in IronRuby too!)
result = dividend / divisor
// You have to hand it to Ruby for picking great keywords for
// exception handling. While C# borrowed Java's "try / catch / finally",
// Ruby went with the more macho "begin / rescue / ensure".
// As Yoda himself would say: "Do or do not. There is no try."
The better approach would be to do a little defensive programming and make sure that divisor is non-zero before performing the division operation. So why do tutorials on exception handling almost always bring out the “Someone’s trying to divide by zero” example?
There are two reasons:
The truly exceptional exceptions -- I/O errors, timeouts and other things that cause exceptions are a little harder to set up and take more code to handle. Hence the divide-by-zero example; it illustrates try and catch (or rescue in a Ruby block) in a way even the newest newbie can understand.
The problem is that many tutorial authors don’t get any deeper than simply explaining the keywords with simple examples, leading people to misuse exceptions, either as a substitute for checking for preconditions or as an unstructured form of flow control in the style of the much-maligned goto (which in many cases is considered harmful).
Like goto, exceptions are unstructured jumps, which make your program’s flow more complex. Unlike goto, exceptions are computationally “expensive” because of all the extra work involved in setting up and backtracking program flow that comes with a thrown exception.
A good guideline to follow is that exceptions are for exceptional cases. Stuff that you can’t easily predict. You can tell if a division operation is going to result in an undefined result – just look at the divisor! Harder to predict are things like whether a server access will time out or if the hard drive will decide that the moment you’re reading a file is the best possible time to corrupt it. Those hard-to-foresee, believed-to-be-rare, exceptional cases are really what exceptions are meant to handle.
Think of exceptions is as being like the airbags in your car. The idea is that they’re a last resort; they’re no substitute for defensive driving. (Like airbags, they’re also expensive to reset.)
Lee Dumond goes into further detail on the topic of defensive programming as being like defensive driving in an article titled Defensive Programming, or Why Exception Handling Is Like Car Insurance. He cites the “Someone’s trying to divide by zero” example, provides a list of defensive programming strategies that you should consider before coding up that exception handler and talks about those exceptional cases when you will have to use an exception. Check it out!
(This article also appears in my personal technical blog, Global Nerdy.)
I completely agree with your statement about most tutorials just explain the keywords and easy/contrived examples! I've created a blog series to help/try to adddress these short comings! I hope people enjoy.
Exceptional Series (1/3): Throwing exceptions in different ways (http://www.pchenry.com/Home/tabid/36/EntryID/121/Default.aspx)
Exceptional Series (2/3): Performance impacts with Exceptions (http://www.pchenry.com/Home/tabid/36/EntryID/124/Default.aspx)
Exceptional Series (3/3): Alternative error handling mechanism for Exceptions (http://www.pchenry.com/Home/tabid/36/EntryID/123/Default.aspx)
Bonus: a truely exceptional case but even then, the Exception being thrown is actual a lie, DOH! (http://www.pchenry.com/Home/tabid/36/EntryID/125/Default.aspx)
I would say that "catches" are the airbags of code. The exception is the accident...
You're missing one *very important* aspect of exception handling: don't catch exceptions unless you know what can do to compensate for them *and* can do something to compensate for them. Exceptions are "hierarchial", there may be higher level code that handles an exception and has enough information to act upon that exception. If lower level code catches that exception but doesn't do something appropriate with it, it takes away the ability of that higher level code for handling that exception more appropriately. In the divide by zero example:
public int Divide(int dividend, int quotient)
// Now what do I do?
By catching the exception here we take away information from higher level code that could have done something (like inform the user their entry of "0" was invalid for a quotient. In this example, you could suggest that returning zero is appropriate for a return value for a divide by zero operation. But, the method that performs the division isn't the place to make that decision. That is an SRP violation at the very least. It would replace a valid condition with, essentially, an error condition. The caller of Divide would never be able to truely know if a result of "0" is an error condition and thus can never do anything with divide by zero conditions.
It's not about syntax (as you say), it's about knowing how, *when* and *where* to catch exceptions. Saying "exceptions are only for exceptional cases" doesn't give people the tools to correctly write exception handling code.
I agree with Peter's final comment. Good exception handling is about knowing when, where, and how they can/will occur along with knowing what you can do with them at that point in time of the code's execution stack. Too many developers write code like the Divide example you show and think, "Well a divide by zero exception can happen here so I should wrap this with a try...catch" without understanding if they really can do anything once the exception is caught.
In some circumstances your example might be valid. Those circumstances would dictate that you can gracefully recover the code's execution path by catching and handling the thrown exception. When doing this you are guaranteeing that all code calling this method will continue to execute in the expected way even if the divide by zero happens.
In other circumstances there may be nothing that you can do to control the failure of the code. Take, for example, a divide by zero error when calculating the tax on an invoice just prior to saving to some persistance store. In this case I'd strongly suggest that the code cannot continue to save the invoice with a non-calculated tax amount. This is an unrecoverable execption from the standpoint of the code's execution path. Here you'd want to let that exception bubble up further in the call stack before handling it in a way that said to the user "Hey, we had a major error here and we can't go any further". Simply catching the divide by zero exception in the Divide method will most likely not allow you to handle these types of failures in the manner that the business or users need you to.
When I see brownfield code that is this granular in its capabilities and I see the try...catch in it, I begin to thing that the application is going to have severe issues with swallowing of exceptions leading to extremely hard to trace defects. Handling errors in a maintainable and meaningful way isn't just about throwing try...catch at every place you see a possible error. It's about understanding which exceptions can occur and still allow the application to run as originally intended and those which require you to stop future execution from occurring.
Peter, Peter and Donald: Thanks for chiming in!
Just as pointers in C was a thorny enough topic to be worthy to have whole books written about it, I think that exception handling is equally worthy. It's a topic that I plan to explore regularly through posts in this blog, based on my own experience, other people's code (some good, some bad) and the literature that's out there, even the papers in "Advanced Topics in Exception Handling Techniques" (http://www.amazon.com/Advanced-Exception-Techniques-Programming-Engineering/dp/3540374434/ref=sr_1_3?ie=UTF8&s=books&qid=1249432705&sr=8-3).
This article, and the ones that will follow this month, are meant to whet the appetites of readers for more articles about the fundamentals. More in-depth articles will begin in the fall, which should give me enough time to write meatier pieces as well as set up code for readers to play with.
At the same time, I'd like to harness the considerable brainpower of the more seasoned and skilled members of the community. If any of you guys would like to contribute an article to this blog with your particular take on exception handling, drop me a line (email@example.com) -- we should talk!
A quick note for readers who didn't get the "SRP violation" that Peter Ritchie mentioned in his comment: SRP is short for Single Responsibility Principle and is one of the five SOLID principles of object-oriented design. SRP says that every object should have a single responsibility and that all of its services should be aligned with that responsibility.
I gave a brief overview of the SOLID principles in this entry:
and Robert C. "Uncle Bob" Martin did his usual bang-up job of covering them in detail here:
I agree. I've seen a lot of code littered with try-catch that did little other than either suppress the exception (bad) or rethrow it (pointless). We've established some standards around exception handling on my team, like use try-catch only as a last resort and only when you're going to do something meaningful with the exception.
Agreed! I wrote a <a href="http://thelimberlambda.com/2009/07/15/on-exceptions/">similar article</a> a while ago.