Is it ever advantageous to use 'goto' in a language that supports loops and functions? If so, why?
Everybody who is anti-goto
cites, directly or indirectly, Edsger Dijkstra's GoTo Considered Harmful article to substantiate their position. Too bad Dijkstra's article has virtually nothing to do with the way goto
statements are used these days and thus what the article says has little to no applicability to the modern programming scene. The goto
-less meme verges now on a religion, right down to its scriptures dictated from on high, its high priests and the shunning (or worse) of perceived heretics.
Let's put Dijkstra's paper into context to shed a little light on the subject.
When Dijkstra wrote his paper the popular languages of the time were unstructured procedural ones like BASIC, FORTRAN (the earlier dialects) and various assembly languages. It was quite common for people using the higher-level languages to jump all over their code base in twisted, contorted threads of execution that gave rise to the term "spaghetti code". You can see this by hopping on over to the classic Trek game written by Mike Mayfield and trying to figure out how things work. Take a few moments to look that over.
THIS is "the unbridled use of the go to statement" that Dijkstra was railing against in his paper in 1968. THIS is the environment he lived in that led him to write that paper. The ability to jump anywhere you like in your code at any point you liked was what he was criticising and demanding be stopped. Comparing that to the anaemic powers of goto
in C or other such more modern languages is simply risible.
I can already hear the raised chants of the cultists as they face the heretic. "But," they will chant, "you can make code very difficult to read with goto
in C." Oh yeah? You can make code very difficult to read without goto
as well. Like this one:
#define _ -F<00||--F-OO--;
int F=00,OO=00;main(){F_OO();printf("%1.3f\n",4.*-F/OO/OO);}F_OO()
{
_-_-_-_
_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_
_-_-_-_
}
Not a goto
in sight, so it must be easy to read, right? Or how about this one:
a[900]; b;c;d=1 ;e=1;f; g;h;O; main(k,
l)char* *l;{g= atoi(* ++l); for(k=
0;k*k< g;b=k ++>>1) ;for(h= 0;h*h<=
g;++h); --h;c=( (h+=g>h *(h+1)) -1)>>1;
while(d <=g){ ++O;for (f=0;f< O&&d<=g
;++f)a[ b<<5|c] =d++,b+= e;for( f=0;f<O
&&d<=g; ++f)a[b <<5|c]= d++,c+= e;e= -e
;}for(c =0;c<h; ++c){ for(b=0 ;b<k;++
b){if(b <k/2)a[ b<<5|c] ^=a[(k -(b+1))
<<5|c]^= a[b<<5 |c]^=a[ (k-(b+1 ))<<5|c]
;printf( a[b<<5|c ]?"%-4d" :" " ,a[b<<5
|c]);} putchar( '\n');}} /*Mike Laman*/
No goto
there either. It must therefore be readable.
What's my point with these examples? It's not language features that make unreadable, unmaintainable code. It's not syntax that does it. It's bad programmers that cause this. And bad programmers, as you can see in that above item, can make any language feature unreadable and unusable. Like the for
loops up there. (You can see them, right?)
Now to be fair, some language constructs are easier to abuse than others. If you're a C programmer, however, I'd peer far more closely at about 50% of the uses of #define
long before I'd go on a crusade against goto
!
So, for those who've bothered to read this far, there are several key points to note.
- Dijkstra's paper on
goto
statements was written for a programming environment wheregoto
was a lot more potentially damaging than it is in most modern languages that aren't an assembler. - Automatically throwing away all uses of
goto
because of this is about as rational as saying "I tried to have fun once but didn't like it so now I'm against it". - There are legitimate uses of the modern (anaemic)
goto
statements in code that cannot be adequately replaced by other constructs. - There are, of course, illegitimate uses of the same statements.
- There are, too, illegitimate uses of the modern control statements like the "
godo
" abomination where an always-falsedo
loop is broken out of usingbreak
in place of agoto
. These are often worse than judicious use ofgoto
.
There are a few reasons for using the "goto" statement that I'm aware of (some have spoken to this already):
Cleanly exiting a function
Often in a function, you may allocate resources and need to exit in multiple places. Programmers can simplify their code by putting the resource cleanup code at the end of the function, and all "exit points" of the function would goto the cleanup label. This way, you don't have to write cleanup code at every "exit point" of the function.
Exiting nested loops
If you're in a nested loop and need to break out of all loops, a goto can make this much cleaner and simpler than break statements and if-checks.
Low-level performance improvements
This is only valid in perf-critical code, but goto statements execute very quickly and can give you a boost when moving through a function. This is a double-edged sword, however, because a compiler typically cannot optimize code that contains gotos.
Note that in all these examples, gotos are restricted to the scope of a single function.
Obeying best practices blindly is not a best practice. The idea of avoiding goto
statements as one's primary form of flow control is to avoid producing unreadable spaghetti code. If used sparingly in the right places, they can sometimes be the simplest, clearest way of expressing an idea. Walter Bright, the creator of the Zortech C++ compiler and the D programming language, uses them frequently, but judiciously. Even with the goto
statements, his code is still perfectly readable.
Bottom line: Avoiding goto
for the sake of avoiding goto
is pointless. What you really want to avoid is producing unreadable code. If your goto
-laden code is readable, then there's nothing wrong with it.