views:

269

answers:

4

(not sure if it's only a C++ thing)

Exception handling is hard to learn in C++ and is certainly not a perfect solution but in most cases (other than some specific embedded software contexts) it's certainly the better solution we currently have for exception handling.

What about the future?

Are there other known ways to handle errors that are not implemented in most languages, or are only academical research?

Put another way: are there (suposedly) known better (unperfect is ok) ways to handle errors in programming languages?

+5  A: 

I can't say that it is better than exceptions, but one alternative is the way that Erlang developers implement fault tolerance, known as "Let it fail". To summarize: each task gets spawned off as a separate "process" (Erlang's term for what most people call "threads"). If a process encounters an error, it just dies and a notification is back to the controlling process, which can either ignore it or take some sort of corrective action.

This supposedly leads to less complex and more robust code, as the entire program won't crash or exit due to lack of error handling. (Note that this robustness relies on some other features of the Erlang language and run-time environment.)

Joe Armstrong's thesis, which includes a section on how he envisions fault-tolerant Erlang systems, is available for download: http://www.erlang.org/download/armstrong_thesis_2003.pdf

Kristopher Johnson
+1: actually Erlangs approach is "let it fail, we're ignoring it anyway ;>"
Kornel Kisielewicz
It sounds intriguing in theory but how would it work in practice? Ex. You make a call to foo() which fails and you depend on foo()'s result. If you need to be asynchronously notified if foo() failed things can get complicated.
seand
I don't like this. IMHO the sane default for error handling should be that, unless you explicitly handle a given error, you implicitly assert that it can't happen in your use case and your program crashes if it does. In addition to out of band signaling, exceptions provide a nice, sane fail-fast default.
dsimcha
@dsimcha, @seand: The thing about Erlang's philosophy is that you're *not* depending on the result, as such. Usually, when you spawn off a process, it's to handle some job, and you really don't care whether it completes successfully or not. Erlang was designed for fault-tolerant systems like PBXes, where if there's a problem, you'd rather that it doesn't propagate to the running processes. Likewise, if you're running a web service, you don't want one error on one session to take down the whole system. You just let the process die and start it anew.
greyfade
To be clear, the reason Erlang has this philosophy is that when you spawn a new process, it doesn't "return" anything. You send it a message to be handled, and it *may* send a message back in response.
greyfade
Functionally, how is aborting a subprocess and sending a notification all that much different from throwing an exception?
David Thornley
@dsimcha: IMHO, there are three kinds of exceptions: (1) those where an attempted operation failed but did not change any invariants, (2) those where a failed operation may have invalidated some invariants, and (3) those where the whole system is melting down. If an unexpected exit from a routine would break invariants in a way that try/finally can't fix, the routine should catch type-1 exceptions and rethrow as type 2 (with an InnerException). Otherwise catch and swallow at the level where try/finally blocks will have restored any invariants. Too bad many hierarchies don't make that easy.
supercat
+1 Erlang is not a theoretical/academic language. Ericsson developed it because they needed their switches and routers to be fault tolerant. No other language (even now) gave them what they wanted.
slebetman
Is the difference in philosophy between Erlang and C++ a wall that makes unatural to add a similar feature to C++? (or other imperative languages maybe?)
Klaim
The Erlang function-as-task behavior is sometimes known as "futures". In the next release, C++ will get library support for futures.
MSalters
One big difference between Erlang and C++ is that Erlang runs in a virtual-machine environment, so one doesn't need to worry about "native" exceptions and other faults. Another is that the current C++ standard doesn't really address concurrency, so that tends to lead to platform-specific features and behavior.
Kristopher Johnson
+6  A: 

Well, there's always been return codes, errno and the like. The general problem is that these can be ignored or forgotten about by programmers who are unaware that a particular call can fail. Exceptions are frequently ignored or missed by programmers too. The difference is that if you don't catch an exception the program dies. If you don't check a return code, the program continues on operating on invalid data. Java tried to force programmers to catch all of their exceptions by creating checked exceptions, which cause a compilation error if you don't specify exactly when they can be propagated and catch them eventually. This turned out to be insanely annoying, so programmers catch the exceptions with catch(...){/* do nothing*/} (in C++ parlence) as close to their source as possible, and the result is no better than ignoring a return code.

