Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Welcome Guest | Log In | Register | Benefits
Channels ▼
RSS

Design

The Scourge of Error Handling



Related Reading


More Insights




mfranjione376

Possibly have mutiple catch levels that are documented in the code, which the IDE understands. Level 1 are true system level exceptions. Make Level 3 be algorithmic significant exception (i.e. intelligent compiler keeps compiling by assuming a missing ";" now exists so that it can better report possible subsequent syntactic errors). Level 2 is somewhere between the two (if necessary).

Then apply sproggit's IDE ideas at specific levels.

It's basically the 3 level errors/warnings/info we've used/seen before. Set your IDE to Level 0 and you don't see ANY catch blocks. Level 3 and you see ALL catch blocks (feel free to reverse the numerology if that makes more sense).

SN000

Could you say more about the "consumption of runtime resources"? I always thought that monads provided a very elegant and clean way of seperating the "abnormal" flow from the main control flow. For most monads it just translates into an extra return value which is processed with an "if".

Matt F.

A very interesting point. I'm not familiar with Google's Go language but I agree that exceptions and error returns alone do not present an ideal error handling mechanism for robust and readable code. However 'C' family languages, especially C++ are rapidly evolving at the moment and there are new ways to both raise and catch exceptions and error return codes that can be used to provide clean code and easily found error handling locations. Augmented types, Flyer based error handlers and techniques being developed for AOP such as call interception. In short its not so much the language that needs to change to solve this problem, that's already happened but the techniques developers use need to catch up to making use of the tools we now have. Error codes and exceptions will remain the underlying mechanisms because they work but the way we use them is changing. I don't believe it will be a problem to be stuck with them when the ease of use and readability issues are solved.

Compilerquäler

In most cases there is no point in the code where You know more about an error condition than at the position where the error happens.
I'm using mostly C and preferring to keep the things allways as simple as possible. So I 'm preferring code that is written straight foreward and step by step as the things have to be done. I handle error conditions the simple way to immediately log the problem where it happens and jump via a 'goto' to a cleanup position for locally allocated ressources at the end of my procedures and return a error value after the cleanup. Such code is quite easy to maintain because it's easy to locate most of occuring problems.
May be it's a good thing to let always procedures just return true in success case and false in error cases. So the caller just needs a single if statement and can handle resulting problems immediately after the call of a procedure without additional statements to check other types of error return values. Error codes can be stored in a global variable (i.e. errno) or at the position where an optional argument pointer of the functions points to.

tknarr

That skips something, though: not all errors are exceptions. Take the open() example: if my program's response to a failure to open the file is to just continue as if the file contained nothing, then exceptions make the code more complex. The cleanest code there is to check the success/failure status of the open() call and write the code as "open(); if success then { ...; close(); }". I can't do that cleanly with exceptions, and I can't think of a way to do that with external error-handler blocks because they'd need to set a local variable back in the block the error occurred in (which they don't have access to since that block's local variables are out-of-scope).

I suspect that's why calls like open() return a special value on failure, because that particular situation's so common. If you need exceptions it's easy to write "open(); if failure then throw exception;". The code to convert an exception into a status code that can be checked in an if statement isn't nearly as concise.

I think it's helpful to remember that we don't have a binary situation, we have a trinary one: success, failure, error. Not all failures are errors, and in fact not all errors are failures (I have more than a bit of code where it's the _success_ of an operation that indicates an error's occurred).

williamlouth

The adaptation I am talking about happens at runtime not after multiple logged restarts of an actor which involved engineering changing an environment variable or the code itself. This runtime self adaptation does not appear to be adequately addressed by Erlang or many other Actor implementations which seems strange considering the self healing capabilities built into most packet/switch based systems.

williamlouth

Hi Andrew,

Notification is handled via Signal Rules which are fired at the entry and exiting of a Signal Boundary. This is much more efficient and useful as it allows decisions logic to be applied across multiple signal types and their aggregation. Instead of throw an exception rules can fire another signal which will propagate upwards until one of the boundary rules decides to instead translate/transform the signal into a runtime exception.

sproggit

There are some fascinating and educational comments here from others - so thanks to all those who have posted replies.

I believe there may be an aspect or dimension to this question that we have not explicitly posited, but which is critical to answering it efficiently - context.

If I am "hacking" - experimentally writing code with the aim of getting a preliminary alpha release to compile and/or execute cleanly, then I suspect that having to flip backwards and forwards between to chunks of code may be distracting, even if I can open another IDE Window to manage that.

Conversely, if my code is stable, polished, locked down, commented and ready for deployment, then I would agree with the idea that *intrusive*, or perhaps extensively intrusive error handling may detract from ability to read the flow.

