The Scourge of Error Handling
CowboyRobot writes "Dr. Dobb's has an editorial on the problem of using return values and exceptions to handle errors. Quoting: 'But return values, even in the refined form found in Go, have a drawback that we've become so used to we tend to see past it: Code is cluttered with error-checking routines. Exceptions here provide greater readability: Within a single try block, I can see the various steps clearly, and skip over the various exception remedies in the catch statements. The error-handling clutter is in part moved to the end of the code thread. But even in exception-based languages there is still a lot of code that tests returned values to determine whether to carry on or go down some error-handling path. In this regard, I have long felt that language designers have been remarkably unimaginative. How can it be that after 60+ years of language development, errors are handled by only two comparatively verbose and crude options, return values or exceptions? I've long felt we needed a third option.'"
Ignoring the error completely, data integrity or planned functioning be damned.
I think MS already tried the blue screen.
BASIC has had it all along!
What a fool believes, he sees, no wise man has the power to reason away.
Exceptions should NOT be used for 'normal' errors. They should be used for events that are, well, exceptional. A healthy program should NEVER raise an exception, but may deal with a lot of error conditions.
While I have seen good error handing schemes in many languages, so far, I haven't seen anything as good as C++ exceptions combined with RAII. Exceptions alone aren't that great, but if you combine it with the way constructors / destructors work and compose in C++, it ends up working really well. A lot of languages with exceptions lack RAII. Java and C# have exceptions but don't have destructors (the language equivalent is much less useful than C++) much less ones that compose.
The only real problem is that lots of C++ code rely on return codes, no error handling at all, or poor use of exceptions and resource management. There are lots of C++ programmers who stumble on error handling code and haven't learned how to take advantage of the tools the language provides. Of course error handing logic can be quite hard, even if the language helps out a lot.
STM is also a great way of doing error handling. Transactions (like used in databases) make error conditions much easier. But they cannot be limited to databases; transactions in the file system (Microsoft has this with NTFS) and transactions in memory data structures (STM) are very valuable.
Visual Basic had:
On Error Resume Next
I last typed that when I was about 13...
The documentation shows a couple of valid uses for it.
Normally exceptions should be used in exceptional cases, not in normal control flow. Exceptions are usually quite expensive, especially in C++ compared to just returning an error code. Language APIs should be fast, but also convinient so they had to made a trade-off.
It is not clutter. It is necessary. Trash cans in the home might be considered clutter too I suppose. Some people artfully conceal them within cabinets and such, but in whatever form, they are both necessary and either take up space or get in the way or both.
It is the reality we live in. If you want to code in a language that doesn't require error handling, you might look to one of those languages we use to teach 5 year olds how to program in.
Good code does everything needed to manage and filter input, process data accurately and deliver the output faithfully and ensuring that it was delivered well. All of this requires error checking along the way. If you leave it to the language or the OS to handle errors, your running code looks unprofessional and is likely to abort and close for unknown causes.
I think the short of this is that if anyone sees error checking as clutter or some sort of needless burden, they need to not code and to do something else... or just grow up.
How can it be that after 60+ years of language development, errors are handled by only two comparatively verbose and crude options, return values or exceptions?
Of course, it could be that this just means that your own language horizons are too narrow. Prolog and icon come to mind.
Lacking <sarcasm> tags,
"Never test for an error condition you don't know how to handle." -- Steinbach's Guideline for Systems Programmers.
Non-Linux Penguins ?
...like the one described here:
http://www.reddit.com/r/ProgrammingLanguages/comments/m0f1o/how_about_a_programming_language_in_which/
In this way of programming, errors are just another set of events.
Exceptions here provide greater readability
Nah, they don't.
How can it be that after 60+ years of language development, errors are handled by only two comparatively verbose and crude options, return values or exceptions? I've long felt we needed a third option.
Maybe - and admittedly this is just a guess from my fairly ignorant viewpoint - it's a very hard problem. How can it be that after 100+ years of industrial development, we're still heavily reliant on internal combustion engines to get us around? Why have we only got people as far as the moon in 60 years of space travel? Why, after x years, have we only achieved y?
Because that's the way it is. Is there some reason we should have the third option by now?
systemd is Roko's Basilisk.
The author commends the use of multiple return values and a side-band error value that must be checked? Gee, multiple return values have been in Lisp forever, and maybe he's not aware of this little thing called "errno"?
Error handling is very, very tedious by nature. There are bajillions of ways that a system can go screwy, and many of these have individualized responses that we want distinguished for it to behave intelligently in response. We expect computers to become "smarter", and that means reacting intelligently to these problematic/unexpected situations. That is a lot of behavioral information to imbue into the system, all hooked into precise locations or ranges for which that response is applicable. That information is hard to compress.
The key to taming exceptions is to use them differently. Any exception that escapes a method means that the method has failed to meet its specification, and therefore you will need to clean up and abort at some level in the call chain. But you don't need to catch at every level (unless your language forces you to), nor should you need to do anything that relies on the "meaning" of the exception. Instead, you take a local action: close a file, roll back the database, prompt the user to save or abandon, etc, and either re-throw or not according to whether you have restored normality. There will only be a few places in your app where this type of cleanup is needed.
If you're not doing it this way, you're using exceptions as a control structure, and that's never going to be clean.
Paid Q&A/Research
We should also add to this discussion mention of Ken Pitman's and others Condition System from Lisp languages. It can be read about here:
http://www.nhplace.com/kent/Papers/Condition-Handling-2001.html
and
http://www.gigamonkeys.com/book/beyond-exception-handling-conditions-and-restarts.html
Condition handling and Conditions. Old school, but does the job without too much clutter!
Things either work, or they do not. The function either completes, or it throws an exception. You can catch this, or you can choose not to.
I want to delete my account but Slashdot doesn't allow it.
Yup. Errno has been around as long as UNIX system calls, so there are really 3 common mechanisms, not 2 as the article says.
Even if there was a shortcut for safely ignoring return values, I would (the company I work for would) still need to check and catch every return. Why? We have to log them all.
If you don't want to deal with failed returns, I find that a scripting language is the best way to go. I write my glue functions to handle nulls gracefully and I am done.
This is the real third option.
The so-called "error returns" from things like file opening are telling the program something very important about what's going on. The program's flow must be designed from the beginning to interpret and handle errors. This is in fact be much of what a good program does.
It doesn't matter whether we use exceptions or error codes to signal the errors as long as the program is designed to accurately interpret the errors that do occur. In some sense, exceptions may be easier to implement in today's event-driven interactive interfaces. Regardless, though, the design must not allow errors to be lost.
Was it Cooper in "About Face" who said that an error alert pop-up was essentially an admission of failure on the part of the programmer?
Rick.
But even in exception-based languages there is still a lot of code that tests returned values to determine whether to carry on or go down some error-handling path.
The whole idea of exceptions is that you don't need to worry about checking return codes. If you're putting a lot of work into checking return codes for error conditions, then you're working with some ugly code that probably needs refactoring. One place I see this a lot is in Java wrappers around libraries ported from C (or used directly with JNI). Often, to make the documentation and example code line up perfectly, the wrapper returns invalid values for exceptional circumstances rather than just throwing exceptions. This is wrong.
The only code that should be run routinely for error-checking is to see if an exception should be raised. That's fast, only needed once per major function (so you're not re-checking at every minor transformation), and doesn't incur the cost of exceptions unless they're actually raised (which should be fairly rare, as they're exceptional).
You do not have a moral or legal right to do absolutely anything you want.
errno is just a return code in other clothing.
Python: 'And then suddenly you have a language which says "we're all stuck with whatever the whiniest coder wants".'
Monads are fun for error handling. :)
I donno if they present exactly what the author might consider a third option though, well certainly they can present other options, like with the Either monad, but that's no simpler really.
The Christian religion has been and still is the principal enemy of moral progress in the world. -- Bertrand Russell
Just don't bother checking for errors.
1984 was not supposed to be an instruction manual.
A lot of coders just cover entire routines of code in t/c blocks because they don't really want to handle errors at all.
Ummm, I think that depends. If you enclose a code block with ~some~ exception handling, you obviously know there is a possibility for a problem. Its what you do with that exception that separates the coders from the slackers. Also, when coding an API, there's little more appropriate than using a "throws" clause, it should be up to the caller to deal with raised exceptions as it sees fit.
That said, exceptions are so expensive I tend to favor return codes in speed-sensitive code and turn off exception handling if the compiler allows it.
Python: 'And then suddenly you have a language which says "we're all stuck with whatever the whiniest coder wants".'
The biggest problem with exceptions is that they get thrown too far, changing them into comefroms (the opposite of a goto). And like gotos, they encourage spaghetti code. The best way to deal with them is to limit them to thrown exceptions only to their callers. That way, all exceptions become part of the subroutine's interface. Remember, for a programmer, out of sight is out of mind. If it's not part of the interface, it will be forgotten. For those who are interested, you can read my blog for details and an example.
Don't stop where the ink does.
If possible, design a component such that no errors will ever occur (except for hardware failure). An entire program can't be designed that way, obviously, but individual components can be. Collecting all resource allocation into a small, well defined set of locations will relieve the majority of other code from the need to handle errors. In such a design, the majority of functions will return "void".
Normally exceptions should be used in exceptional cases, not in normal control flow.
People keep saying that, but I've yet to find someone who can defend the position with a logical argument.
Fundamentally, you run some code to do a job. There are two ways it can finish early: either it succeeded, and we did all the work/figured out whatever information we were asked for, or it failed, and maybe we want to report this along with some related information. Either way, there is nothing useful left to do except hand control back to the higher level code that asked for the work to be done, along with the outcome of that work, as efficiently as possible without leaving anything in a mess as a side effect.
Exceptions, as provided in many mainstream programming languages today, could serve either purpose just fine. The semantics work the same way in each case. The performance implications are the same in each case. Aside from the unfortunate name "exception", which we could replace with something like "outcome" or "result" just as easily, and the commentary of certain commentators, whose arguments are rarely more than an appeal to their own authority, there is no difference between the two cases semantically or in terms of the code I want my computer to run.
So, why should exceptions be used only in exceptional cases, apart from dogma or convention? They're just a tool, like variables or functions.
Exceptions are usually quite expensive, especially in C++ compared to just returning an error code.
I'd like to see your profiling results to back up that claim. I've got a few years of working on high performance code that suggests most compilers from the past decade or more use some variant of table-based dispatch to handle exceptions. That means they will not need to manually unwind the stack step-by-step in the case where the exception is thrown/raised; they can just run any necessary clean-up handlers and otherwise skip over everything between throwing the exception and catching it. It also means there will be less error checking code required all the way up the call stack in every other case. In other words, this model runs faster and it does does so whether or not an exception is thrown. The overhead is in the space for storing the jump tables and the compiler's effort to generate them (both of which can be unpleasantly large) but not in the run-time speed.
If you disagree, post your argument. (-1, Overrated) isn't your personal censorship tool for views you don't like.
The nice thing about functions (rather than just simple subroutines) is that you can chain them in a single line. E.g.
a = geommean(factorial(b), zeta(c))
The single return value mechanic really makes it easy to use for math-style expressions. But the single return value mechanic isn't adequate when the function is allowed to have errors. In a language like c, one might do something like
f = factorial(b, &error);
z = zeta(c, &error2);
if (error==0 and error2==0) { a = geommean(f, z, &error3); } else {error3 = 1;}
Unfortunately, there's no longer any simple way to chain the functions, which kinda defeats the point of functions. So exceptions were invented.
Perhaps a better language would have some better handling of multiple-output functions. Maybe some way of chaining functions such that the error behavior can be defined. I'm not sure exactly how to do that.
There are two ways to do error-handling: try{}catch{}, or if{}else{}. That's "using exceptions" and "using return values", under Dobb's naming.
The difference in usage is simple: one handles errors immediately, thus cluttering the code with all the things that could go wrong, while the other separates error-handling out, pushing it to the end of a block (and away from the code that actually generates the error, which can complicate debugging).
I can really think of no other way to do it. You can handle the error where it happens, or handle the error at the end. I tend to look on anyone whining about how hard error-handling is with suspicion - their suggestions (if they even have any) are almost always "the language/compiler/interpreter/processor/operating system should handle errors for me", and there are enough obvious flaws in that logic that I need not point them out.
Check out Haskell's Error Monad transformer. Used properly it is a beautiful thing.
In other words, you do not know how to use them effectively therefor any use of them must be bad?
T/C blocks are a tool, just like everything else in programming. They can be abused, and they can be perfect for the job. Anyone who tries to claim that some tool is universally bad and has distain for any piece of code using it regardless of its appropriateness does not strike me as a very good programmer, or at minimal a very limited programmer.
All code is just assembly language in other clothing.
You don't write an article like this unless you're actually going to suggest a different solution in it. Otherwise it just comes off as whiny and inexperienced. "Oh, if only we could not do that thing that everyone must do if they want robust code!" Reminds me of beginner CS students who don't want to make an extra header file or prototype functions. We're not doing magic here, and no amount of wishing for magic will make it happen. Work with some magic module (ActiveRecord, maybe) for a while and you'll quickly learn to hate magic, anyway. Discipline is required to write code that will stand the test of time. If I were wishing for something, it'd be that more programmers had the discipline to write good code consistently.
I'm trying to teach myself to set people on fire with my mind... Is it hot in here?
And I must say that as the Editor in Chief he has a very simplistic view of the problem. If I understand, his view is that a global exception added at the compiler level would somehow solve all the problems. He gives the example of calling "open" without worrying about it failing. Of course he doesn't state how to handle the failure when it occurs. For example
open(file1); // ok // failure
open(file2);
What happens to file1 in this case? How is the code cleaned up? There may be a case where you don't want to just close all files in the functions, but just create file2 if the open failed. (for example).
His complaint is that there is too many options available for error handling, and that they lead to cluttered code. As far as I can see the alternative is not enough options available and code not always doing what you want, and having to fight the compiler in order to get what you want.
This is generally seen with asynchronous code, but it could apply anywhere.
Consider: (javascript) XmlHttpRequest has a readystatechange callback. Most javascript libs wrap it up so you pass in two callbacks, one for success and one for failure/error.
e.g.
jQuery.ajax(url, { ... }, ... }
success: function(data, textStatus, jqXHR){
error: function(jqXHR, textStatus, errorThrown)) {
);
No return, no exception, the programmer decides how to handle it.
Do you even lift?
These aren't the 'roids you're looking for.
We need the computer fairies to handle our errors, that way the beauty of our code will not be marred by mundane things like error checking.
Seriously, error checking is part of the process. It's not the fun part of the process but it's a necessary part. Return values and exceptions work just fine as long as you get off your high horse and realize that your code will not be hung in the Louvre. Working is more important than pretty.
Problems don't exist in reality, they exist in points of view.
Don't complain about syntax, grammar, or spelling. There is no.hell like input on android.
Programming languages should have the following built-in:
1. Logging (like Log4Net with choices of database, file logs, e-mail, Windows Error Log, etc.). All exceptions should be logged to the chosen log path automatically.
2. Web pages should go to an Error page, with logging and a link back to the home page. Also, they should record the yellow screen of death info like elmah automatically in the error logs, not show them to the user.
3. Popup dialogs on applications should be more user friendly, with the programmer info going into the logs. Microsoft should work on their error messages so that they make more sense to the user. "Directory not found" should instead say, "Application Name could not find the path c:\folder\subfolder. Please ensure that this folder exists or contact the software developer." Good default error messages would save millions of hours worldwide.
With these simple changes, the quality of software would improve substantially and the work of developers to catch errors would go down dramatically, if they were comfortable with the default handling.
Peter predicted that you would "deliberately forget" creation 2000 years ago...
That Mr Steinbach must be a fucking idiot. If I encounter an error I can't handle I have to log that and stop running. RIGHT NOW.
Quality code will always be "cluttered" with data validation code, result verification, and a host of other details.
The simple fact is that computers are stupid. They have to be explicitly told what to do in every conceivable situation the code could encounter at runtime, or else the code will crash and the user will complain about it being "unusable".
I notice that despite the article author's bitching about the situation, they had not one suggestion as to what to do instead. It's easy to bitch about life, but a lot harder to suck it up and deal.
If exception handling and return-value checking code are "too hard" for someone to understand, they need to get the hell out of the programming industry and leave it to professionals who actually find it fun and challenging to deal with all the details. Not everyone has the mindset of a true programmer.
I do not fail; I succeed at finding out what does not work.
Yoda agrees: "Do or do not. There is no try."
I suggest the author has a talk with Alan C. Kay.
Once there was an research done in PARC about so-called "Aspect-Oriented Programming". The idea was to describe different aspects of the problem (in our case the business logic and the error handling) in different domain-specific languages, then use "compiler" to combine the two into actual executable code. As far as I know, the idea never got further beyond research stage. In any case, it's the only alternative to error codes and exceptions I've ever seen.
All coding should proceed as if every possible exceptional condition (device not ready, cache fail, controller failure, cat dials 911 on speakerphone) is the primary and intended purpose of the Project. Hash collisions not merely covered as a contingency but pursued with vigor in the main line to the Nth degree, where N indicates the infinitesimal possibility of multiple simultaneous hash collisions that would be the likely result of a vengeful god constructing the universe such as to produce a life of continuous and foul exceptions.
When gathered at the water cooler, coders would discuss triumphs in their particular areas of malfunction, and when they corroborate as a group it is to merge their respective threaded exceptions into a parallel paroxysm of failure, branching with virtual threads and physical coring such that the greatest possible number of malevolent conditions are met and coded for, simultaneously. Proceeding steadily towards the grail of the Grandest Failure.
The Grandest Failure being the stuff of mere legend, yet it is what drives us. It represents that supreme and sublime moment where everything that can go wrong has gone wrong and the very fundament reeks of wrongness.
Buffers are not starved as an exception, they are starved by design! Disk controllers are never ready. Communications packets never arrive in sequence, or so we assume because there are no markers to check, when they do arrive they are garbled beyond repair. Reconstruction occurs as a matter of course! Streams are unsynchronized by nature, incompatible by rote, unresolvable.
Off the corridor in a dusty hallway a small team of pariahs is assembled to perform the dirtiest and most detestable task of all: to handle the exceptions and branches thrown by the main line, conditional branches sketched out briefly (whose existence is known but not mentioned in polite conversation) are pursued in secret. This is necessary work but unrewarding as it leads away from the noble purpose of Grandest Failure, towards useful work. Such stuff as consolidation, transaction handling and data ordering, forgive me for uttering, Chaos be Praised!
For the goal is to produce a System that boldly and efficiently proceeds down the pathways of most numerous and most simultaneous failure, where the actual success of anything triggers the exceptions and is cast off to the side.
If robustness of design becomes human sentiment, it could be said that the System confidently strides forward boldly embracing every error condition and is shocked -- horrified -- every time something goes 'right'. As life's own experience is our guide, it is seldom disappointed.
The output of useful work in such a System the source of great embarrassment and discomfort, a necessary evil.
That is the principle behind the control systems of the Improbability Drive. It is the driving principle of the quantum flux, Brownian motion and wave/particle paradox.
All of this Order and Progress (blaspheme!) is but a side road off a side road ad infinitum. The main path leads to Chaos. Follow that path and revel in it. There is no honor in coding for success, any idiot could do that.
Down deep people know this is the Way. That is why when coders meet in dim conference rooms and the slideshow laptop suddenly projects a Blue Screen of Death for all to see, there is an eruption of thunderous applause, as if one had dropped a tray of food in a crowded cafeteria. Deep down we know failure is the noble path, and success the exception.
<blink>down the rabbit hole</blink>
That's the basic problem I see. For the example given of an open() call, what happens when the file doesn't exist? It could be an error, resulting in the program throwing up a message and aborting the operation. Or it could be a perfectly normal and expected occurrence, resulting in the program continuing as if it'd opened the file, immediately hit an EOF and closed the file. So with the proposed mechanism, every time I see an operation I need to jump to the bottom of the file and find it's error block to see if it's actually an error or not. That breaks a basic rule for coding: don't make the reader jump all over the place to see all of what's happening.
Worse, the same file-not-found condition could be an error at one spot in a routine and a normal result at another. That means you not only need the error-block construct, you need an additional construct at the point of call to associate different error blocks with the different open() calls. And you haven't eliminated the jumping-all-over-the-place problem.
Exactly. That was my first thought on reading this: "If there's a better way, show us. Come up with a solution. What's stopping you?"
In reality it's not that obvious, or someone would have thought of it already. I would look at engineering practices and see how they handle failure modes. Sometimes it is better to let the thing break as long as you design it to do the least amount of harm when it does.
It's possible to develop defect-free software as long as all factors are under your control. E.g. a program that runs on unreliable hardware can never be made reliable.
"Slow down, Cowboy! It has been 3 years, 7 months and 26 days since you last successfully posted a comment."
In other ways it is more like an exception in that you can ignore it and thereby pass it to functions higher on the stack as long as you do not set a new error yourself.
Well in Java, there is RuntimeExceptions (meant to propagate up to the user, who can give it to support) and Exceptions.
You can handle Exceptions you can't handle by rethrowing them as a RuntimeException. RuntimeExceptions also do not need to be declared in the function signature.
Not that I do that often, I'm busy with PHP, where the exception handling is usually done like "Hey, you broke it!".
Hey don't blame me, IANAB
Btw there are even more options. For something truely exception worthy you can also throw a POSIX signal, and let the signal handler either handle it and resume the process/thread, or give up and terminate the process/thread.
I mean given how often I see code from developers not use any of this anyway ("sarcasm on" I don't have time, I just have to write perfect code "sarcasm off") I wouldn't be too encouraged to implement something new an innovative. (Since even if it was awesome they'd probably not use it.) Gee can you tell I'm a developer that too familiar with people cutting every corner they can, even if it really doesn't save them any time anyway?
Did you know 80 to 90% of the moderators on slashdot wouldn't recognize a troll even if one dragged them under a bridge.
It doesn't matter whether we have one, two, or ten options: very few developers are conscientious enough to give a shit. Whether they're there just for a temporary job and don't care about quality, or they're trying to get back at a prick of a boss, or they just don't know or realize that they should handle unexpected results (aka. noobs!), until the language or CPU checks for us, our software is likely fucked.
I'm still haunted by the memory of too much medical software written by too many people where they check maybe one condition and continue on - it's all good. In one case, I noticed that a function could return a failure, and we never checked for it. I added a check for it and was told to take it out because we didn't know how such a check would propagate through the decision chain and affect system operation. God help you, Siemens $productName blood analyzer users!
Every time I write a new function, I think in terms of paranoia: "How can this fuck up, and what values should it return?" My code often focuses extensively on error conditions. I justify it this way: when you get a very specific error, you can bet it came from one of few places so it'll be easy to find and trace back in the code. If you get a generic error, good luck figuring that one out without lots of run data.
Yes, it's sometimes cumbersome, and it adds a few extra lines to the file. So what? Use your editor to hide the error-handling block, and if you're concerned about file sizes, buy a bigger disc. Software engineering requires that you, you know, engineer the software for robustness. It should be freakin' bomb-proof . Take a little pride in your work. If you find that unappealing, please, get out of the business.
Sometimes, a low level function cannot know what to do about an error condition without a huge violation of scope. The right thing to do is throw an exception and let it be caught within the scope that has some way to deal with it.
The try block should be the size of the dependent code. If A raises an exception, everything that needed A to actually workk in order to do anything meaningful should be in the try block. In many cases, that will be the entire rest of the function.
There is no reason at all that try/catch MUST create slower or less efficient code.
When Chuck Norris throws an exception, it is always fatal.
How is the Riemann zeta function like Trump rallies? Both have an endless number of trivial zeros.
I'm all for another option if it results in less cryptic error messages thrown to the user level. I don't know how many times I've had to help someone and told them don't read that error literally. what it REALLY means is this....because a single function spit out an error and was just passed up the chain until the user sees it ....
How about a language that takes into account that errors are more common than successes and throws successes instead of errors?
AB HOC POSSUM VIDERE DOMUM TUUM
with and using are excellent error handling mechanisms that have no clutter and clearly define the protected code. Most exception handling is little more than logging what went wrong since many exceptions really don't have solutions that can be coded for. The main thing we are trying to do in these situations is write out some data so we can debug and clean up any messes (open connections for example) we have laying around so the things don't get worse.
Monads monads monads monads monads from Haskell.
Or "workflows" in F#. Related to, I think, "generators" in Scala?
Roughly (and I'm going to make up some C++/Java style syntax here), you write something like this:
workflow someExpressionMaybeAnObject
{
int x = someassignment;
some statement;
someotherstatement;
}
At the end of each line, you check the return value for errors, and use the handlers defined by the object up top, which could short-circuit the rest of evaluation.
These are actually a lot more general than error handling. For example, they generalize Python-style list comprehensions when used in a certain way.
In Haskell-land, there's a lot of interesting math about how they work, but you don't need it for error handling.
Moral: Learn a wider variety of languages!
Errors happen, and have to be handled. With code.
If you have an alternative method in mind, please share the secret.
Sheesh, evil *and* a jerk. -- Jade
I also see this more in Java due to the checked exceptions -- who hasn't seen something like // Make the compiler shut up
try {...} catch (Exception e) {}
So, you can write PHP in Java? Awesome!
W. RIchard Stevens had a pretty good approach to error-handling in "Advanced Programming in the UNIX Environment". In most cases, he wrapped system calls or library calls in wrappers that would test for errors and abort with an error message on failure. These wrappers meant that the main program could forget about error-checking.
In the relatively rare cases in which the main program felt it could recover from an error, it would call the system call directly and handle the error itself. This led to pretty clean but still very safe code.
Naturally, this approach is inappropriate for authors of library code, but it's great for the main body of an application.
Just embrace the segfault.
In other words, you do not know how to use them effectively therefor any use of them must be bad?
Any objection to the current fashion/buzzword/hegemony (e.g. OOP, exceptions, multiple inheritance, mortgage-driven agile development, subversion) in programming quickly gets battered down with "You're just angry that you don't know how to use it, and you don't understand it." Don't you find that argument tiresome? I certainly do.
Furthermore, the belief that all tools have a good purpose, or that they are "perfect" for some set of tasks no matter what, is fallacious. You probably wouldn't jump to the defense of an intentionally esoteric programming language in such a manner.
Sounds like the author would like to have all their error-handling code nicely compartmentalized at the end of the function where it doesn't clutter the normal codepath, and I can't say I disagree, at least in many scenarios. So some techniques I'd like critiqued:
State-based exceptions - a way to do rough exception handling when any errors are unrecoverable and you're just trying to avoid making things worse. I've never done this, but now that I think of it there may be some potential there. As basic "local clean up" example
.... .... .... .... } // since we didn't actually fix the problem
UpdateSomething(...){
MyEnum state;
try{
state = OPENING
state = PROCESSING
state = SAVING
return;
}catch(...){
switch(state)
{
throw;
}
}
Another would be something like named catch blocks as "sub-functions", which, now that I RTFA, I think is similar to what the author was suggesting at the end. As I envision it being used as a c++ extension:
.... } .... } /* it's all error-handling below this point*/ .... } ...)// all other exceptions .... ; throw; } ...) .... }
Foo() {
try using catch_1 (watch_1)
{
try using catch_2 (watch_a, watch_b)
{
return;
catch_1 (watch_1, TypeA exception)
{
catch_1 (watch_1,
{
catch_2 ( watch_a, watch_b,
{
}
this would be functionally equivalent to the current situation, except that the code for catch statements would all be grouped after the end of the function rather than cluttering up the "all's well" codepath, and the variables accessible within the catch block would be declared up front to avoid scope obfuscation.
In fact this syntax could be extended to allow normal functions to be used as catch handlers as well, allowing common cleanup scenarios to be shared between multiple functions without requiring redundant wrapper code like catch(a,b,exc){cleanup_function_1(a,b,exc);}
--- Most topics have many sides worth arguing, allow me to take one opposite you.
The article boils down to "I can't keep track of the difference between normal execution code and error handling code therefore my solution is to put error handling code somewhere else so I don't have to look at it". The proposed "solution" just puts another layer of obfuscation into the code and can be done today with procedures. The proposal is not a step forward.
Were those links supposed to support your argument?
I'm sorry but they didn't explain anything at all.
Readability, no. Maintainability, sort of.
If an un-exceptional program is producing peculiar results, it can require considerable effort, often hours, to locate the cause of the oddity, and considerably longer to integrate the proper extensions to deal with it correctly.
An exceptional program, however, will produce an un-caught exception. Almost mechanically, I can insert catches at various points in the program, then hone in on where the proper catch should be applied. With virtually no understanding of the underlying program, I can, uh, repair it.
David Parnas pointed out how, in software, the efforts of a few can create need for the many. Nowhere is this more clearly evident than the rabid promotion of a fetish.
Exceptions, value propagation, panicing and ignoring all have merits and drawbacks; their applicability depends primarily upon the situation and scope.
In case of errno, though, there are no further levels of abstraction added here. It's literally the same exact thing, just moved to a thread-local variable instead of a return value. But it still has all the disadvantages - you have to remember to check for it (which, if anything, is even easier with errno), and it uses a single nondescript integer to describe any error.
Surely there are different sorts of errors, which would suggest different approaches for dealing with them?
I guess it's pretty hard/futile to deal with most of these issues at a language level, because the appropriate course of action and channels of communication depend on the system. It strikes me that most of this stuff is something a domain-specific framework or API should be handling.
C# has IDisposable/using. It's not equivalent - a far cry from it - but it covers some common cases. And Java is getting Closable/try-block in Java 8, which is more or less the same thing.
Exceptions should NOT be used for 'normal' errors
Exceptions should be used when you have an error that only your client code can resolve; I like to use writing to a full disk as an example. An I/O routine does not know what should be done if you try to write a full disk -- maybe your program cannot continue, maybe you can ask the user to delete some files. In most of today's programming languages, your error handling choices are:
There is, however, a third option that you have in Common Lisp but which could be implemented in any language: restarts. The thrower of an exception can and should be able to set "restart points," to which the catcher of the exception can transfer control flow after the error has been corrected. Thus, if you tried to write to a disk which is full, the catcher could ask the user to delete some files, then invoke the "try again" restart; the I/O routine knows how to retry the operation.
This requires an exception propagation mechanism that does not unwind the stack until some time after the exception is caught -- e.g. at the end of the exception handler. The exception handler's stack frame will need a pointer to the appropriate stack frame and return address for the case where no restarts are invoked (not hard), but otherwise the function call stack would be no different than it is now.
Palm trees and 8
When using a function, the current situation is that you get an out-of-band (the exception) or normal (typed return value) answer. This is a lie when you functionally look at it from the outside: you now have two types; the exception or the predefined typed answer. In case of an exception there is some glue which will give you a GOTO (to the catch block). The exception type is sometimes abused to return one of many types of answers from a function.
The only option is to lose the typiness (is that a word?). Some alternatives: Provide the function with handlers for certain categories of answers. Alternative 2: Use the type of answer to switch to a block of code given a certain type of answer (as exceptions are dealt with). Drawback is you can only give one return value (you might want to return multiple differently typed answers). Alternative 3: Return a type-prioritized set of answers; can be done already but is not 'best practice' at the moment. If a certain type of answer is not dealt with: return the function until it is done (a la exceptions).
A use case I have is that we send SMS batches but some messages may fail directly. Many types of errors may come back and all should be handled differently. We now make some plumbing with 'reply-object-classes' and they suck. They have to be created, filled and read...
Maybe combine a few possibilities. And yes: we are still in the pre-industrial area regarding software development. Everything takes expensive labour.
nosig today
A good example is the way Horn clauses present alternatives in Prolog -- although what you really want is not Prolog but something more like XSB with tabling and incremental table maintenance.
Seastead this.
The delayed execution is the 3rd option. Or an event model. Think of something like a make file where you don't do anything until all the pre-requisites have been satisfied. Error checking is necessary because ultimately you want to be able to have programs which can adjust to real world events (mouse motions, changing screen sizes, stuff arriving on the network wire). And in the real world thinks may not happen in the order you hope they would or in one of the orders you thought they could. Probably the most common way to handle this is publish subscribe or event model.
Any guest worker system is indistinguishable from indentured servitude.
Okay, now take all those things you said a 'good coder' does, and then add that person using Try/Catch on top of it.
I doubt you would know a 'true programmer' if he/she slapped you in the face.
Persistent Volume manager for Kubernetes - https://github.com/dwimsey/openshift-pvmanager
Would this help? First, create unchecked exceptions to match each checked exception. Then, allow applications to somehow provide a list of checked exceptions that should be unchecked (which would carry the checked exception as a chained exception. Perhaps only checked exceptions returned by 'external' routines would be affected...
Don't they teach about encapsulation to you youngsters these days?
The point is that library function A() failed to encapsulate some of the exceptions that B() can throw.
One)
For the return value option: It is usually bad coding practice to mix error codes with information the function returns. If a function returns an int, don't stuff an errno in there when things go wrong. Separate the data from the error code. I hate it when functions that are supposed to return a pointer use NULL as the error code. NULL doesn't tell you anything, and it very well may be a valid value to return anyway. Set errno, send a signal, return a separate error code, but don't put your noise (error code) in the signal.
Two)
I want to see the errors. Don't hide them from me. Force me to deal with them, at least as far as generating a meaningful error message. (Meaningful!. that's a different rant.) I don't want the hardware, OS, compiler, library or whatever to take away from me the ability to see -- and handle -- the error myself. Feel free to offer me some boilerplate error code, but don't force me to use it. Half of elegant programming is in the error handling. If you think about it, a program seems "smart" when it handles error conditions well, instead of just crashing or quitting.
And the plus:
It seems to me what the guy is complaining about is the aethestics of the code, not the actual error handling. Wouldn't a folding editor help with that? You just mark the error handling code and let the editor collapse it for you, until the time comes when you need to see it (and it will). Problem solved.
This is why we have testing.
Reading and understanding the manual and getting something right at the design stage takes less time than reimplementing it and retesting it after receiving feedback that the the initial private test has failed. And when private testing takes less time, you get to release the product to the public sooner and collect revenue sooner.
The only people who do it any other way are writing mission critical code that costs a fortune to develop. You know what? You're fired.
It appears you don't think your company's key product is critical to the mission of your company, sir. In that case, you can't fire me because I quit.
POSIX signals themselves are a bit of a horror. Like C++ exceptions (as Google correctly points out) they have implications for `other' code, the worst case being code that has not be written to cope with interrupted system calls. Also, signal dispatch has portability problems; signals did not anticipate threads and POSIX was slow and iterative in its promulgation the standard solution, so many subtleties have appeared among implementations.
However, I think you have the right instinct. I personally find myself working in explicitly event driven environments frequently. Node and TCL for example. Here you can not indulge the illusion of absolute control over the fate of the instruction pointer. Any time you `yield' to the runtime you wind up entering your code at some other point as the runtime dispatches events.
Using the event model to cope with errors and exceptions would mean that anything that would traditionally throw an exception or return a error code would instead be a yield point and may generate an error event. You would then provide a handler to receive these events with enough context to cope with the problem.
I've come to the believe the event driven model is a far better model for the actual conditions one assumes when implementing logic. The moment you write main(){...} you are subject to signals that are handled by a collection of default handlers. One day the system becomes non-trivial and you must 'fix' these handlers. Perhaps you have no business writing main(){...} and adopting a naive, linear model in the first place. Instead, you're supposed to implement (the moral equivalent of) a signal handler instead.
Down at the bottom, where CPUs process machine code, hardware interrupts are endemic. The hardware itself imposes the event model. It may be the case that most machine/assembly code still written by humans today are simply event handlers; logic servicing hardware interrupts.
Lurking at the bottom of the gravity well, getting old
Sorry,
You only want to work in success state? The mark a of a truly good or great programmer is that the understand that faults happen and that they have to be dealt with. A great programmer makes this look simple and elegant.
An absolutely horrible programmer wants to hide this handling and do as little as possible with it. These programmers are also the ones that tend to somehow find a way to incorporate some new cool library or frame work into the code for no reason what so ever.
Unfortunately Agile is development models are inadvertently promoting this bad practice as people start to fee a need to bang out as many user stories as possible. Lots of shops create stories biased towards the success especially when stories are mostly penned by the business.
I call this problem. The success path code fallacy. The amount of code debt that results is huge.
Learn how to deal with errors early and efficiently so that you don't have to deal with them in unrelated bits of code where the amount of effort to deal with them is much much more. Then the amount of work you have to do with error checking will drop.
The problem with return codes, IMO, is that the operating system/runtime natively use exceptions, so when you *attempt* to convert code to using error codes, you have a false sense of security that your app won't crash due to thinking you're not using exceptions. I've run into this a couple of times: the architect thinks his/her app is rock-solid (in terms of crashing) but the fact is, you cannot escape exceptions. You may not throw exceptions, but your runtime does, and as a result, your code doesn't use try-catch anywhere. Then you get an exception you don't expect (i.e. a system exception, such as NullReference), and guess what? Your app crashes. Or even worse, you wind up putting a catch (Exception) {} into your code thinking you'll just convert your error into an error code, but you end up losing the true nature of the error as you convert your context-specific exception into a general "ActionFailed" error code. This kind of error winds up being a pain in the ass to diagnose, as you have to backtrack in your code to find where your error information got lost during the conversion.
And everything to do with the fact that error paths are rarely actually TESTED by the programmer, so if the program is complex enough the likelihood that an error condition will be handled properly rapidly drops to near zero.
The problem is thus really that of not being able to easily test that the error paths actually do the right thing.
-Matt
Considering how mature exception handling is at this point, I would hardly call it the 'current fashion'.
Exceptions have their uses. If someone is writing them off completely, then that speaks to them either not considering those cases, being willfully ignorant, or not being competent in the tool.
While normally I agree with your argument here, I think you are way over applying it as a way to shut down defense of a tool you do not like.
Besides, the 'you don't understand it' argument is tiresome when people use it to silence people who fail to acknowledge as the one true solution, so essentially the opposite usage since in this case it is being used to attack a 'one false way' argument, with no accompanying 'one true way' one.
Exceptions have their uses, but those use cases completely overlap with simply checking for Bad Things there and then & taking appropriate action, even if that appropriate action is using a goto that jumps to the 'relinquish & return nil' part of a routine (which is essentially what exceptions are when coupled with destructors -- exceptions are fancy implicit goto statements/longjmps).
I agree it's a bit far to claim that exceptions should never have existed, as the OP did. I quite appreciate Haskell's 'Maybe' monad, and the exceptional circumstances you can explicitly declare as bodies of a function; that is a sort of exception handling that I like because it combines both a clear indication that exceptional circumstances may arise at that place, and the immediacy of checking for bad circumstances and bailing out there and then.
This is a fallacy. The code shows how the problem is solved. The documentation describes the intent of the program - this is not the same thing. If the code is wrong the documentation describing the intent allows you to pick up the inconsistency, and what should actually be happening.
Yes, the documentation can also be wrong, and needs to be maintained as much as code and program data. It is part of our jobs to keep all the resources accurate and useful, including the documented *intent*. Anyone who says different is a lousy developer (most probably because they mistake the purpose of documentation, so under value it - or have never been given great documentation that saved them oodles of time).
It's usually an indication of that, but not always. Experienced programmers can realize when they're actually dealing with a distinct usage scenario that just happens to be, at the moment, solved with the same block of code. Recognizing those situations and explicitly choosing not to reuse code will make your refactoring job considerably easier in the future and is most certainly an indication of a non-amateur.
"Don't blame me, I voted for Kodos!"
I would qualify myself as an amateur-- my "programming" consists of powershell and batch most of the time, except a few times a year when I break off to use AutoIt for some more complicated stuff. Remembering my CS classes, I have always tried to throw together the core program first, and as it starts to form I start breaking things off into functions. I think I often go too far, trying to functionize things that simply arent good candidates; and others I leave things as part of the main program flow that should really be functions.
Ive also noticed that doing either of those two things will, as you say, negatively affect any attempt to rework your code in any significant way.
C# has IDisposable/using. It's not equivalent - a far cry from it - but it covers some common cases. And Java is getting Closable/try-block in Java 8, which is more or less the same thing.
That's a language feature of Java 7, and is already in production.
"Little does he know, but there is no 'I' in 'Idiot'!"
A common problem is that few development organizations take as much care in the design of their error handling as they do in the functional handling. Without this care, one result is that method signatures have too many caught exception declarations. This leads to poor exception handling in the caller and so on up the call stack. A good exception design -- for example Spring's -- leads to very few exception declarations on the method. I think that Spring's next step of hiding the exceptions is a mistake, but their code is far more used than mine so who am I to argue!
Absolutely not. Maybe there is a config file, and you just can't find it because it is somewhere else. Let the user know you can't find it and then offer a choice of browsing to the file or doing what you said. Nothing would be more confusing than thinking your config file is being read when in fact the software does what you said behind the scene instead. "Shit! No matter what option I change in the config file the damn thing just won't work!
Guns don't kill people; Physics kills people! - John Lithgow as Dick Solomon on Third Rock From The Sun
The fundamental problem is that sometimes an error is an error to the calling program, but sometimes it is not.
For example, when you issue: open "$HOME/.myconfig", the inability to find the file does not mean there is an error. Just that the optional config file is not there. But when you try to open the source file for an operation, the open-error really IS an error.
This duality happens at most levels. A library wrapping "open" will have the same problem. Does the caller consider this a fatal error or not?
Similarly, sometimes errors should result in telling the user and then quitting. But for a gui application it's better to show a graphical message and continue, even if the error is more or less "fatal".....
Mod parent up, I'm in complete agreement: "If there's a better way, show us. Come up with a solution. What's stopping you?" And indeed it's not obvious, or we would already have the solution.
I'm not sure I agree that it's possible to develop defect-free software. All hardware is unreliable. Mean time between failure.
Perfect software may be perfect in our minds; but software immediately degrades when implemented as machinery.
Perhaps the original poster is frustrated by the perfection in our minds failing to overcome the limitations of physical reality ... much as we all wish to live forever, even though we know that's not going to happen.
-kgj
Somebody please mod parent up to 11, and give them a knighthood, a late night television show, and a comedy nobel prize!
This needs to be read by so very many people!
In that case, I would create a second method with the same signature that calls the first one (plus some documentation why).
Duplicated code should always be avoided: either split a method up so you can reuse parts, or make a private method that contains both code paths and is called by the two other methods with different parameters.
That being said, I've had methods that looked 99% the same, but used for example one different type, making them impossible to merge/reuse (generics can't fix everything).
This isn't an either-or / where's-the-third option question - exceptions and return values just need to be used for what they're best suited.
Use exceptions for error conditions that can be generalized, i.e., they need no more information than this for a domain-specific (client/service/DAL) Policy to decide what to do.
1. Is it a genuine error, or a stopping condition encountered in normal operation?
2. Does it contain a message intended for the end-user to see?
3. Should it be logged?
No need for try/catch blocks everywhere for this since you've generalized it.
Error conditions that can't or shouldn't be integrated into a generalized exception-handling policy... these should relay information through function return values as part of an application's business process, not through exceptions.
It's not complicated.
What this guy is complaining about is that Error Handling code must reside at some point along the code that may encounter an error. He specifically mentions that he'd like to put all error handling in another file.
What he really wants is a tool for organizing code blocks in a more logical way.
What this guy wanta is something like the Leo editor. Of course Leo is not the only player in it's cathegory, see Outliners.
Now for the most interesting problem, singaling abnormal return conditions. Exceptions trully are the right contruct, specially for any code that is intended to be used in a larger program. Exceptions do the right thing and they do it better.
Code examples. For example, all these snippets of pseudocode are functionally the same:
# using out parameters
code = functionCall(argument1, argument2, OUT PARAMETER value)
if code != SUCCESS_CONST
return code
else
doSomething(value)
# using ad hoc structures
returnHandler = functionCall(argument1, argument2)
if not returnHandler.success
return returnHandler.errorCode
else
doSomething(returnHandler.value)
# using a resource object, basically same as above, but with more functions in the name space
resource = functionCall(argument1, argument2)
if not library_successTest(resource)
return library_getError(resource)
else
doSomething(library_getValue(resource))
# using exceptions
doSomething(functionCall(argument1, argument2))
Yes, the last example is exception oriented code. No Try/Catch needed. Must exception oriented code include a stupid amount of superfluous try/catch. The first idea is that code that doesn't handle errors shouldn't care about errors. in this example `doSomething` doesn't do anything with the error values of functionCall so why should it take so much effort to feed the return of functionCall to doSomething or bail?
If you argument is that the calling code shouldn't return an error and should handle the error; think about it. Whatever the parent intended to do has failed, so the parent still has to handle the error but now it doesn't have any information about what went wrong. Unless of course, you intend the caller to fix everything, in which case you live in a perfect world where errors don't occur. Or, you intend functions to handle errors for their all their calling parents which is insane.
But... the future refused to change.
I assert()....
The exception handler needs an exception handler...
Remember it is turtles all the way down.
Truth is stranger than fiction, but it is because Fiction is obliged to stick to possibilities; Truth isn't. Mark Twain.
There's a reason Valve is liked for releasing things when they're ready. People liked the same thing about Blizzard until Blizzard went apedung about maintaining its monopoly on matchmaking servers for its video games (the bnetd case).
I think one of the core uses comes from that 'appropriate action' part. It is not always possible or even desirable to handle an error where the error actually occurred.
Another major use case would be where the programmer does not have control over the code, such as loadable modules or libraries linked to.
I can see the argument that they are a fancy goto, but then again events are just fancy function calls. That exceptions can propegate up a call stack until something catches them (without the throwing function needing any knowledge of anything earlier on the stack) gives them significant extra functionality.
If you have ever thrown an exception to indicate a success condition, you fail.
This is just plain wrong because if another developer ever implements an interface without a try...catch, you just caused the application to crash because of successful operation.
My experience with Exceptions, and API's that are full of them, is that the developers are pretty much saying they don't know what the hell is going on, so TRY everything and respond accordingly. There is this thing called Unit Testing, which means you build tests for both success and failing conditions for an interface and ensure the application will not crash and recover for failing conditions. An app should always succeed, so constantly trying interface calls is overhead. I only TRY calls to external API's because I cannot clearly know they will succeed, I didn't build unit testing in 3rd party tools therefore I don't trust them.
I mean, what the fuck does Exception stand for? Its an Exception to the known state of an app. If you throw for ALL known states including success, you fail.
I haven't thought of anything clever to put here, but then again most of you haven't either.
No, he's not seriously suggesting that. You just failed the Turing Test.
As someone who's written many thousands of lines of code for a living, I've often sat down and looked at a piece of code and realized there was a better way to divide up my classes and functions that I didn't think of initially and wonder if its worth the effort to refactor it intelligently or leave it as-is for the sake of maintenance.
I nearly always write down those thoughts in my code though, so they can be found later. /* TODO (LAZY): yes this should be a generator and func_y() should be private */
- Michael T. Babcock (Yes, I blog)
Thanks for the clarification. The nit I picked was out of scope, given the understanding that defect-free software assumes the integrity of the hardware.
-kgj
That's why they invented goto.
Confucius say, "Find worm in apple - bad. Find half a worm - worse."