Exceptions are a super GOTO. Rather than allowing you to jump anywhere in the position of the procedure, they allow you to teleport up the call stack to any number of handlers.
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.
Indeed. Rather, exceptions are a super "break", not a goto.
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.
You mean, how can that be replaced with ordinary operators? If you have multiple return's, move your 'finally' block to a separate function and call it before every 'return'.
In languages with implicit construction/destruction facility, like C++, you'd probably live happily without the finally block. There are however situations in C++ when you want to create a class with ctor/dtor only to make sure some block is executed when going out of scope, no matter how. That dtor would be your finally block.
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?
He argues that developers are generally too stupid and lazy to use exceptions correctly (which may be true), but if they instead are forced to use manual error code checking (which achieves the same, but requires typing lots of tedious and fragile boilerplate code) - they would do it diligently and correctly.
I didn't really think that that was his core argument. From my reading, he was saying this:
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.
I agree with you interpretation, I just don't agree with the logic that if developers are too lazy to handle exceptions, they will do the alternative (checking and handling return values) correctly, since it is just as easy to ignore an error code return value than it is to ignore an exceptions.
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.
For a start, unless you're Google, attracting talent is hard.
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.
The problem is people using try / catch instead of if (x != null) {} else {}. So when something goes wrong you end up with zombie code that keeps going long after something important did not happen.
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.
Well, sorta. Honestly, the problem here was that your platform sucked. If sem_init() "doesn't work" on OS X (is that really true?), there's no guarantee that the putative exception-enabled version would be properly throwing an exception either. Exceptions can be really useful, but they won't fix broken libraries for you.
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.
You're right, my post had less to do with exceptions and more to do with venting my frustration at the less-than-helpful C error handling system. It's not good when the API makes you think you're losing your mind.
When reading C code either all return codes are checked or not. Either way you know whether errors are dealt with. If you don't check a pointer before dereferencing it - the code is wrong. It's that simple. Correct code looks correct, crappy or unfinished code looks crappy or unfinished. This is a good thing.
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.
If you don't check a pointer before dereferencing it - the code is wrong.
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.
Exceptions in mainstream languages have a fatal flaw: they unconditionally unwind the stack when thrown. This sacrifices flexibility and makes it unnecessarily difficult to save state or take action at the point of the throw. Of all languages I've ever seen, only Common Lisp really does this right by separating out the concept of signaling a condition and of handling it.
I think his article should be called "unexpected errors suck". Sure, it would be nice if there were never any errors, but there are, and you have to handle them somehow. Compare:
try {
connect_to_database;
prepare_query("SELECT * FROM ...");
run_query;
return results;
}
catch database error {
warn "Whoa, your database is broken!";
}
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.
The problem with the above code is without exceptions you know where the program failed 23843 or 28783 but with exceptions all you know is something is wrong.
bzzt. depending on the language and the author of the exception-throwing code, the exception will contain information about where the error occurred and what might have caused it.
True, but in the above code such information is not propagated outside of the function. If you want to add that you need to throw another exception inside of the catch block or skip the catch.
At this point, I'm tired of typing the code. Checking for errors after every function call is way too much work
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.
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.
Exceptions suck, but I really think this is going the wrong way.
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).
This kind of argument also seems to miss something important: that exceptions (at least in C++, but this argument probably applies to other languages as well) aren't intended as a general-purpose error-handling mechanism, which return codes are perfectly adequate for.
Exceptions are intended for unexpected conditions (i.e., exceptional) that must be handled somehow. In C++, this is pretty fundamental to the RAII idea.
I agree with his premise that exceptions get misused by "average" developers. Where I work I see a lot of swallowed exception, which has lead to some evil bugs. I have also encountered some code that's literally impossible to write meaningful test code for because every exception is swallowed and there's no return code.
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.
I only write Java on very rare occasions (usually for clients that don't care if the code is throw-away quality), and so I usually end up with a "throws Exception" after every method. Horrible, but hey... it's not my fault Java sucks so hard.
No offense, but yeah: that's pretty horrible. Checked exceptions are one of Java's few true innovations. Used properly, they force you to handle errors in the spots where they occur. What you're doing is a 1:1 equivalent of ignoring the error codes returned from your C functions. That's just never the right way to do thing.
Exceptions are not meant to be handled where they occur, that's why they unroll the stack. For the most part, you can't recover from an exception -- you just abort whatever you're doing, print an error message, and let the user try something else. Checked exceptions cause developers to re-interpret the exceptions or just swallow them -- neither is a benefit.
Exactly. If the database server blew up, there's nothing my program can do. I could try reconnecting, and some libraries do, but that doesn't usually help anything. Once the server is back up, the Fibonacci (or exponential) back-off is so high that's it's faster to just restart the program manually. So usually, dying is easier for everyone.
A good thing about exceptions is that you can place your handler where you know it's best, that could be in the top level. Unless you use Java so you are forced to fill this burocratic method passport all along the way. That's no good and putting the word "innovation" there freaks me.
On a large CRM-type application I worked on in the past, database constraints were used to enforce stuff like unique login IDs. Yes, some validation could be done in the UI but that was the way it was done.
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.
You can use unchecked exceptions exactly the same way with as checked exceptions. The difference is that you can ignore them and let them propagate up when it makes sense.
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.
Propagating exceptions without handling them anywhere is bad, but IMHO still better than ignoring an error code.
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.
That's pretty ugly, although I've seen a lot of that style of coding at my workplace as well. The problem is that there generally isn't anything useful to do once you catch the exception besides throw up an error page. You're probably bother off either throwing RuntimeExceptions or wrapping exceptions as RuntimeExceptions.
There are other return codes besides ints. E.g., replace a function that returns an int or throws an IOException with one that returns an Either<int, IOException>.
The problem with exceptions is that most developers using them don't use them well, which does kind of mean that they should be avoided if you're not sure you need it. But I don't think that they have no place in running code. (Since I write mostly Javascript, try/catch is so expensive that it's usually not worth it, especially if you build your site so that it works when the JS doesn't, which is just good practice.)
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.
I have lived this kind of crap before. New job. The boss has some fringe idea that makes life more difficult for everybody. But until now I haven't seen this. Exceptions suck. Yeah, sure.
I personally thought the article was garbage. The author rehashed a lot of old ideas and lumped together a bunch of anecdotes without giving much new insight.
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.
Except for the point about exceptions being like GOTO, I fail to see how any of this differs from the complexities of procedurally handling return code errors. Exceptions give you a better way to distinguish error handling from control flow.
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?
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.