Exceptions Are Your Friend (but so is garbage collection)
By Adrian Sutton
Benjamine Carlyle and I have been discussing exceptions. Put simply, he hates them, I love them. I think I know why now.
Adrian Sutton argues that exceptions are not in fact harmful but helpful. I don’t know about you, but I’m a stubborn bastard who needs to be right all the time. I’ve picked a fight, and I plan to win it ;) I’m also a stubborn bastard (just ask Byron about ampersands), so let the death match begin! Actually, as I mentioned in this case I think we’re arguing different things so we might be able to resolve this amicably. Benjamin’s biggest problem with exceptions is that code in between the throwing of the exception and the handling of the exception “gets hurt”. The answer here is called abstraction. Abstraction in this context has a very simple tenant: when I call a method, it does stuff and I don’t care how it does it. In other words, if I call the method doStuff(), I don’t care if an exception is thrown in a deeply nested function call or directly by doStuff, to me doStuff simply through an exception. This is then true for doStuff, it only cares about each method it calls and not any methods under that and so on. So for any given piece of code we only think about one level. Methods and classes provide abstraction. Now that we’ve abstracted things, there simply is no code left in the middle to get hurt. Each method should handle whatever exceptions it may cause in whatever way it needs to. It’s the whatever way it needs to that differentiates Benjamin’s viewpoint and mine. Benjamin has been talking about exceptions in C++ whereas I have been talking about exceptions in Java. In C++ when an exception is thrown you have to carefully sort out what memory you had already allocated and not freed then make sure you free it etc etc etc. In other words, an unexpected failure is catastrophic and pretty much unrecoverable. In Java however, it’s no big deal at all. Objects will happily be garbage collected when no longer required, it’s simple to tell if objects have been created and it’s also really simple to clean up any allocated resources. So in Java if we were working with a database, we’d do something like:
public void doStuff() {
try {
_db.openConnection();
// Work with connection, possibly throwing exceptions.
} finally {
_db.releaseConnection();
}
} Note here that we didn't catch any exceptions, they will still propagate up to somewhere that can handle them intelligently (we probably should have caught them and used a wrapper exception for neatness but it's all the same concept). Intelligently might mean killing the application or notifying the user or perhaps even trying again. The finally block however is guaranteed to run in all cases (other than the JVM exiting – remember, System.exit() is evil). With that finally block in place, our method is now guaranteed to clean up after itself correctly no matter what happens. Situations that you need a finally block in java are pretty rare. It pretty much only happens when you're working with databases or object pools. In C++ however, anytime you allocate memory, you need a finally block and not only that, you need a pretty complex one to make sure that you don't miss freeing any memory and don't free memory that wasn't allocated. I don't know enough C++ to know if this is even possible but it definitely sounds complex. The
article that Benjamin points to discusses these problems but does so in the context of C++ and points out some incredibly shoddy points in the example code which would be flaws even if return codes were used. I fail to see anything in that article that is actually specific to exceptions. It’s all a lacking of programmer ability in the first place (which would have existed regardless of how errors were reported and handled) or limitations of the language in use (anything related to memory management). Regarding the quote:
Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it. Using exceptions is not writing something as cleverly as possible, it’s actually writing something as clearly as possible. In fact, exceptions are really no different to return codes except for the fact that they are more obvious. It is trivial to replace all instances where an exception is used with a form of return code instead but it will make the code significantly less readable. Besides, the “cleverly as possible” is particularly referring to using “cute” or “tricky” ways of doing things instead of using a clearer approach. Exceptions do not fall into this category as they were specifically created to make things clearer.
Adrian also picks up my final points about how to program with the most straightforward error checking, and perhaps I should have worded my final paragraphs more clearly to avoid his confusion. I don’t like threads (that share data). They do cause similar code path blowouts and comprehensibility problems as do exceptions unless approached with straightforward discipline (such as a discipline of message-passing-only between threads). Threads are difficult, threads do increase complexity, threads however are extremely important. Threads that don’t share data shouldn’t be different threads, they should be different programs (or at least processes). Threads must be used wisely. I’ll leave it at that for now, but expect a future entry on the subject.
Dealing with the operating system can’t be written off so easily, and my note towards the end of my original post was meant to convey my sentiment of “exceptions are no good elsewhere, and if the only place you can still argue their valid use is when dealing with the operating system… well… get a life” :) Anyone who puts forward the argument that exceptions are more important along operating system boundaries is an idiot. The clearest examples of exception usage is found at operating system boundaries definitely because that’s the area where unexpected errors are most likely to occur, however unexpected errors can happen in many more places than at the operating system boundary. Exceptions are appropriate anywhere that a return code is appropriate. Remember, exceptions are simply a different and clearer way of returning the error code.
Most software is far more internally-complex than it is complex along its operating system boundary. If you want to use exceptions there, feel free. Just don’t throw them through other classes. I personally think that exceptions offer no better alternative to return code checking in that limited environment. They are clearer. That’s all. Checked exceptions have the added benefit of forcing people to deal with them (another major reason why C++ exceptions aren’t particularly useful). When an IOException is thrown in Java, every single method that it might impact on must have explicitly dealt with it. No code will be caught out because they didn’t expect that exception, ever. I’m not familiar enough with C++ to be certain, but I am definitely leaning towards thinking that exceptions in C++ is evil, though mostly I’d just suggest that C++ itself is mostly the problem (it’s just too difficult to recover from unexpected errors). I don’t know what the solution to the problem is and I suspect that the same kind of problem would show up in C despite the fact that exceptions simply aren’t an option. What I can say without a doubt however, is that exceptions in Java are well thought out, simple to use and provide a massive benefit to writing clear, robust code. Most of this I suspect is due to the fact that it is surprisingly simple to recover from errors in Java in many situations. There is however a never-ending debate in the Java community about whether or not checked exceptions are a good thing or not. Personally I like them, James Gosling puts forward some very strong arguments for them but they do have some problems particularly when dealing with interfaces and the list of possible exceptions can get out of hand at times. Still, the concept of exceptions themselves is definitely beneficial, the particular implementation of them may not be though.