Search for a label than a trailing right brace

  • Thread starter rcgldr
  • Start date
  • Tags
    Search
In summary: I respectfully disagree. If your code starts using several goto's, you probably need to rework your design."Incorrect. Proper use of goto's can help improve readability and organization of code.
  • #71


I don't know fortran, but it sounds like the triple branch is worse than goto. I guess it gets less attention because it's not present in as many languages.
 
Technology news on Phys.org
  • #72


"I don't know fortran, but it sounds like the triple branch is worse than goto. I guess it gets less attention because it's not present in as many languages."

Agreed.
 
  • #73


People do pick on Fortran's triple branch and have done so even before FORTRAN became Fortran. Fortran 90 marked it as obsolete, and compilers can be made to issue warnings about use of any features marked for obsolescence.

C's (and C++'s) switch statement is essentially a computed goto. This enables constructs such as Duff's device, here used to copy count items from array from to some special memory location to:
Code:
switch (count % 8) {
  case 0: do {  *to = *from++;
  case 7:       *to = *from++;
  case 6:       *to = *from++;
  case 5:       *to = *from++;
  case 4:       *to = *from++;
  case 3:       *to = *from++;
  case 2:       *to = *from++;
  case 1:       *to = *from++;
           } while ((count -= 8) > 0);
}

Note however, that there are no gotos here!

Speaking of switch statements, I've always found the default fall-through behavior a bit annoying and bug-inducing. Most languages that have a switch statement analog do not have this behavior; they don't even have a mechanism to support fall through.
 
Last edited:
  • #74


D H said:
C's (and C++'s) switch statement is essentially a computed goto. This enables constructs such as Duff's device.
I had forgotten switch case can be used to as means to implement multiple entry points into nested code.
 
Last edited:
  • #75


Switch is more limited, and therefore safer than goto: all of the labels that flow can jump to have to lie within the switch block.

Still, many an unwary programmer has left out a necessary "break" statement. On a small scale, that illustrates a peril of goto-structured code: forgetting to jump to the beginning of the next block at the end of the current one.
 
Last edited:
  • #76


Also, jumps are only forward jumps. These two things taken together make it fairly straightforward to understand and harder to abuse.

I agree, however, that the fall-through is a bad idea in general. I think it's for the best that most newer languages don't allow this.
 
  • #77


Duff's Device is abuse personified. I would rate Duff's Device as much worse in terms of software quality than a goto used for a multi-level break or for error handling. But hey! Duff's Device doesn't have any gotos. It must be OK.

Finding all of the abusive but goto-free practices in a software package can be difficult. Static analysis tools can find some, but making them do so also tends to make them produce a lot false positives. Finding all of the errors in a software package is even harder; for a package of any size is practically impossible. The goal of testing is to reduce the errors to some acceptable level. Even then, a very significant amount of money, well over half of the total project budget for safety-critical software, is spent on testing.

In comparison, finding all the gotos in a software package is easy:
grep -l goto `find $PACKAGE_HOME -name '*.cc'`
There shouldn't be very many. That does not mean there shouldn't be any. A well-used goto can significantly the reduce accidental complexity of the function in which it is used, and may well reduce the accidental complexity of project as a whole.

Those of you who advocate no gotos whatsoever are raising a phony argument. Nobody here is advocating a return to 1960s-style spaghetti code. Djikstra was fighting indiscriminate use of gotos. Gotos are not used indiscriminately now. They are used on a very discriminating basis, if for no other reason that many quality software projects that do allow the use of gotos only allow this use after granting a waiver.

Good programmers learn to look for design patterns, things that can be reused. Excellent programmers learn to look for anti-patterns, things that should be avoided. The key problem with dogmatically rejecting goto, break, and multiple return statements is that doing so creates an anti-pattern, the Arrow Anti-Pattern. Use of the arrow anti-pattern increases the accidental complexity of the code. As errors are highly correlated with code complexity, increasing the accidental complexity is a bad thing to do.


