I suppose I should get around to replying to this article, part of a blogosphere exchange that Adrain Sutton and myself were having about a month ago when I dropped off the net for a while.
I'll start by attempting to resummarise our respective positions. Adrian, please let me know if I've misrepresented you on any of these points:
The Question | Adrian's position | My position |
---|---|---|
Main Language Background | Java | C++ |
Industry Background | Web-based Desktop applictions | Safety-related distributed back-end applications |
Main Software Concerns | Not surprising the user | Failing (over to the backup) in a way that doesn't kill anyone |
When to throw an exception (when to try and recover from an error) | Whenever unexpected input arrives. Some other code might be able to handle this problem in a way that allows the user to save their work. | Whenever unexpected input arrives from outside the program. Unexpected input from inside the program is an indication of faulty software and faulty software must be terminated with prejudice to avoid unsafe situations from arising. |
Uncaught exceptions | An inherently good backup strategy for regular expection handling. If noone knows how to deal with the condition the program exits (reasonably) gracefully. | What you do when you can't find the assert function :), but faulty nontheless. The thrower and rethrowers of the exception expected someone to catch it, and include code to be able to continue operating after the exception is caught and further functions are called on them. If the exception wasn't caught this recovery code is untestable at the system level and likely to be buggy. Throwing an exception in this case just adds a longer code path than you would find in the assert-based case and gives developers the false impression that they can rely on the recovery code. |
(Implicity or explicitly) Rethrown Exceptions | Good for propagating exceptions to the guy who knows how to deal with the problem | Possibly-surprising side-effects to function calls. In java this isn't a huge problem, as your code won't compile if you don't catch all of the exceptions you are supposed to catch. C++ doesn't hold you hand, unfortunately, but that's the fault of C++. Python is similar to C++ in this respect and I'll come back to python later in this blog entry. I get serious about control flow simplicity. In the normal case I forbid the use of multiple return statements in a function. If forbid the use of multiple exit points in loops. I forbid the use of not operators in boolean expressions where the "then" and "else" branches could simply be reversed. Let's just say that exceptions (like GOTOs) mess with my... space. |
Caught exceptions | The standard way of dealing with exceptional conditions | Personally, I would prefer an object be returned. If the object's return code (or activator function) was not accessed by the time the last reference disappeared, that's where things should die. The object would form a contract between the "thrower" and the caller to handle the object, and failure to do so would be termination-worthy. Awkward control flow jumps are bad. Returning multiple types from a single function is bad. Exceptions encapsulate both badnesses and make them a first-order language feature. |
The reason I started writing about exceptions was not to annoy the gentle reader. The spark was actually in python's pygtk module. You see, python has a problem: It doesn't detect errors at compile time. This means that whenever you make a typo or do anything equally silly you have to find out at runtime. Rather than terminating the process when these are discovered, python throws an exception.
Fine. An exception is thrown. I don't catch it. I get a stack trace on the command-line, and everyone's happy right?
No. The pygtk main loop catches it, reports it, and keeps running.
You can't really blame pygtk, can you? Just above it on the stack is the C-language gtk library code which has no exception support whatsoever. Even if it did, chances are it wouldn't support arbitrary python exceptions being thrown through it. The pygtk library has one of two choices: Catch it, or kill it.
As you might have gussed by now, my preference is "kill it!". There is supposed to be an environment variable in pygtk to govern this behaviour, but it appears to have disppeared at some point as I haven't been able to stop it from handling the exception. As a result, my program keeps running (after not fulfilling the task I gave it) and I have to explicitly check the command-line to see what went wrong. Pain-in-the-bloody-arse-exceptions.
I don't think exceptions are a great idea. I think they add more code than they reduce, and add more code paths than the (in my opinion) usually better course of action in terminating immediately. I know my views are heavily-grounded in systems where failure of the process doesn't matter because you aways have a redundant copy and where you're more worried about misleading data making its way to the user than to have them see no data at all. Still, I think my views are more applicable to the desktop world than the views of the desktop world are applicable to my world.
Save your work? That's for sissys. Use a journalling file-saving model. Save everything the user does immediately. You can support the traditional file save/load facility using checkpoints or other niceties but I fail to see why any application in this modern age of fast hard drives should ever lose data the user has entered more than a few hundred milliseconds ago.
Use exceptions because of a bug in some piece of code that's feeding you data? You're just propagating the bug and making handling it a much more complicated problem. Just fix the code in question and be done with it. Don't get me wrong, I absolutely think exceptions have some good points over returning a silly little error code. I just think that the bad outweighs the good. I believe that a refinement of the return code model rathern than the complete reimplementation we see in exceptions would have been a better course for everyone.
Benjamin