Why Exceptions Suck
ckwop.me.uk
ckwop.me.uk
Wrong. Exceptions, unlike GOTOs, can't jump straight into a loop or any construct that has a local state. In fact, exceptions never break local states, and that's their beauty.
The best proof that exceptions don't break anything is that you can always represent a throw/catch cycle with IF's, RETURN's, possibly also subroutines, but there will be no GOTOs. You will have equivalent functionality, except the code will be a bit bloated and less readable.
I do think there's some reasonable insight in this article. Lots of folks get very confused about the difference between an exception and an "error", or "return value", and produce code that mixes the concepts in strange and terrible ways.
Nonetheless, misuse of a tool isn't really an indictment of the tool. Certainly good exception handling can make some designs much, much cleaner than they would be with traditional handling.
ctor
try {
dostuff
} catch 1 {
blahblah
} catch 2 {
aoeuaoeuaoeu
} finally {
dtor
}
//why not put the dtor here and just leave off the finally block completely?I'm not sure I buy that.
1) Exceptions that arise from a bug should just assert 2) Exceptions that arise from an expected 'exceptional' are actually part of the true logic of the program, and shouldn't be shunted off into a catch/rescue block, because that increases the temptation to consider these as being some how subordinate to the 'happy case'. 3) The fact that exceptions are easy to propagate up the stack means that they can break encapsulation, and it becomes easy for lazy programmers to not treat special cases correctly.
Of course, I may be completely wrong about that, but I just thought I'd share what I had understood, as it's apparently different to your interpretation.
And if we agree that lazy developers might not handle every error, the "default case" with ignoring exception (the exception propagates to the top and kills the program) is much preferable than the default case without: the program state becomes corrupted, but the program keeps running.
Another point: Exceptions are not for cases which can be handled as part of the normal flow. Rather they are for exceptional cases which requires you to break out of the current context and handle the error on a higher level. So if you use exceptions correctly they don't break encapsulation, rather they help encapsulation since you can handle errors at the appropriate level.
Why do people keep parroting this?
It's much harder to use a return code badly
Return codes are almost always ignored. In straight C, errors are a real pain. They just are. In working code, the evils of exceptions don't come up, because you don't get exceptions for the most part. As a practical matter, they're just debugging tools, and not control handlers.
This is 100% true. I was once trying to do something with POSIX semaphores on OSX and while the code compiled without so much as a peep and ran, the semaphore didn't seem to be working. Only after meticulously examining my semaphore calls and printing out the error codes was I able to discover that sem_init doesn't work on OSX. (At least this was my experience). This would have saved me hours of debugging time if the compiler spat out a warning instead.
And it's also true that the POSIX synchronization primitives are a good example of how not to implement error handling in a C API. Too many things that are fundamentally usage errors (and should be caught at compile time) are flagged at runtime, leading to the silent failure issue you saw.
When you read Ruby code you have no idea if the programmer thought about exceptions at all. In order to figure out what exceptions should be possibly handled you have to take the union of all the exceptions that can be thrown by all the functions you call, and then check if they are all handled correctly. Unfortunately, it's impossible to determine which exceptions can be thrown by a function, because any function can throw any number of exceptions. If your buddy decides to throw a new exception in some helper function then the exception signature of a hundred functions changes.
So basically, there's no way of telling whether the code is correct. Good luck with that. So people give up and just rescue every possible error and return a default value, excactly like described in the article.
For your enjoyment:
http://www.google.com/codesearch?q=lang%3Aruby+rescue&hl...
And this is production code you're looking at.
Really? This might be true after a malloc or another system call, but in general, your modules should have specifications. If you have written a function which states that the incoming value must be a pointer to some value, that pointer shoud never be checked before dereferencing it. It's the caller's job to ensure that a valid pointer is sent.
Blindly checking for errors that will never come up is not much better than not checking at all.
I don't want to get into the details here, but Kent Pitman wrote a great paper on the subject: http://www.nhplace.com/kent/Papers/Condition-Handling-2001.h...
try {
connect_to_database;
prepare_query("SELECT * FROM ...");
run_query;
return results;
}
catch database error {
warn "Whoa, your database is broken!";
}
To a language without exceptions: dbh = connect_to_database;
if(!dbh){
errno = 0x23843;
return NULL;
}
statement = prepare_query;
if(!statement){
errno = 0x28783;
return NULL;
}
At this point, I'm tired of typing the code. Checking for errors after every function call is way too much work, especially if future instructions need to past ones to have been successful. Exceptions let me break right out of the related block, and do something to fix it. If I don't do something to fix it, someone up the call stack can fix it.I don't think exceptions suck. Errors are what suck.
catch database error {
warn "Whoa, your database is broken!";
}
Ignores all that and hides the error. I only point this out because I have seen a lot of real world code where people skip even the assert in the catch statement.Edit: You can add all of this stuff manually (or as part of the warn function) but I would like to see a language where you can define a standard place where all errors are logged independent of the given code unless it’s explicitly overridden. So you people can't add empty catch blocks.
Think of what the exception code ends up as in ASM. Basically try transparently wraps system calls with the same type of error handling code as his example. However, if you want to write stable code you need to account for each of these errors anyway. So when your DB connection fails you need to know if it’s a TCP/IP error or a Bad Password etc which means either wrapping each line with its own try catch or decoding the exception within a catch block. Granted, exception handling let’s your write code for the ideal case and avoid crashing when something messes up but as soon as you want to recover from an error without starting over you end up writing the same code anyway. And because the happy path works it’s not obvious just how far you are from high quality code.
Exceptions usually suck because people are trying to graft some kind of complicated event handling system on it, to which it's not suited. Frankly, this sort of thing is better handled by co-routines. Exceptions suck because they're the often the only way to do something without more explicit control flow.
On top of this, we're supposed to be able to check everything and write error checked code, but suppressing errors can be surprisingly beneficial: observe, say, DRYing out deep checks http://code.causes.com/blog/drying-out-deep-checks.
One of the reasons GOTO sucked so much was because at the time it was all we had. Now it isn't. Explicit control flow is useful and important for certain tasks (like network programming).
Exceptions are intended for unexpected conditions (i.e., exceptional) that must be handled somehow. In C++, this is pretty fundamental to the RAII idea.
However, I don't think that going back to checking return codes solves anything. Most of the problems I see at work are related more to checked exceptions than using exceptions in general. Thankfully, most of java frameworks I use have moved towards making everything an unchecked exception.
If you ignore an error code, the program will continue but probably fail later or just be wrong in subtle ways, generate corrupt output or whatever. An unhanded exception OTOH will just stop the program. At least then you realize you have a problem, and where it originated.
Empty catch-clauses however, are just as bad as ignoring an error code.
When a user tries to create a user with a login ID that already existed, the database constraint is encountered and an Oracle exception is thrown. It is caught and and packaged up through the application stack until it becomes an application-level exception. The end user sees a localized, friendly message instead of seeing a raw, nasty ORA error message. I'm not sure how this could be done without checked exceptions.
A real-world example of your use case:
The Spring framework repackages all SQLExceptions into a custom data access exception hierarchy. So, if you choose, you could catch a DatabaseConstraintException (or whatever the appropriate exception is, I'm not sure off the top of my head), and throw an application specific exception like InvalidLoginId.
I recently came across something like this, which struck me as a horrendous abuse of "exception" handling:
// drastically simplified so as to not make anyone nauseous/crazy:
try {
$x = doSomething();
doSomethingElse($x);
if (!$x->foo) {
$x->foo = "bar";
throw new NoFooException($x);
}
doSomeMoreThings($x);
} catch NoFooException {
handleNoFooCase($x);
}
Not 10 lines later, in the same function: do {
...bunch of code.
if (!$x) {
break;
}
bunch more code.
if (!$y) {
break;
}
bunch more code.
} while(false);
followed by new fewer than 3 other "clever" control constructs.Frankly, GOTOs would have been easier to follow.
The answer is yes.
The thing that really bothered me is that I got the distinct feeling that the author is only parroting old advice, without actually trying anything himself.
In particular, he said that exceptions are expensive in any language. In Python in particular, exceptions are no more expensive than any other control structure, as that was an explicit design decision.
Exceptions work, that's all there is to it, but they have to be documented. The Python documentation is a very good example of this. For the built-ins in particular, every single operation states what type of exception is thrown in what case.
Exceptions can be very effective if:
1. Each function/method documents what exceptions it throws under what circumstances.
2. Exception causes are thoughtfully separated into different types. One example of a violation of this principle that has frustrated me recently is Python's os.makedirs function, which recursively creates a hierarchy of directories. This can cause an exception if one of the directories cannot be created for some reason (permissions, desired directory is an existing file, etc.) or if the leaf directory already exists. In some cases, the latter case really is not an error (you just want "mkdir -p" functionality), but it's hard to distinguish between the two cases.
3. The proper exceptions are caught. Too often programmers attempt to catch every exception and re-propagate it (or worse, ignore it). In many cases, exceptions are only meaningful during debugging, and once minor issues are worked out, the exceptional case is guaranteed to never occur. These types should be allowed to propagate to the top-level and crash the program, that is the whole point of the exception. In other cases, the programmer needs to be aware of what exceptions may occur (see 1), to reason about how such cases should be handled (if at all, perhaps allowing it to pass up the stack makes sense), and to handle it appropriately. Only in a top-level logger for a long-running program should a catch-all exception ever be used.
...and that turned out much longer than I initially planned. I am just tired of hearing the same old "exceptions are bad because people will use them to mask real errors" argument repeated over-and-over, when no programmer worth anything would ever do that. Also, this popular comparison of exceptions to GOTOs is ridiculous. The article on GOTOs was written at a time when spaghetti code which jumped all over the place to save repetition was not at all uncommon. Thanks to more recent constructs like do...while, break, continue, for loops, function objects, and more, crazy "old school" code patterns are much less common. To march that argument out today any time someone dislikes a given control flow structure is a disservice to the original paper (which was actually addressing real problems in code structure), because the comparison invariably is between apples and oranges.
I'm not even sure I understand how GOTO is unmitigated evil. Occasionally it can be the clearest way to break out of a nested loop. Doesn't the Evil GOTO song and dance come from an ancient era when it played a completely different role in languages?
The languages may be ancient, but the era is not.
I'd be willing to bet more than half the production code running now has ancient GOTO capability and half of that is misusing it.
(Apologies to Churchill) Never was so much earned by so many maintenance programmers from the misuse of so few letters.
This is mostly a complaint about shitty developers than about exceptions.