Some reading material:
  • Steve McConnell, "Code Complete: A Practical Handbook of Software Construction", Section 16.1, Microsoft Press, 1993
    http://www.stevemcconnell.com/ccgoto.htm

    This book is a must-read for anyone who wants to progress far in the realm of computer programming and was written by one of the three most influential people in the software industry. The above link is an extract from this book on the use of the goto statement. Even though McConnell comes from the software engineering community, he says "In the remaining one case out of 100 in which a goto is a legitimate solution to the problem, document it clearly and use it."


  • Frederick Brooks, "No Silver Bullet Essence and Accidents of Software Engineering", Computer, 20:4, 1987
    http://www.lips.utexas.edu/ee382c-15005/Readings/Readings1/05-Broo87.pdf

    This paper is the origin of the terms "accidental complexity" and "essential complexity". You can find this paper in Brooks' book "The Mythical Man-Month", another must read.


  • Jeff Atwood, "Flattening Arrow Code", Coding Horror (blog), 2006
    http://www.codinghorror.com/blog/archives/000486.html
    Atwood's Coding Horror blog said:
    The excessive nesting of conditional clauses pushes the code out into an arrow formation:

    Code:
     if
       if
         if
           if
             do something
           endif
         endif
       endif
     endif

    And you know you're definitely in trouble when the code you're reading is regularly exceeding the right margin on a typical 1280x1024 display. This is the http://c2.com/cgi/wiki?ArrowAntiPattern" in action.


  • lucky.linux.kernel, Google Groups, 2003
    http://kerneltrap.org/node/553/2131
    NewsGroup said:
    From: Linus Torvalds
    Subject: Re: any chance of 2.6.0-test*?
    Date: Sun, 12 Jan 2003 12:22:26 -0800 (PST)

    On Sun, 12 Jan 2003, Rob Wilkens wrote:
    >
    > However, I have always been taught, and have always believed that
    > "goto"s are inherently evil. They are the creators of spaghetti code

    No, you've been brainwashed by CS people who thought that Niklaus Wirth
    actually knew what he was talking about. He didn't. He doesn't have a
    frigging clue.
 
Last edited by a moderator:
  • #78


D H said:
Steve McConnell, "Code Complete: A Practical Handbook of Software Construction"
Wow, that brought back memories. At a previous job, a co-worker doing a code review commented in a email to the group on my use of a single instance of if(...){goto} in a very simple module. The group boss, who would occasionally publicly chastise group members with group emails, responded back with a group email that I should refer to Code Complete, noting he had bought a copy for each member of the group, and that I should follow it's guidelines. In what was a potentially career limiting move I pointed out that, except for the "goto goons", the usage of goto in such cases was acceptable and that the arrow anti-pattern of deep nesting of if else, the one that the group boss himself was pushing, was the only method considered unacceptable by that book. The response after that was that the book was just a general guide.

This example of arrow anti pattern from the link above isn't much different from actual code sequences used by the "compliant" members of that group:

Code:
if get_resource
   if get_resource
     if get_resource
       if get_resource
         do something
         free_resource
       else
         error_path
       endif
       free_resource
     else
       error_path
     endif
     free_resource
   else
     error_path
   endif
   free_resource 
 else
   error_path
 endif
 
Last edited:
  • #79


