views:

1634

answers:

17

A while back I switched the way I handled c style errors.

I found a lot of my code looked like this:

int errorCode = 0;

errorCode = doSomething();
if (errorCode == 0)
{
   errorCode = doSomethingElse();
}

...

if (errorCode == 0)
{
   errorCode = doSomethingElseNew();
}

But recently I've been writing it like this:

int errorCode = 0;

do
{       
   if (doSomething() != 0) break;
   if (doSomethingElse() != 0) break;
   ...
   if (doSomethingElseNew() != 0) break;
 } while(false);

I've seen a lot of code where nothing gets executed after there's an error, but it has always been written in the first style. Is there anyone else who uses this style, and if you don't, why?

edit: just to clarify, usually this construct uses errno otherwise I will assign the value to an int before breaking. Also there's usually more code than just a single function call within the "if (error == 0 )" clauses. Lots of good points to think on, though.

+37  A: 

The second snippet just looks wrong. You're effectively re-invented goto.

Anyone reading the first code style will immediately know what's happening, the second style requires more examination, thus makes maintenance harder in the long run, for no real benefit.

Edit, in the second style, you've thrown away the error code, so you can't take any corrective action or display an informative message, log something useful etc....

Glen
No, goto is different. In this case he just jumps out of the current scope and thus nothing can go wrong.
TomWij
ok, so calling it goto is a little harsh, but I couldn't resist....
Glen
@Glen: Nope, not harsh at all. That's the first thing I thought of too. +1
Graeme Perrow
@TomWij: remove `do`. Replace `while(false)` with `error:`. Replace `break` with `goto error`. Same behavior.
Shog9
Even if it where goto - sometimes a central point for error handling will keep your code much cleaner.The secret is to know when to use which method.Linux (kernel sources) for example use goto a lot.
Shirkrin
I presume he meant to type: if ((errorCode = doSomething()) != 0) break; and then have: if (errorCode > 0) handleError(errorCode); after everything. Otherwise he's ignoring the errorCode which makes debugging much more hassle than seeing a log/console message, as you say.
JeeBee
using goto clean_up to clean up following an error before returning isn't that uncommon in C.
Pete Kirkham
@Shirkrin: if you want goto, then **use goto** - don't mislead the reader by implying there's a loop when there isn't one.
Shog9
I am reminded that you can write fortran in any language...
MikeJ
I agree with Glen. It is nothing but a glorified goto.
Naveen
yep, it's a goto. HOWEVER, I have had one instance where a GOTO was the right solution. (One. In like eight years.) This, however, is not it.
John Gietzen
Yes, edited the question. If the error code contains useful information, I will keep it. Often it's just an indication that information has been put somewhere else (errno).
patros
Goto? By the same logic, "if", "for", and function calls are also "glorified goto". We add restrictions to full-blown "goto" to make it more usable -- "break" is one such restriction. The "do" might be misleading (a loop?), but "goto" is also misleading (jump to anywhere?). The "break" has specific meaning: escape one level of context.
Ken
So maybe the answer is "yes", you're the only one who does that
LB
This is certainly not reinventing goto in any way. In terms of control-flow, this is perfectly safe (it has more in common with `return` than `goto`. Is `return` evil too?)Of course, in terms of readability, I prefer the first version.
jalf
+11  A: 

I think the first one gives you more control over what to do with a particular error. The second way only tells you that an error occurred, not where or what it was.

Of course, exceptions are superior to both...

rlbond
+1 for exceptions.
Welbog
I vote for exceptions too.
Fred Larson
Using exceptions would be great. But we still have to use code that doesn't use exceptions, e.g system API's, dlclose etc. So having a good system for dealing with these error codes is important
Glen
Joel Spolsky's complaint that exceptions are invisible goto's is here: http://www.joelonsoftware.com/articles/Wrong.html
Glenn
but imagine.. there are no exceptions if you wan't to write c?!
Shirkrin
@Shirkrin - You can have "exceptions", you just have to use gotos to simulate them. See Eclipse's answer. And then see Joel Spolsky's argument against using exceptions in the first place, as referenced by Glenn.
Chris Lutz
I hate exceptions. Good solid return codes. Some of us out here *DO* check them you know.
Chris Kaminski
+14  A: 

The first style is a pattern the experienced eye groks at once.

The second requires more thought - you look at it and see a loop. You expect several iterations, but as you read through it, this mental model gets shattered...

Sure, it may work, but programming languages aren't just a way to tell a computer what to do, they are a way to communicate those ideas to other humans too.

