Wednesday, August 22, 2007

Paranoid Programming

I read and enjoy Joel on Software (and used his CityDesk product for my website). I have to respond, however, to some of his comments in this article.

I strongly agree with most of it. For example, I'm glad to see conventions such as "Don’t put closing braces more than one screen away from the matching opening brace." (And I wish I could enforce that standard at the place I work at!) Also, he writes: "I have to admit that I’m a little bit scared of language features that hide things". I have seen very confused code caused by "features" in C++ like the parentheses operator on iterators. I always prefer to see a real method name such as "Next()" (thanks to Nick Pouschine for putting into words the solution to my unease with that syntax). And I never have, and never will, overload the plus operator for any reason.

I don't, however, see any reason to write off exceptions. I depend very heavily on exceptions in all the C++ and Python code I write. Joel's example of:

dosomething();
cleanup();


just seems wrong. The cleanup code should be on the destructor (or some equivalent in non-object-oriented languages), which should always get called at some point. Note that I'm assuming you've adopted the convention of "every object is correctly owned" so that memory is always freed at the appropriate point in the code, regardless of whether errors occurred or not.

[Apologies in advance for the CAPITALS.] I don't EVER need to "investigate the entire call tree of dosomething()" to see if there’s anything problematic in there. Rather I ALWAYS assume the worst, which is that any line of can cause an error (divide by zero, anyone?). I'm not paranoid, they really are out to get me!

I therefore write all my code to handle errors anywhere. That way, if an error happens in my code or some third-party code that I'm using, it is handled. Once I "accepted" exceptions, then I wrote code that I found more readable. I also was more confident in its error handling capabilities.

For example, instead of having all that nesting in the code from Raymond Chen's article that Joel references, I could put the variables into fields in an object, all the cleanup code into a destructor and then "flatten out" the error checking (I'll try to put an example in here later...). I don't claim that there are fewer lines of code (there may be more), but the error handling code doesn't get in the way of my reading and understanding of the intent of the code. This is especially true when errors have to be propagated back up a long call stack, where there may be many intermediate functions that would have to return the error. With exceptions, I only care about where they are thrown and where they need to be caught, not all the intermediate points in between.

To me, having the "other channel" of exception handling removes noise from the code and makes it more readable. I agree with Raymond that exceptions are tricky and you have to know how to do the right thing (e.g., don't throw pointers to stack-based objects and make sure to understand threading issues). Once, however, I had those principles understood and enforced, I was able to keep the error handling code "out of the way" of the rest of the code. This allows me to focus on the intent of the code as I read it, and not have to mentally "filter out" all the error handling code.

I certainly don't think that I'm smarter than Joel or Raymond. I do know, however, that I prefer using exceptions rather than having every function return error codes, both for code readability and robustness. I know they make me a more productive programmer.