AUMathTutor said:
Also, jumps [in a switch statement] are only forward jumps.
You may have a point there. Every claimed instance of a "good" goto involves a forward jump, never a backwards jump. Perhaps a goto limited to forward jumps only would not be as risky. Even better might be something like "gonext" which only jumps forward, and only jumps to the nearest following label named "next" (so that you can't interlace two jumps). Something like
Code:
for(int i=0;i<10;i++)
  for(int j=0;j<10;j++){
    condition = do_something(i,j);
    if(condition == ERR)
      gonext;
  }
next:
...
Such a "gonext" jump could not be abused in the ways that goto can.

If we're talking about speculative alternatives to goto, how about this one: a "smashable" block that can be broken out of by a "smash" statement. So the above would be equivalent to the below:
Code:
smashable{
  for(int i=0;i<10;i++)
    for(int j=0;j<10;j++){
      condition = do_something(i,j);
      if(condition == ERR)
        smash;
    }
}
...
This approach would be completely consistent with structured programming.

D H, I agree that Duff's device is not clean code. Is it abuse? It has an upside, namely performance. Perhaps in a few applications, it is necessary. Inline assembly code is also more complicated and less clean than straight C, but sometimes justified for performance reasons.

As we've already talked about (beginning of this discussion), there are ways to get around the "arrow pattern." Here's another, albeit peculiar:
Code:
switch(0){
case 0:
  condition = do_something();
  if(condition == ERR)
    break;
  condition = something_else();
  if(condition == ERR)
    break;
  ...
}
I'm not recommending this, because it is weird code, and weird means confusing. There are better ways to solve the problem, already discussed (throw an error exception or return from the function). But even though it is strange, the above code is arguably superior to using gotos, because unlike goto you don't have to read all the way down to the bottom to find the point that the "break" takes you to. You know, upon reading the break, that you're jumping out of the enclosing switch.
 
  • #80


D H:

"Duff's Device is abuse personified. I would rate Duff's Device as much worse in terms of software quality than a goto used for a multi-level break or for error handling. But hey! Duff's Device doesn't have any gotos. It must be OK."
Wow, your logic is awesome.

X is bad.
Y does not contain X.
Y is good.

I hope that after years of experience my understanding of basic logic is still as well-developed as yours.

"They are used on a very discriminating basis, if for no other reason that many quality software projects that do allow the use of gotos only allow this use after granting a waiver"
Well that's reassuring. Perhaps if you had been paying attention to my posts, you would have seen that I have said this should be what is done, over, and over, and over, and over, and over, again. I guess the world isn't such an insane place after all.

Jeff Reid:

It seems like the only argument against the arrow anti-pattern is that it pushes code out to the right when there are more than two nested ifs. While I don't think this is really too big a problem... I mean, so you have to scroll a little... I guess it is sort of a pain and not too aesthetically pleasing either. I'll think of another way to handle the above... I'm thinking a resource counter and a loop or two.
 
  • #81


And I really like your idea of gotoNext. If it weren't for interlacing GOTOs and backwards GOTOs, I'd have much less of a problem with them.
 
  • #82


Stop with the histrionics, AuMathTutor.

The argument against the arrow anti-pattern is that cyclomatic complexity and the presence of software errors are highly correlated. A software item that exhibits this arrow pattern is strongly indicative of that the code is buggy.
 
  • #83


"Stop with the histrionics, AuMathTutor."
I'll stop with the histrionics if you cut out the ad hominems and the blatant misrepresentations of my positions.

And as far as the arrow anti-pattern, I think there are alternatives that don't use the GOTO. Just because something takes some thought to figure out doesn't mean the thought isn't worth the effort.
 
  • #84


D H said:
The argument against the arrow anti-pattern is that cyclomatic complexity and the presence of software errors are highly correlated. A software item that exhibits this arrow pattern is strongly indicative of that the code is buggy.
The arrow anti-pattern has the same cyclomatic complexity as the equivalent code rewritten with gotos. The number of "if" statements is probably not the point--maybe there is a way to rewrite the code without so many tests, which would definitely be good if possible, but it's also likely that the conditionals test conditions that have to be tested (irreducibly complicated). That alone doesn't make it an anti-pattern. It's an anti-pattern because the excessive indentation makes it hard to read, and cleanup code may end up far from the resource allocation, as http://c2.com/cgi/wiki?ArrowAntiPattern points out.
 
  • #85


Jumping forward to nearest label would seem to be a bad idea, because the target of the jump could secretly change depending on what the intervening code contains. (In particular, if it contained another gonext .. label pair)


Since the "don't goto backwards" thing keeps coming up, I'll mention the two cases I thought of where they might be useful. (This isn't a fully-formed thought, just the germs of ideas)

The first involves two partially overlapping logical blocks of code. e.g. you might want to implement

Code:
do {
  // Some code
  // Other logical block starts here
  // Some code
} while(condition);
// Some code
// Other logical block ends here

For example, the second logical block might be an atomic access to some resource, and other concerns might strongly suggest, or even strictly require, that that block really and truly be a block, and therefore you cannot use while to express the loop. You could retain the structure directly, e.g. in a hypothetical java + goto language, by

Code:
top_of_loop:
  // Some code
  synchronized(resource) {
    // Some code
    if(condition) { goto top_of_loop; }
    // Some code
  }

But other solutions (in languages I know) would require either using things in a strange way

Code:
  while(true) {
    // Some code
    synchronized(resource) {
      // Some code
      if(condition) { continue; }
      // Some code
    }
    break;
  }

or abandoning the loop all together and writing some more complicated structure that works around the block.


The other example is complementary to the exception handling case -- upon a failure, rather than skipping ahead to the end of function to report failure, they skip backwards to try again.


Of course, there is also the case where you really are implementing an unstructured algorithm (e.g. a state machine automatically generated to do parsing), so any method of implementation is going to have an unstructured pattern to it, and the question is whether the state machine is described in C (and thus needing gotos in all directions, or while true & switch & break to emulate the goto) or if the state machine is described in some other manner amenable to whatever state machine object you've written.
 
  • #86


You're right, unintentionally changing the destination would be a fatal flaw in gonext. The "smashable" block is a better solution (a smashable block could be nested within another smashable block, and a smash statement would only get you out of the nearest enclosing one).

I suppose, if you wanted to actually use gonext, you could make two gonext's in a row without an intervening "next" tag be a syntax error. This would prevent the accidental introduction of a gonext/next pair between another gonext/next pair.

As you've shown, loops work okay for the situations you're describing. The goto is unnecessary. My personal style for a "retry until complete" situation is to use a "finished" flag, like:

Code:
  finished = false;
  while(!finished) {
    // Some code
    synchronized(resource) {
      // Some code
      if(condition) { continue; }
      finished = true;
      // Some code
    }
  }

This doesn't significantly change the structure, but perhaps it makes it a little clearer what I'm doing.

In situations where you have unstructured, automatically generated code, it doesn't matter what it looks like--unrestricted use of goto is fine in that situation (so long as the code is correct and efficient) because it's not intended for humans.
 
Last edited:
  • #87