Paul Dixon
It's not an infinite loop. That's part of the weird. It's a do while loop that will only execute once. He's using breaks to skip over methods he doesn't want to execute. Of course, the fact that you did miss this subtlety is good reason to not use it. If reasonably experienced developers can miss this, then what hope do less experienced ones have.
toast
Not trying to imply anything bad about you. Just trying to illustrate why we should keep obscure coding tricks to a minimum.
toast
I'd corrected that before you wrote the comment, but you're right, it only serves to illustrate the sort of cognitive dissonance the second pattern causes!
Paul Dixon
I don't think you ever want to set up expectations in code and then break them some time later. Any use of "do {} while (false);" should be flagged up front in a comment, or, preferably, transformed into something else.
David Thornley
+2  A: 

This should be done through exceptions, at least if the C++ tag is correct. There is nothing wrong if you are using C only, although I suggest to use a Boolean instead as you are not using the returned error code. You don't have to type != 0 either then...

TomWij
which would be great, if every method we called in C++ threw instead of returning an exception. However we don't have control over every method we call. most system API's use errorCode's instead of exceptions
Glen
+3  A: 
Traveling Tech Guy
Exceptions: the modern man's GOTO.
Paul Nathan
which would be great, if every method we called threw instead of returning an exception. However we don't have control over every method we call. most system API's use errorCode's instead of exceptions
Glen
@Glen: `if ( !(error = FunctionCall()) ) throw CustomException(error);`
Shog9
@Paul: Exceptions are more like COMEFROM, actually.
David Thornley
@Shog9, now that could work.
Glen
@David: Because that's even better. ;-)
Paul Nathan
@Paul - I agree this is the closest you get to GOTO without using the G-word, but as someone who developed natural language compilers - sometimes GOTO is the fastest, easiest, clearest way to get what you need. Being scoffed at by the academy doesn't make it less useful :) @Shog9 - great solution!
Traveling Tech Guy
@TTG: I'm not arguing that GOTO is _sometimes_ the cleanest way. I do think exceptions are effectively another "action at a distance" effect much like GOTO. I've typically found return-codes to be a mentally cleaner way to manage these kinds of issues than exception-spaghetti.
Paul Nathan
+4  A: 

Why not replace the do/while and break with a function and returns instead?

You have reinvented goto.

I guess since this isn't C++ one might need to do manual cleanup before return.
Eugene
There may be code that gets executed after the "loop". The "loop" could be made into it's own functions, with returns instead of breaks though.
patros
+1  A: 

I've used the technique in a few places (so you aren't the only one who does it). However, I don't do it as a general rule, and I have mixed feelings about it where I have used it. Used with careful documentation (comments) in a few places, I'm OK with it. Used everywhere - no, generally not a good idea.

