If you use C++ without exceptions, it means you cannot use the STL or any library that throws. Pretty weak.
Additionally, almost all examples of "exceptions are bad", fall short because of RAII (at least in C++) or finally clauses (in other languages).
Good reasons for (almost) not using exceptions (in C++):
- Performances
- "Expectability"/readability
- Platform-dependant constraints
There are probably others, but the article only talks about the "readability" arguments without even mentioning things such as exceptions specifications.
One thing you need to remember about exceptions: they're nothing more than a fancy goto.
What a weird claim. Not only do you not need exceptions to use the C++ standard library, but you can (or at least used to be able to) compile C++ code without support for exceptions.
Also, exceptions are not just a fancy goto, at least in C++. They're a fancy longjmp. Goto doesn't unwind the stack.
Edit
Wait wait wait it appears I am wrong about this; the STL we used could be configured not to use exceptions but wow, that sucks, you need them for the standard library.
STL "containers" and "streams" that need exceptions are pretty useless for any sort of critical code IMHO. STL "algorithms", on the other hand, are pretty useful and work with exceptions disabled just fine.
This, of course, depends on your definition of "critical code". I, for one, don't see how anything critical can rely on the single memory allocator that came bundled with the compiler.
If this is FUD, please downvote me into grayness, but I remember an article - or maybe even a book - proving that exception-safety in C++ was impossible. I remember nothing else about this; it was probably around 1999-2000.
Am I completely making that up? Or did we discover ways around the "impossible" part? I haven't coded in C++ since, uh, gcc 2.97 or so.
Can someone who uses exceptions in large systems offer a comment on which exceptions they throw?
One of the problems I have in trying to design with exceptions is that they seem to offer a lot of potential to break abstraction layers.
As a concrete example, if you have a cacheing layer which - say - is implemented over the filesystem. In the event of an inability to access the correct location, the low-level routines might throw a permission-related exception. If you swap out the filesystem cacheing implementation with, say, one based on memcached you might instead get a network-related exception.
Neither of these types really make sense in the context of a 'cacheing layer error', they seem to leak implementation details - breaking the abstraction.
Do people accept this, or do they catch-and-rethrow new exception types at major layer boundaries? (e.g. in the above two implementations, both low-level exceptions may be caught and rethrown as "CacheInitialisationError" with some perhaps some additional diagnostic about the underlying cause). If they have such API-specific error types, do you go to the trouble of modelling an exception type hierarchy? So all your 'cache layer' exceptions inherit from 'CacheError' so a higher level can catch all "CacheExceptions" in one place? This seems like a lot of additional modelling effort, is it commonplace?
To my mind, the possible exceptions a API call may make (or equivalently, the errors it could return) are part of the API, and the errors need to be at the same conceptual level as the rest of the API. (So you can't have filesystem errors in a generic cache API).
Given your scenario, you are indeed correct: throwing implementation-specific exceptions leaks internal details and further couples clients of the API. Can you imagine using such an API which forces you to continually handle new exceptions that arise from new implementations? As you suggest, it's better to create an exception for the API which then wraps the underlying exception. I would not go so far as to create a family or hierarchy of API exceptions unless there's a need to communicate each of them individually.
What usually happens in practice is a combination of none and all the above. A bit of a mess, really.
When I "simplify" a bunch of stuff into a nice pretty "thing", I'll catch all exceptions (Throwable) and then re-throw if. Depending on the thing, I'll stick an enum in my exception. Usually, I only care about "should I retry?" or "give up"
Personally, I think exceptions were a mistake for errors. I prefer how you do error codes via ocaml.
let val = func ... in
match val with
Result(v) -> do nice thing
| Error(500) -> retry
| Error(503) -> retry
| Error(_) -> abort
Then even if wait_for_our_men_to_come_in() throws we still close the gate. There is additional granularity - we can execute statement on success or failure too.
You can do the same thing in C++ RAII with an extra function scope though. Just create a Gate object or whatever and drop it on the stack before you call the wait_for... thing. An exception thrown out of that scope will clean up the Gate object for you and close it. Or just use Java, which has a "finally" syntax that does the same thing. Or Python's "with", which match D very closely indeed.
The point is that D is hardly unique about this. And further: the fact that D has syntax that makes this operation clean doesn't mean that it will be used. That first example can be written in any language, and often is. The "open/close" pair is clear and obvious in this example for didactic purposes, but in old code under maintenance that kind of assumption can be very subtle.
Syntax won't save you here. The problem being explained (which I only partially agree with) is that the whole exception model is flawed.
Erik Meijer interviews Robert Griesemer about Go. They chat about exceptions for error codes. The exception talk starts ~8:00.
Erik is in favor of failing fast with exceptions b/c most of the time, when you have an exception in an application, you want to fail fast because there is "nothing you can do" at the point where you have an unexpected null, etc.
Very interesting discussion between two smart guys.
Java world -> Aren't checked exceptions just error codes that use types instead of arbitrary values and compiler gaurantees?
And wasn't the thought that checked exceptions would be better for critical code because the compiler guaranteed that they were handled.
I'm no fan of java's error handling, but I think that the insinuation that the lack of any formal error handling makes you write less error prone code is bordering on the absurd.
Detail oriented individuals write robust code given the constraints of their environment. If its arbitrary strings that indicate error state they use strings if its integer return codes they use integer return codes.
This is why I hate all modern programming languages (I'm actually writing an article on that >.>). The conclusion to this article is the same as many others. Programming language feature X is better in situation A and Programming language feature Y is better in situation B. Where the features are often incompatible (or fulfill similar areas, error handling in this case). The solution is of course to have multiple programming languages in your projects that have both situation A and situation B
But languages are so annoying to integrate (where they can call into each other and support each other's features, without ridiculous glue code. Let alone swap code in the middle of a file), unless they were designed to (e.g. python and C). It is hell to set up the library dependencies and get the compilers to cooperate with each other. Which is why most just pick the language that's best for the majority of their project (or spend large amounts of developer time integrating other programming languages (not scripting languages, that's related but not quite the point) into their project).
Which is why we need the ability to change semantic languages within the same programming system, and have the programming system resolve the differences between the semantic languages automatically. So you can have one semantic language with features designed for situation A and another with features designed for situation B and just swap between them as needed and have them compile together automatically. This is what my Masters thesis and probably what my PhD dissertation will be on.
your first instinct should be to use finally, not catch, but Java brought us the tragedy of checked exceptions (in which your #1 motivation in working with exceptions is to shut up the compiler) and the bad habits have been adopted by people who use other languages that copy the (otherwise pretty good) Java exception handling style.
It's very good that you understand how to write exception-safe code (although makeAMess() should typically be OUTSIDE the try {} block), but he's clearly comparing the NAIVE cases, i.e., people not paying attention to errors in either idiom.
Exceptions are far far more readable than error code handling. Exceptions allow one to separate error handling from the structure of the "mainline" execution path of an algorithm. Weaving if/else statements in and out of the mainline path of an algorithm muddies the intent. Exceptions help with this greatly. Instead of having to comprehend the entirety of the algorithm (when exception handling can sometimes take up 50% or more of the code), you can zero in on the most common execution path. Then understand the degenerate cases in turn. The fewer branches your brain has to process in any given moment is a boon for readability.
Yes, exceptions are just fancy goto's--but that is a good thing! Goto's when used sparingly can greatly increase the readability of exception handling code. Exceptions simply create a language level abstraction for this functionality.
> Yes, exceptions are just fancy goto's--but that is a good thing! Goto's when used sparingly can greatly increase the readability of exception handling code. Exceptions simply create a language level abstraction for this functionality.
That is entirely incorrect, except in the odd case of Sinclair ZX Spectrum Basic, which is the only programming language that I"m aware of that allowed you to do "LET a=10: GOTO a" (all other languages with goto require a label; GCC has an extension for "indirect goto" and "label address" that is somewhat similar, although it restricts you to staying within the same function scope).
If anything, it's a fancy "longjmp", that also calls destructors along the way.
The big difference, and it is HUGE, is that when you do a "goto", you know where you the next instruction is coming from. When you throw an exception, you do not know which instruction will execute next from looking at the throw site (and in fact, you may need to conceptually inspect an unbounded number of stack frames to tell; which is different from longjmp or expression goto which only require you to examine one memory location).
> Exceptions allow one to separate error handling from the structure of the "mainline" execution path of an algorithm
In theory. In practice, they essentially guarantee that the error handling is not properly tested, and often useless except in the case of "everyday expected errors", which are not really exceptional, but rather quite frequent.
[+] [-] shin_lao|13 years ago|reply
Additionally, almost all examples of "exceptions are bad", fall short because of RAII (at least in C++) or finally clauses (in other languages).
Good reasons for (almost) not using exceptions (in C++):
- Performances
- "Expectability"/readability
- Platform-dependant constraints
There are probably others, but the article only talks about the "readability" arguments without even mentioning things such as exceptions specifications.
One thing you need to remember about exceptions: they're nothing more than a fancy goto.
[+] [-] tptacek|13 years ago|reply
Also, exceptions are not just a fancy goto, at least in C++. They're a fancy longjmp. Goto doesn't unwind the stack.
Edit
Wait wait wait it appears I am wrong about this; the STL we used could be configured not to use exceptions but wow, that sucks, you need them for the standard library.
[+] [-] pandaman|13 years ago|reply
This, of course, depends on your definition of "critical code". I, for one, don't see how anything critical can rely on the single memory allocator that came bundled with the compiler.
[+] [-] jaylevitt|13 years ago|reply
Am I completely making that up? Or did we discover ways around the "impossible" part? I haven't coded in C++ since, uh, gcc 2.97 or so.
[+] [-] KaeseEs|13 years ago|reply
[+] [-] ksherlock|13 years ago|reply
[+] [-] jbert|13 years ago|reply
One of the problems I have in trying to design with exceptions is that they seem to offer a lot of potential to break abstraction layers.
As a concrete example, if you have a cacheing layer which - say - is implemented over the filesystem. In the event of an inability to access the correct location, the low-level routines might throw a permission-related exception. If you swap out the filesystem cacheing implementation with, say, one based on memcached you might instead get a network-related exception.
Neither of these types really make sense in the context of a 'cacheing layer error', they seem to leak implementation details - breaking the abstraction.
Do people accept this, or do they catch-and-rethrow new exception types at major layer boundaries? (e.g. in the above two implementations, both low-level exceptions may be caught and rethrown as "CacheInitialisationError" with some perhaps some additional diagnostic about the underlying cause). If they have such API-specific error types, do you go to the trouble of modelling an exception type hierarchy? So all your 'cache layer' exceptions inherit from 'CacheError' so a higher level can catch all "CacheExceptions" in one place? This seems like a lot of additional modelling effort, is it commonplace?
To my mind, the possible exceptions a API call may make (or equivalently, the errors it could return) are part of the API, and the errors need to be at the same conceptual level as the rest of the API. (So you can't have filesystem errors in a generic cache API).
What happens in practice?
[+] [-] MikeCodeAwesome|13 years ago|reply
What usually happens in practice is a combination of none and all the above. A bit of a mess, really.
[+] [-] mathgladiator|13 years ago|reply
Personally, I think exceptions were a mistake for errors. I prefer how you do error codes via ocaml.
let val = func ... in match val with Result(v) -> do nice thing | Error(500) -> retry | Error(503) -> retry | Error(_) -> abort
[+] [-] kombine|13 years ago|reply
The technique was originally developed by Andrei Alexandrescu for C++ and described in http://www.drdobbs.com/cpp/generic-change-the-way-you-write-...
[+] [-] ajross|13 years ago|reply
The point is that D is hardly unique about this. And further: the fact that D has syntax that makes this operation clean doesn't mean that it will be used. That first example can be written in any language, and often is. The "open/close" pair is clear and obvious in this example for didactic purposes, but in old code under maintenance that kind of assumption can be very subtle.
Syntax won't save you here. The problem being explained (which I only partially agree with) is that the whole exception model is flawed.
[+] [-] pragmatic|13 years ago|reply
http://www.joelonsoftware.com/items/2003/10/13.html
Also:
http://channel9.msdn.com/Blogs/Charles/Erik-Meijer-and-Rober...
Erik Meijer interviews Robert Griesemer about Go. They chat about exceptions for error codes. The exception talk starts ~8:00.
Erik is in favor of failing fast with exceptions b/c most of the time, when you have an exception in an application, you want to fail fast because there is "nothing you can do" at the point where you have an unexpected null, etc.
Very interesting discussion between two smart guys.
[+] [-] nsfyn55|13 years ago|reply
And wasn't the thought that checked exceptions would be better for critical code because the compiler guaranteed that they were handled.
I'm no fan of java's error handling, but I think that the insinuation that the lack of any formal error handling makes you write less error prone code is bordering on the absurd.
Detail oriented individuals write robust code given the constraints of their environment. If its arbitrary strings that indicate error state they use strings if its integer return codes they use integer return codes.
[+] [-] SolarNet|13 years ago|reply
But languages are so annoying to integrate (where they can call into each other and support each other's features, without ridiculous glue code. Let alone swap code in the middle of a file), unless they were designed to (e.g. python and C). It is hell to set up the library dependencies and get the compilers to cooperate with each other. Which is why most just pick the language that's best for the majority of their project (or spend large amounts of developer time integrating other programming languages (not scripting languages, that's related but not quite the point) into their project).
Which is why we need the ability to change semantic languages within the same programming system, and have the programming system resolve the differences between the semantic languages automatically. So you can have one semantic language with features designed for situation A and another with features designed for situation B and just swap between them as needed and have them compile together automatically. This is what my Masters thesis and probably what my PhD dissertation will be on.
[+] [-] PaulHoule|13 years ago|reply
try { makeAMess() errorProneOperation() } finally { cleanUp() }
your first instinct should be to use finally, not catch, but Java brought us the tragedy of checked exceptions (in which your #1 motivation in working with exceptions is to shut up the compiler) and the bad habits have been adopted by people who use other languages that copy the (otherwise pretty good) Java exception handling style.
[+] [-] microtonal|13 years ago|reply
[+] [-] rix0r|13 years ago|reply
[+] [-] to3m|13 years ago|reply
[+] [-] hackinthebochs|13 years ago|reply
Yes, exceptions are just fancy goto's--but that is a good thing! Goto's when used sparingly can greatly increase the readability of exception handling code. Exceptions simply create a language level abstraction for this functionality.
[+] [-] beagle3|13 years ago|reply
That is entirely incorrect, except in the odd case of Sinclair ZX Spectrum Basic, which is the only programming language that I"m aware of that allowed you to do "LET a=10: GOTO a" (all other languages with goto require a label; GCC has an extension for "indirect goto" and "label address" that is somewhat similar, although it restricts you to staying within the same function scope).
If anything, it's a fancy "longjmp", that also calls destructors along the way.
The big difference, and it is HUGE, is that when you do a "goto", you know where you the next instruction is coming from. When you throw an exception, you do not know which instruction will execute next from looking at the throw site (and in fact, you may need to conceptually inspect an unbounded number of stack frames to tell; which is different from longjmp or expression goto which only require you to examine one memory location).
> Exceptions allow one to separate error handling from the structure of the "mainline" execution path of an algorithm
In theory. In practice, they essentially guarantee that the error handling is not properly tested, and often useless except in the case of "everyday expected errors", which are not really exceptional, but rather quite frequent.
[+] [-] aidenn0|13 years ago|reply
[+] [-] LoseThosMan|13 years ago|reply
[deleted]
[+] [-] mpolun|13 years ago|reply
[deleted]