Now, what interests me about this mindset to the problem is that it relocates the challenge from the "body of code" i.e. the actual source language files, and places it in the IDE's ability to represent it. Let's go back to the original article and it's thought that the error handling was intrusive. Suppose I could configure my IDE editing window to "auto-collapse all catch blocks" {the syntactic equivalent}. Conceded, my code would still contain chunks of "try{...}" logic in parenthesis, but in practice this is not a disadvantage simply because it helps me read the code as a series of well defined logical blocks. Other options exist - for example the IDE could colour code the catch{...} logic with a darker, or less prominent hue than the mainline code.

So: the conclusions I draw are:-

1. My requirements vis-a-vis in-line or abstracted error handling will vary depending upon the maturity of the code in question, and depending upon what task I am trying to accomplish. Abstracting error handling is only going to be useful in a narrow set of use cases.

2. As the author states, the problem here is a legibility issue, related to ease-to-read. That's primarily the job of the IDE, not the language, and it has multiple potential solutions.

wtpayne

I find the fact that control flow is explicit and local in Go to be a very very nice feature. I can well understand the potential for confusion when program control jumps around non-locally (Having worked on large legacy code bases where this happened a lot). On the other hand, I also agree that large amounts of error checking code "clutter" the source, making it hard to discern the original intent of the code. We are at a seeming impasse; how do we reconcile these two requirements? Not by the mechanism the author of the article proposes, I am afraid, which meets the first requirement, but fails the second.

Fortunately, we can see that both of these problems are to do with *reading* the program logic, or rather, in how the program logic is *presented* to the reader. We already have code-folding editors. How about defining and using very regular conventions for ubiquitous "boilerplate" aspects of program logic, such as error handling and logging, then extending our editors with quick & easy filters to hide or display the boilerplate, depending on the reader's needs.

More generally, the notion that textual programming languages can support multiple display representations remains an under-explored area, at least in the mainstream.

gbjbaanb

not really, the supervisor will restart the system and it will fail again until the error condition (usually external) is resolved. You'll determine what the error is from looking at logs, but you don't need to handle the errors internally to the process, they are handled by the caller. Your program will be tested so it appears to run under normal conditions, so you don't need to handler errors for these, tested, conditions using this approach.

gbjbaanb

there is a third option - errno. All functions place an error code in this system variable that the caller can inspect after the call completes. No return values, no exceptions.
Now, the caller does need to check this, and I think it could be better mandated, it could also be enhanced with an error message as well as a code, but to be really effective it needs to be *the* error communication mechanism that all functions use.

Or, you could use it in conjunction with the concept of components or modules - you place error guards around calls to a larger component that doesn't need to handle errors quite as robustly internally - as long as they get passed back and always checked at the module boundaries. Then the entire module will be considered to have failed on error, regardless of what the particular error was. eg, if my brakes fail in my car, I don't really care which bit of the brake component failed, as long as I know its failed, I will just replace it. ok, poor analogy :) but the point is to treat the system as connected wholes rather than lots and lots of little bits that need continual checking.

tknarr

I see one problem, and that's that not all failures are errors. Take the open() call example. In one occurrence it might be an error, resulting in an error message and aborting the operation. In another occurrence it might be a normal and expected result, and the code just continues as if it'd found an empty file. Worse, both might occur in the same block of code. So not only would you need error blocks, you'd need some way to associate different error blocks with different occurrences of the same potentially-failing function. That of course can be dealt with by language constructs, it merely adds back in some of the verbosity you're trying to remove.

The worse part is one the proposed method can't fix, because the method creates it by design. That's that to figure out *which* case this one is, expected-result or real-error, you can't just look at the open() call. You have to abandon the code you're reading and jump down to the bottom of the function to find somewhere in a plethora of error blocks the one associated with this particular open() call. That breaks an old rule of coding: don't make someone reading the code jump all over the place to see what's happening. Exceptions and return values at least let someone reading the code see right there what happens when the file isn't found. It may not be aesthetically pleasing, but it's *practical*. And as a working developer I find I have less and less patience for misfeatures that, while academically and theoretically elegant, make my life harder.

Andrew Binstock

Presumably, the error-handling code would be placed somewhere by convention, so that a maintenance programmer would always know where it is.

It's not that different from constructors. New creates an object, but if the constructor does any kind of fancy processing, it's hidden from view until you dig into the class. But you know exactly where to look to find it.

Andrew Binstock

I like this idea of signals. It could even be refined into something like the signals/slots mechanism as used in Qt, so that failures are flagged but handled elsewhere with code that's not cluttering the main line of the logic.

mfranjione376

