Updated: there is now a page on this site dedicated to this topic.
About 8 months ago, I was going to a big blog post on error handling in programs. I think its an interesting and practical topic. Proper code requires lots of good error handling. I was trying to understand which systems work the best and I couldn’t really come to a conclusion myself.
So before going on, what is the best system for error handling that you have used?
How does language X, Y, or Z handle it. (C, Smalltalk, Objective-C, Forth, Common Lisp, Io, Erlang Haskell, Ruby, Python, PHP, Java). What about OS level systems or windowing systems?
What is the best system that you have used?
Just to get things started. I’ve found that a lot of the C style systems to be too laisez faire. On the other end of the spectrum, putting a try / catch/except handler for every possibility doesn’t make me feel too good either. Then you have things like PHP which toss errors if you use an ‘@’ symbol on the function call.
Anything innovative out there?
The most interesting I’ve seen is actually Common Lisp’s, because it not only lets you define error “conditions,” it also lets you define restart points. That’s worth a couple points in my book.
Ruby has an interesting retry/next functionality that lets you modify some state and retry the exception generating block. There’s a code example in the pickaxe from the SMTP handling code which uses a retry to do a EHLO command, and then if it fails, fall back to HELO.
Erlang has a wacky thing, where many functions return a tuple of the form {ok, Result} or {error, Info}, so you can use the built in pattern-matching to deal with errors and successful termination. It also uses a “the process has crashed” metaphor rather than a try/except metaphor. They have some very interesting philosophies — I am just learning Erlang but my feeling is that they have a lot of great methods for writing very robust software, because of the simplicity of the system.
I pretty much hate the way C deals with errors, primarily by setting a global variable “errno” and returning something meaningful or else NULL. Crap crap crap. Nearly every other language I know provides some variation on try/except or try/catch, which seems to be sufficient for most things although it’s weird to have languages which cannot examine the type of an arbitrary object yet are capable of using that to determine the type of error which has occurred. People sometimes complain about the complexity of code which uses them. In my experience, I was best served when I dealt with expected errors as closely to their source or else not dealing with them at all and having an overall exception handler which produced a “Whoops! You Found a Bug!” window or page and then letting users submit it with full stack trace so I could add the proper handlers to the proper locations.
In general I think there are two philosophies for dealing with errors: either write code which has no bugs, using a rather painful development style such as zero-defect, or treat bugs as a management problem as I describe above. Exceptions via try/except are really quite amenable to the management style of programming.
Having been first exposed to exceptions in C++ and a little in Java, I thought I would hate them forever. Especially when I was working on an IIS CGI service in Windows — back in ’96, if there was an unhandled exception error, IIS, which runs as a service, would pop up an Unhandled Exception error dialog — to a non-existent terminal screen. You couldn’t delete the file on disk (for example, to install a corrected version of the code), because it was ‘in use’ — because of the dialog box. You couldn’t kill the service, either, because services ran at a security higher than administrator. Ugh, every exception in testing, I had to reboot the damn box completely.
However, having worked with exceptions in Python, I started liking them quite a bit — the key elements, I found were things like the “finally” clause, which allowed you to make sure you were able to clean stuff up before you left the scope. Particularly important when dealing with networked elements, and you want to be able to pass warnings and close connections from outside systems.
That said, I have to agree with Daniel — expect and handle exceptions as locally as possible (or as locally as makes sense — sometimes, things like “connection terminated” exceptions make sense to handle at a higher level in a network manager thread than at each little I/O call in the application logic), and use a global handler to report issues. This is especially cool in website development, because then you can have a single high-level catch that can conditionally dump your traceback to the browser (or to a log file, or whatever), to help you track down stuff during testing, making sure, naturally, that you’ve output the proper content-type headers…
I like Ruby, although I think their implementations are only slightly better than Python (which I guess is kinda interesting, given that Ruby has actually been around longer, IIRC)…
In C, even C++, I just make sure I’m always sending back an integer return code, and put any other return variables into the parameter stack with pointers — unless I’m sure I can get away with some very simple return values (like NULL for functions that would normally return string pointers)… Then it’s really just a question of being a stickler for checking your return results. Back at ONElist, I learned the neat trick of using “do { } while(0);” so you can use “break;” as a quick exit from a block of code. That actually works quite well to keep code fairly clean, particularly if you’re doing a lot of data validation, although it’s definitely not obvious what the purpose of the structure is, if you’ve not seen it done before…
…Paul
Here’s my basic philosophy regarding C++/Java/C# exceptions:
There are basically 3 reasons to catch an exception.
Catch an exception if it can actually be handled.
It’s often useful to catch an exception X at a boundary between abstraction levels, wrap X in a higher level exception Y that is more meaningful to the next higher abstraction layer, and throw Y.
If an exception can’t be propertly handled, admit defeat, report failure and log further details.