Besides these two error techniques, some of the functional languages support the use of various monadic return types which can encapsulate both errors and return values (e.g. Scala's Either type, Option type, or a monad that lets you return an approximate answer along with failure log). The advantage to these is that the only way to work with the successful return value is to execute code inside the monad, and the monad ensures that the code isn't run if there was a failure. (It's rather complicated to explain for someone who isn't a Haskell or Scala programmer.) I haven't worked with this model so much, but I expect it would be as annoying to some people as checked exceptions are.

Basically, IMO, error checking is a matter of attitude. You have three options:

  1. Realize you have to deal with it, accept that fact cheerfully, and take the effort to write correct error handling code. (Any of them)
  2. Use language features that force you to deal with it, and get annoyed because you don't want to deal with it, particularly when you're sure the error will never happen. (Checked Exceptions, Monads)
  3. Use langauge features that allow you to ignore it easily, and write unsafe code because you ignored it. (Unchecked Exceptions, Return Codes)
  4. Get the worst of both options 2 and 3 by using language features that force you to deal with it, but deali with every error in a way that explicitly ignores it. (Checked Exceptions, Monads)

Obviously, you should try to be a #1 type programmer.

Ken Bloom
I never thought of exceptions as errors that can't be ignored. I liked the point :)
Cătălin Pitiș
"except ignoring an exception means the program dies" the obvious hitch here is that the program dies, but maybe not *right now*. It will die 10 minutes from now, after your rogue program has eaten your heap. And then it will give you a core dump that leads you on wild goose chases and no hint and to the actual cause of the crash.
John Dibling
@John: Ignoring an exception by not even trying to catch it means your program dies instantly, because the exception propagates all the way to `main()` without finding a handler, so the C++ runtime calls `abort()`.
Ken Bloom
Maybe John meant ignoring in the `catch (...)` discarding sense rather than the unhandled sense.
Ben Voigt
I'll clarify my post.
Ken Bloom
+5  A: 

Assuming you want your code to do different things according to whether an error occurs or not, you have basically three options:

1) Make this explicit everywhere in the code (C-style error return value checking). The main perceived disadvantage is that it's verbose.

2) Use non-local control flow to separate error-handling code from the "usual path" (exceptions). The main perceived disadvantage is keeping track of all the places your code can go next, especially if documented interfaces don't always list them all. Java's experiment with checked exceptions to "deal with" the latter issue weren't entirely successful either.

3) Sit on errors until "later" (IEEE-style sticky error bits and quiet NaNs, C++ error flags on streams), and check them only when convenient for the caller. The main perceived disadvantage is that setting and clearing errors requires careful use by everyone, and also that information available at the site of the error may be lost by the time it's handled.

Take your pick. (1) looks bloated and complex, and newbies mess it up by not checking for errors properly, but each line of code is easy to reason about. (2) looks small and simple, but each line of code might cause a jump to who-knows-where, so newbies mess it up by not implementing exception guarantees properly, and everyone sometimes catches exceptions in the wrong places or not at all. (3) is great when designed well, but you never know which of several possibilities each line of code is actually doing, so in a UB-rich environment like C++ that's easy to mess up too.

I think the underlying problem is basically hard: handling errors explicitly increases the branches in your code. Handling errors quietly increases the amount of state that you need to reason about, in a particular bit of code.

Exceptions also have the "is it truly exceptional?" problem. You could prevent exceptions from causing confusing control flow, by throwing them only in cases that your entire program can't recover from. But then you can't use them for errors which are recoverable from the POV of your program but not from the POV of the subsystem, so for those cases you fall back to the disadvantages of either (1) or (3).

Steve Jessop
A: 

Common Lisp's condition system is regarded as being a powerful superset beyond what exceptions let you do.

munificent