Relevant exhibits: files sqlstmt.ec, upload.ec, reload.ec from SQLCMD source code (not, not Microsoft's impostor; mine). The '.ec' extension means that the file contains ESQL/C - Embedded SQL in C which is pre-processed to plain C; you don't need to know ESQL/C to see the loop structures. The loops are all labelled with:

    /* This is a one-cycle loop that simplifies error handling */
Jonathan Leffler
+6  A: 

Make it short, compact, and easy to quickly read?

How about:

if ((errorcode = doSomething()) == 0
&&  (errorcode = doSomethingElse()) == 0
&&  (errorcode = doSomethingElseNew()) == 0)
    maybe_something_here;
return errorcode; // or whatever is next
DigitalRoss
I don't know if I'd call that "easy to quickly read".
Chris Lutz
Maybe not the easiest thing to read, but the built-in short-circuiting behaviour is exactly the desired one.
Eclipse
+39  A: 

If you're using C++, just use exceptions. If you're using C, the first style works great. But if you really do want the second style, just use gotos - this is exactly the type of situation where gotos really are the clearest construct.

    int errorCode = 0;

    if ((errorCode = doSomething()) != 0) goto errorHandler;
    if ((errorCode = doSomethingElse()) != 0) goto errorHandler;
      ...
    if ((errorCode = doSomethingElseNew()) != 0) goto errorHandler;

    return;
errorHandler:
    // handle error

Yes gotos can be bad, and exceptions, or explicit error handling after each call may be better, but gotos are much better than co-opting another construct to try and simulate them poorly. Using gotos also makes it trivial to add another error handler for a specific error:

    int errorCode = 0;

    if ((errorCode = doSomething()) != 0) goto errorHandler;
    if ((errorCode = doSomethingElse()) != 0) goto errorHandler;
      ...
    if ((errorCode = doSomethingElseNew()) != 0) goto errorHandlerSomethingElseNew;

    return;
errorHandler:
    // handle error
    return;
errorHandlerSomethingElseNew:
    // handle error
    return;

Or if the error handling is more of the "unrolling/cleaning up what you've done" variety, you can structure it like this:

    int errorCode = 0;

    if ((errorCode = doSomething()) != 0) goto errorHandler;
    if ((errorCode = doSomethingElse()) != 0) goto errorHandler1;
      ...
    if ((errorCode = doSomethingElseNew()) != 0) goto errorHandler2;

errorHandler2:
    // clean up after doSomethingElseNew
errorHandler1:
    // clean up after doSomethingElse
errorHandler:
    // clean up after doSomething
    return errorCode;

This idiom gives you the advantage of not repeating your cleanup code (of course, if you're using C++, RAII will cover the cleanup code even more cleanly.

Eclipse
+1 `goto` gets a bad rap, but in this case it just makes sense.
Andrew Keeton
If you are using this idiom to insure unrolling partially completed work, you usual arrange the error labels in reverse order that they get called.
dmckee
As long as the error code he's checking for is indeed 'exceptional'. Exceptions are good for catastrophic errors not for normal run of the mill conditions.
Jay
@dmckee - Good point - I'll edit it to reflect that.
Eclipse
+1  A: 

The classic C idiom is:

if( (error_val = doSomething()) == 0)
{ 
   //Manage error condition
}

Note that C returns the assigned value from an assignment, enabling a test to be performed. Often people will write:

if( ! ( error_val = doSomething()))

but I retained the == 0 for clarity.

Regarding your idioms...

Your first idiom is ok. Your second idiom is an abuse of the language and you should avoid it.

Paul Nathan
I personally prefer to have the assignment and the checking on different lines, because it starts to look cluttered if I don't (probably because I prefer not to have spaces between parenthesis. I prefer not to have more than one pair of parenthesis regardless).
Chris Lutz
I'm not sayin' that this is the One True Way. This is just the classic idiom. I usually split the assignment and checking into two blocks as well - seems to work better as code evolves.
Paul Nathan
A: 

The first style is a good example of why exceptions are superior: You can't even see the algorithm because it's buried under explicit error handling.

The second style abuses a loop construct to mimic goto in a mislead attempt to avoid having to explicitly spell out goto. It's plainly evil and long-time usage will lead you to the dark side.

sbi
+3  A: 

Your method isn't really bad and it's not unreadable like people here are claiming, but it is unconventional and will annoy some (as you noticed here).

The first one can get REALLY annoying after your code gets to a certain size because it has a lot of boilerplate.

The pattern I tended to use when I couldn't use exceptions was more like:

fn() {
    while(true) {
        if(doIt())
            handleError();//error detected...
    }
}

bool doIt() {
    if(!doThing1Succeeds())
        return true;
    if(!doThing2Succeeds())
        return true;
    return false;
}

Your second function should be inlined into the first if you put the correct magic incantations in the signature, and each function should be more readable.

This is functionally identical to the while/bail loop without the unconventional syntax (and also a bit easier to understand because you separate out the concerns of looping/error handling from the concerns of "what does your program do in a given loop".

Bill K
+1 Yes, I think the biggest reason to avoid it is the strong reaction. Didn't realize so many people would have strong opinions.
patros
A: 

For me, I'd prefer:

if(!doSomething()) {
    doSomethingElse();
}
doSomethingNew();

All of the other stuff is syntactic noise that is obscuring the three function calls. Inside of Else and New you can throw an error, or if older, use longjmp to go back to some earlier handling. Nice, clean and rather obvious.

Paul W Homer
A: 

There seems to be a deeper problem here than your control constructs. Why do you have such complex error control? I.e. you seem to have multiple ways of handling different errors.

Typically, when I get an error, I simply break off the operation, present an error message to the user, and return control to the event loop, if an interactive application. For batch, log the error, and either continue processing on the next data item or abort the processing.

This kind of control flow is easily handled with exceptions.

If you have to handle error numbers, then you can effectively simulate exceptions by continuing normal error processing in case of an error, or returning the first error. Your continued processing after an error occurs seems to be very fragile, or your error conditions are really control conditions not error conditions.

Larry Watanabe
A: 

Honestly the more effective C/C++ programmers I've known would just use a gotos in such conditions. The general approach is to have a single exit label with all cleanup after it. Have only one return path from the function. When the cleanup logic starts to get complicated/have conditionals, then break the function into subfunctions. This is pretty typical for systems coding in C/C++ imo, where the APIs you call return error codes rather than throw exceptions.

In general, gotos are bad. Since the usage I've described is so common, done consistently I think its fine.

Frank Schwieterman
A: 

How about this version then

I'd usually just do something like your first example or possibly with a boolean like this:

bool statusOkay = true;

if (statusOkay)
    statusOkay = (doSomething() == 0);

if (statusOkay)
    statusOkay = (doSomethingElse() == 0);

if (statusOkay)
    statusOkay = (doSomethingElseNew() == 0);

But if you are really keen on the terseness of your second technique then you could consider this approach:

bool statusOkay = true;

statusOkay = statusOkay && (doSomething() == 0);
statusOkay = statusOkay && (doSomethingElse() == 0);
statusOkay = statusOkay && (doSomethingElseNew() == 0);

Just don't expect the maintenance programmers to thank you!

GrahamS
A: 
anon