I agree somewhat with what you are saying, Our discussion has been about how the client/caller reacts to the reported error. However, the service/method doesn't know (or need to know) how clientA reacts to an error compared to clientB. Yet, all this discussion is about how the service/method reports errors.

Now what would be cool is a mechanism to easily translate one error reporting mechanism into another, and leave it up to the client to put the proper error-reporting-wrapper around the CALL.

To quote Mr. Binstock, "But we're stuck with them for the foreseeable future — just as we have been for decades"

PRUDERMAN000

Perhaps we should draw a distinction between different types of error handling. In my experience, most error "handling" is trivial. All it does is abort the current operation. The maintenance programmer doesn't need to see that. It's an unnecessary distraction. This is really the sort error handling I had in mind when I wrote my post.

On the other hand, there is another type of handling where you try to do something intelligent. Maybe you retry the operation or switch to a different strategy. This sort of handling is more significant and should be inline with the rest of the code. It is, after all, a fundamental part of the task at hand. I've found this sort of thing quite rare in practice, however.

lobrien

Monadic approaches are appealing conceptually and work at the application level, but as a *general* error-handling mechanism, I worry about their consumption of runtime resources. Straight off-the-shelf, the Option and Either monad provide clean strategies for dealing with troublesome returns.

scH4MMER

The IDE could help out by using a companion window to display the assert/catch/finally routines for any block of code. That way you can view the primary code path side-by-side with its error handling. Synchronizing the two windows would be a challenge, and you would want to be able to scan all error handling for a function, not just for the currently selected line/block of code.

mfranjione376

That's my point, though. To separate the error handling from "normal" processing, how can you tell by looking at the code if any error handling is even being done? Think what a maintenance programmer is going to see (or not see). That sounds dangerous to me.

PRUDERMAN000

The idea is to separate, not to hide. No one disagrees that error handling is important. The problem with mixing error handling with the pincipal code is simply that it makes that code harder to understand. For any given function, chances are that (at some point) a programmer who did not write it will be asked to change it in some way. The easier the code is to understand, the more quickly that change will happen and the more likely it will be correct.

LiKao

In any language which allows the implementation of monads, there is a completely different route to exception handling possible:

http://lambda-the-ultimate....

With these kinds of monadic exception any problems, such as the ones mentioned by google can be solved at the compiler level. I.e. the compiler will simply refuse to compile code where there are unhandled exceptions. This is similar to the necessity in Java to declare handled and thrown exceptions, but even somewhat more powerfull.

mfranjione376

I never understood why the aesthetics of an algorithm is more important than proper error handling/detection. The first lesson most people learn when writing programs is how to handle the "anomalies", e.g. prompt for a number, but get an alphabetic character.

To me, mis-handling of "anomalies" (or not handling them at all) is one of the biggest concerns of software implementation. Yet historically I see the push for "hiding error handling logic" or "eliminating error handling logic". Nooooooo!

When I read Steve McConnell's Code Complete, this was one area I disagreed with (agreed with just about everything else). He liked his code to present the main algorithm flow concisely and try to "bury" the error hanlding "at the bottom" of your if/else constructs. I disagreed then and I disagree now. I'd rather see how I handle the error on the IF part, vs. on the ELSE part.

Handling anomalies (whether gracefully, intelligently, abruptly, adaptively) is HALF of what we do (anybody can write a program that works perfect with guaranteed perfect input from perfect people, right?!?).

To me, the WAY a website handles "error conditions" is just as important as doing the normal processing. Simple example, I use dashes on my telephone number form. But no, it doesn't want dashes, it wants just a 10 digit number. Or I enter an extension, and the form can't handle the extension. BUT, after submitting my INVALID data, the error gets reported, BUT ALL THE FORM FIELDS ARE CLEARED, not just the telephone number.

Yes, algorithms are important. Critical. Maintaining those algorithms are also important. But so is error handling in most software that I write. So why are we trying/pushing to hide/bury an important part of our algorithm!?!

Paddy3118

Sounds like a job for "separation of concerns", "aspect oriented programming", you know, with the concern being exception handling.

I wonder if the Specman-e language would have this licked? Hmm?

YDAOUST410

Error processing is indeed an interesting topic.

As regards me, I never really switched to the using exceptions, for a simple reason: I don't understand them. I don't mean syntactically nor semantically, you can find satisfactory explanations in manuals. I mean architecturally. I never found a tutorial text on the proper use of exceptions in structured software. So I prefer to abstain rather than misuse.

williamlouth

With Erlangs approach in practice there is no adaptation or evolution mechanism in this somewhat flippant approach to fault handling...we learn by our mistakes...the supervisor for the most part simply reinstates the potential (waiting to happen) problem.