AUMathTutor said:
It seems like the only argument against the arrow anti-pattern is that it pushes code out to the right when there are more than two nested ifs.
That's only part of the issue, the main issue is that it the else handling for the outer ifs are farthest away from the code fragment that triggered the else condition. From Code Complete: Moreover, the distance between error-processing code and code that invokes it is too far: the code that sets ErrorState to FileFindError, for example, is 23 lines from the if statement that invokes it.[/quote]

Guarding of code via a flag can be used, to avoid gotos:

Code:
    fgotoxyz = FALSE
    if(fgotoxyz == FALSE)
    {
        // step 1
        if(step 1 failed)
        {
           step 1 error handling;
            fgotoend = TRUE;
        }
    }

    if(fgotoxyz == FALSE)
    {
        // step 2
        if(step 2 failed)
        {
            step 2 error handling;
            fgotoend = TRUE;
        }
    }
    ...
    // xyz section starts here

or switch breaks to perform the same function as goto

Code:
    switch(0)
    {
      case 0:
        //step 1
        if(step 1 failed)
        {
            step 1 error handling
            break;
        }
        //step 2
        if(step 2 failed)
        {
            step 2 error handling
            break;
        }
         ...
    }
 
  • #88


mXSCNT said:
The arrow anti-pattern has the same cyclomatic complexity as the equivalent code rewritten with gotos.

No, it doesn't. Read post #21 for a counterexample.

Converting old-fashioned spaghetti code to something to a pure structured programming form requires the creation of extra variables, extra boolean clauses, and at times, replication of code. For sufficiently large N, there exists an N-statement program written with gotos that requires N! statements when without any gotos. Those extra boolean clauses and the replicated code act to increase static analysis metrics such as complexity.

The statement if (a && b) do_something(); can be rewritten as if (a) if (b) do_something(); The two forms are functionally equivalent but have different cyclomatic complexity (2 versus 3). They do however have the same extended cyclomatic complexity (3). Another way to look at it: To achieve 100% code coverage using a good code coverage tool one would have to write three test cases for this do_something() code snippet, regardless of the form in which it is written.mXSCNT, what you and AuMathTutor are doing is creating phony arguments against the use of goto. I posted a link to a section from Code Complete in post #77. Did you read it? That a five line code snippet that can be written without using gotos is a phone argument against gotos. Where the use of goto is justified is in halving the lines of code and decimating the complexity of a 800+ line monstrosity with a cyclomatic complexity of 96.

In my experience, gotos are most likely to be used in projects where some fool of a project manager has mandated the atrocious (IMHO) "single return" anti-pattern and has ruled out use of break and continue to boot. These rules practically mandate the use of gotos in complex functions where guards must be placed against errors.

One way around the error guard use of gotos is to use exception handling, if available. There are two problems with this. One is that using the exception handler for expected behaviors (e.g., EOF; every file has an EOF, so hitting EOF is not an exceptional thing to do) is itself an anti-pattern, the "Expection Anti-Pattern". Another problem is that in real-time programming, the words try, throw, and catch are made illegal. (Exceptions are slow, which alone wreaks havoc with many real-time scheduling algorithms. That exceptions typically involve hopping in and out of kernel mode wreaks serious havoc with scheduling algorithms.)

The easiest way to avoid having any gotos is to allow the use of the return, break and continue statements. Example: A common requirement in the simulation world is that behavior models must be disable-able and must not perform any computations (other than testing for being disabled) if they are disabled. I would much prefer to see if (disabled) return; at the very start of a model implementation than any other construct. Verification of this requirement is then achieved by a simple code inspection that takes a few seconds.
 
  • #89


Post #21 is an anecdote of a poorly managed project. It has no bearing on my point, which is that a straightforward translation of the arrow anti-pattern to use gotos instead, does not change the cyclomatic complexity of the code. The paths through the code are all essentially the same, just denoted differently.
For sufficiently large N, there exists an N-statement program written with gotos that requires N! statements when without any gotos.
How do you count the number of "statements required"? Any program could be written in a bounded number of statements, simply by using that bounded number of statements to write an interpreter, then supplying the rest of the program as a string constant argument to the interpreter.

We do agree that banning multiple points of return and break/continue statements can harm the legibility of code.
 
  • #90


That an N statement perniciously written pile of spaghetti might expand to N! statements of neatly written structured program should not be too surprising. This rewriting of the code is essentially the general Boolean satisfiability problem, which is of course an NP-complete problem.
 
  • #91


D H said:
That an N statement perniciously written pile of spaghetti might expand to N! statements of neatly written structured program should not be too surprising. This rewriting of the code is essentially the general Boolean satisfiability problem, which is of course an NP-complete problem.

You are not making any sense to me. How about you make things a bit more formal?
 
Back
Top