• Welcome to Jose's Read Only Forum 2023.
 

The GOTO Gotcha!

Started by Donald Darden, August 03, 2007, 10:16:31 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Donald Darden

Some purists believe that the GOTO statement should be totally eliminated from the BASIC language.  It's considered to be the primary cause for what is often called Spagetti Code.

It's true that the humble GOTO can be overused in some situations, but the real reason that programs are vulnerable to Spagetti Code is the lack of proper structure and control of program flow.

Consider that there are several methods by which program control can be diverted when a program is executing.  One well known case involves the IF...THEN...ELSEIF...ELSE...END IF logical structures.  Another involves the SELECT CASE...CASE...CASE ELSE...END SELECT structure.  You may also encounter structures involving ON...GOTO and ON...GOSUB conditions, and even error trapping may cause program control to be diverted.  And of course, events and CALLBACK functions allow external factors to control the manner in which our code is executed.

One question comes to mind:  If all these different methods allow our programs
to divert from a straight progression of instructions, then why is there so much
dislike for GOTO, which merely does the same thing. but without a logical determination being required?  Where is the justice in that?

If I look back at one of the older BASICs, the one called BASICA, or GWBASIC,
which had essentially the same syntax, I could see that the language did not
support IF...THEN...END IF structures.  It only supported the single line version of the IF...THEN logical expression, which means that whatever you chose to do, it had to either fit on that single line to be performed if the statement was true,
or you had to append a GOTO there that would take you elsewhere in the
program to finish out the rest of the operations that followed on the heels of a true condition.

The problem was, you jumped out, but somebody then had to figure out where you had to get back to when you were done.  And that would also take a GOTO statement to achieve.  Along with tracking the related line numbers. Since all this was not well documented, it took on the appearance of spagetti code.  But it was not the fault of the little old GOTO statement.

What tended to condem the GOTO statement further was that you could use a GOSUB statement in many cases in its place.  Then instead of having to use a second GOTO statement to get back, you just used a RETURN statement, and there you were, right back to where you have been.  A lot less tracking involved.

So, the GOTO was needlessly messy, and the GOSUB was neat and tidy.  End of story, right?  Actually, no.  GOTO's do something better than any other method,
which is allow divergent code sequences to merge again at some point.  This can minimize unnecessary redundant coding in some cases.  And since modern compilers allow us to use lables in place of line numbers, the names we use, and the comments we add, can help clarify the purpose of the code involved.

But using a GOTO here or there requires some careful consideration of the relative states of the code at different places, because the GOTO is going to make one portion suddenly flow into the other.  This will certainly involve any variables and their present content, but it also involves more than that.  For instance, what happens if you jump out of a FOR loop?  As explained before, with some older compilers and interpreters, you leave a variable reference on the stack, which creates a problem.  It should not be a problem with a modern compiler.  But what happens if you jump into a FOR loop, meaning you did not properly initialize the FOR variable, or define its conditions?  That puts you in the position of uncertainty.  How about using a GOTO to exit a GOSUB sequence?
That would leave a RETURN statement on the stack, a grave error, unless you
set up a counter RETURN statement to deal with that.  Or following another
sequence and then using GOTO to jump into a GOSUB sequence improperly,
where the RETURN would attempt to return to a location that is not present on the stack.  That would corrupt the stack contents and behave unpredictably.

Some programmers want to religate GOSUB and RETURN to the same heap where they want to dump GOTO.  Instead, they favor the newer SUB and FUNCTION over the older methods.  They like the ability to pass parameters or arguments,
to return a result, to isolate local and static variables, and to exercise a greater degree of modulation and independence.

The way PowerBasic works now, everything has to be in a function or sub, even
the main program code has its own function, either called PBMAIN or WINMAIN.
You have the option to write additional SUBs or FUNCTIONs to do whatever is
necessary, and if you want, you can still fall back on the simpler GOSUB/RETURN
or GOTO if it suits you,  The only trick is, that these simpler forms must fit into one of the existing SUBs or FUNCTIONs, and their scope of operation is confined to being called only from within that SUB or FUNCTION.  Because the mechanism for allocating and returning stack space is managed by the compiler, some of the possible ills that come from misuse of either the GOSUB or GOTO statements have been mitigated, or at least confined to that specific SUB or FUNCTION in which it appears.

The big advantage of using GOTO or GOSUB is simplicity and speed of execution.
It also can mean a much more efficient allocation of stack space, and if you can
pass references via registers rather than the stack, it can mean a dramatic improvement in performance.

But as pointed out, using GOTO or GOSUB properly means at least three considerations are involved:
(1)  The state of any referenced variables and registers at both ends of the operation
(2)  What is on the stack, and what might be left on the stack as a result
(3)  Whether the merger of divergent processes at this point present any
problems or not.

Theo Gottwald

#1
In Low Level programming (near ASM) using JMP or GOTO instructions, is the normal case.
I even do not think that actual CPU Commands let you a chance to get around without unconditional Jumps.
Because of these architectural reasons, in highly optimized code JMPs and GOTO will be found.

Is here anyone who never uses GOTO?


Charles Pegge

GOTOs
Yes Theo, I use them for making an early exit from a nested structure or series of nests to a termination point in a procedure. But with assembler, anything goes, as long as it it well conceived.

Eros Olmi

#3
Never used GOTO.
I've also avoided to introduce GOTO and GOSUB in thinBasic.

I like "spaghetti" but not "spaghetti code" ;)
thinBasic Script Interpreter - www.thinbasic.com | www.thinbasic.com/community
Win7Pro 64bit - 8GB Ram - Intel i7 M620 2.67GHz - NVIDIA Quadro FX1800M 1GB

Charles Pegge

My scripting engine (I have not named it yet), allows  a GOTO, only to jump forwards, and you cannot jump into a deeper nesting, only to a place on the same level or shallower. Target labels are local to a block, but parental (ie shallower nested) labels are visible to their children.

This makes the code easy to follow, avoids continually inventing new label names.

Without GOTOs climbing out of deep nestings, when you are done can be awkward.

Theo Gottwald

QuoteI like "spaghetti" but not "spaghetti code"

I could do without, and i read all the books which explain, why its not needed.
In fact I can write better optimized code with using GOTOs  :-).
Thats why I prefer Rice with GOTOs :-) and also avoid sphaghetti :-).

Eros Olmi

For sure you are better that me on GOTO usage!
I get lost quite soon using GOTO.
thinBasic Script Interpreter - www.thinbasic.com | www.thinbasic.com/community
Win7Pro 64bit - 8GB Ram - Intel i7 M620 2.67GHz - NVIDIA Quadro FX1800M 1GB

Theo Gottwald

Its not that much. Lets look at an example.
I have just picked out a random example.

SUB CMD_0784(BYREF T01 AS STRING)
REGISTER R01 AS LONG,R02 AS LONG
LOCAL T02,T03 AS LONG
LOCAL T04 AS STRING
IC_Debug_In($C0784,T01)
IC_ANYER
SP_018(T01,R02) ' Splittet alles ohne Auflösung
IF (R02>2) THEN GOTO ero ' Here is the GOTO, its called in case of an Input which is not expected.
R01=X_AO() ' Hole Versionsnummer von Windows
IF (R02>1) THEN T04=X_DH() ' Hole Textbeschreibung

  IF (R02=0) THEN
    STM_004(R01) ' Resultat der Operation auf Stapel legen.
    GOTO enx
  ELSE
    SL_27(P$(0),R01)
  END IF

  IF (R02>1) THEN
    SL_26(P$(1),T04)
  END IF

enx:
  IC_Debug_Out($C0784,T01)
EXIT SUB

  ero:
  IC_SetErr(%IC_ER_PA):GOTO enx
END SUB   
                       

Eros Olmi

#8
Now I give you one of my old boss using CBasic (he is still using that language under DOS)

         DEF FN.PRMARCHIO(PLATO%,PONOFF%,PLARGH%,PSCR1$,PSCR2$,\
                          PSCR3$,PSCR4$,PSCR5$,PSCR6$,PSCR7$)\
                                                          PUBLIC
         DIM PMARCHIO$(7)
         LPRINTER
         P.N%=PLARGH%-9
         PMARCHIO$(1)=PSCR1$
         PMARCHIO$(2)=PSCR2$
         PMARCHIO$(3)=PSCR3$
         PMARCHIO$(4)=PSCR4$
         PMARCHIO$(5)=PSCR5$
         PMARCHIO$(6)=PSCR6$
         PMARCHIO$(7)=PSCR7$
         IF PONOFF%=0 THEN FOR P.K%=1 TO 7 : \
            PMARCHIO$(P.K%)=FN.PADR$(PMARCHIO$(P.K%),P.N%) : \
            NEXT P.K% : GOTO 9.1
         FOR P.K%=1 TO 7
         PMARCHIO$(P.K%)=FN.PAD$(PMARCHIO$(P.K%),P.N%)
         NEXT P.K%
     9.1 PRINT FN.PRCLEAR$;FN.PRE$;
         IF PLATO%=0 THEN GOSUB 9.31 : PRINT " ";PMARCHIO$(1); ELSE \
            PRINT PMARCHIO$(1);" "; : GOSUB 9.31
         GOSUB 9.2
         IF PLATO%=0 THEN GOSUB 9.32 : PRINT " ";PMARCHIO$(2); ELSE \
            PRINT PMARCHIO$(2);" "; : GOSUB 9.32
         GOSUB 9.2
         IF PLATO%=0 THEN GOSUB 9.33 : PRINT " ";PMARCHIO$(3); ELSE \
            PRINT PMARCHIO$(3);" "; : GOSUB 9.33
         GOSUB 9.2
         IF PLATO%=0 THEN GOSUB 9.34 : PRINT " ";PMARCHIO$(4); ELSE \
            PRINT PMARCHIO$(4);" "; : GOSUB 9.34
         GOSUB 9.2
         IF PLATO%=0 THEN GOSUB 9.35 : PRINT " ";PMARCHIO$(5); ELSE \
            PRINT PMARCHIO$(5);" "; : GOSUB 9.35
         GOSUB 9.2
         IF PLATO%=0 THEN GOSUB 9.36 : PRINT " ";PMARCHIO$(6); ELSE \
            PRINT PMARCHIO$(6);" "; : GOSUB 9.36
         GOSUB 9.2
         IF PLATO%=0 THEN GOSUB 9.37 : PRINT " ";PMARCHIO$(7); ELSE \
            PRINT PMARCHIO$(7);" "; : GOSUB 9.37
         GOSUB 9.2
         PRINT FN.PRCLEAR$; : GOSUB 9.2
         PRINT
         GOTO 9.9
     9.2 PRINT CHR$(27);CHR$(37);CHR$(53);CHR$(15);
         RETURN
    9.31 GOSUB 9.4
         P.JMARCH%=4  : GOSUB 9.51
         P.JMARCH%=8  : GOSUB 9.52
         P.JMARCH%=12 : GOSUB 9.51
         P.JMARCH%=8  : GOSUB 9.52
         P.JMARCH%=10 : GOSUB 9.51
         GOSUB 9.8
         PRINT FN.PRMARCHIETTO$;FN.PRE$;
         RETURN
    9.32 GOSUB 9.4
         P.JMARCH%=8  : GOSUB 9.51 : GOSUB 9.54
         P.JMARCH%=4  : GOSUB 9.52
         P.JMARCH%=8  : GOSUB 9.54
         P.JMARCH%=28 : GOSUB 9.51
         GOSUB 9.8
         RETURN
    9.33 GOSUB 9.4
         P.JMARCH%=8  : GOSUB 9.51
         P.JMARCH%=4  : GOSUB 9.54 : GOSUB 9.51 : GOSUB 9.53 : GOSUB 9.51 :\
                                                               GOSUB 9.54
         P.JMARCH%=28 : GOSUB 9.51
         GOSUB 9.8
         RETURN
    9.34 GOSUB 9.4
         P.JMARCH%=4  : GOSUB 9.51
         P.JMARCH%=8  : GOSUB 9.53
         P.JMARCH%=12 : GOSUB 9.51
         P.JMARCH%=4  : GOSUB 9.53 : GOSUB 9.54
         P.JMARCH%=12 : GOSUB 9.52 : GOSUB 9.51
         GOSUB 9.8
         RETURN
    9.35 GOSUB 9.4
         P.JMARCH%=32 : GOSUB 9.51
         P.JMARCH%=4  : GOSUB 9.54
         P.JMARCH%=8  : GOSUB 9.51
         P.JMARCH%=4  : GOSUB 9.54
         P.JMARCH%=8  : GOSUB 9.51
         GOSUB 9.8
         RETURN
    9.36 GOSUB 9.4
         P.JMARCH%=32 : GOSUB 9.51
         P.JMARCH%=4  : GOSUB 9.54 : GOSUB 9.53 : GOSUB 9.54
         P.JMARCH%=12 : GOSUB 9.51
         GOSUB 9.8
         RETURN
    9.37 PRINT FN.PRMARCHIETTO$;FN.PRE$;
         GOSUB 9.4
         P.JMARCH%=14 : GOSUB 9.51
         P.JMARCH%=8  : GOSUB 9.53 : GOSUB 9.51 : GOSUB 9.53
         P.JMARCH%=4  : GOSUB 9.51
         GOSUB 9.8
         RETURN
     9.4 PRINT FN.PR12$;CHR$(27);"!0";CHR$(3);
         RETURN
    9.51 FOR P.KMARCH%=1 TO P.JMARCH%
         PRINT CHR$(255);
         NEXT P.KMARCH%
         RETURN
    9.52 FOR P.KMARCH%=1 TO P.JMARCH%
         PRINT CHR$(15);
         NEXT P.KMARCH%
         RETURN
    9.53 FOR P.KMARCH%=1 TO P.JMARCH%
         PRINT CHR$(240);
         NEXT P.KMARCH%
         RETURN
    9.54 FOR P.KMARCH%=1 TO P.JMARCH%
         PRINT CHR$(0);
         NEXT P.KMARCH%
         RETURN
     9.8 PRINT CHR$(3);CHR$(2);CHR$(27);"!2";FN.PR10$;
         RETURN
     9.9 RETURN
         FEND


Quite clear, isn't it?
If you can tell me what this code is doing, send me your bank account number and I will send you 100 Euro ;)
thinBasic Script Interpreter - www.thinbasic.com | www.thinbasic.com/community
Win7Pro 64bit - 8GB Ram - Intel i7 M620 2.67GHz - NVIDIA Quadro FX1800M 1GB

Theo Gottwald

Whats your problem with this clear readable code?
You're quite happy, that I do not want your money unless you want to try again with >100.000 $
hehe :-).

Eros Olmi

You are quite happy not me!
I have another more than 10000 lines of code like that I can post here. But I will not! You are safe for the moment.
;D ;D ;D
thinBasic Script Interpreter - www.thinbasic.com | www.thinbasic.com/community
Win7Pro 64bit - 8GB Ram - Intel i7 M620 2.67GHz - NVIDIA Quadro FX1800M 1GB

Edwin Knoppert

I sometimes use goto to jump to a label which contains code to remove objects or data.
In c#/dotnet they have abandoned goto and gosubs(which i also used) and therefore there is the need to make use of (nested) if's to reach my goal.
Which is annoying..

Spagetti code.. who cares!
I mean this sort of code is written by a newbie, any other structural mistake is never mentioned and i still make common mistakes.
It's always a 'fight' to determine the best way for your code, usually it means, how far do i want to go with modularity.
Great modularity often eats severe time, and just 'embedded' code in a procedure might be written more rapidly.

This discussion tastes the same as talking to a person only doing c.
"BASIC.. nah, no pointers bla bla"

:)

Charles Pegge

I have recently found it much easier to write intricate code  by confining control structures to IF..END IF and DO..CONTINUE..EXIT DO ..LOOP, with the occasional GOTO. No need to use anything else. Some constructs benefit from SELECT ..CASE but only as a final  optimisation.

Code written this way, I find to be far more maintainable, and much quicker to debug in the first instance. The code is extendable with minimal alteration to existing structures.
The price is slightly longer source code, but it is well worth it, and probably makes little difference to the compiled result.

So for the time being, I have stopped using ELSE ELSEIF WHILE REPEAT FOR..NEXT but GOTO is still very useful for abandoning nests, especially where there is an error to handle.

Kent Sarikaya

In the 1980's, goto was a way of life for all the programs I worked on. I had to come in on a project and figure out what the previous guys did and their code was full of goto's but it was easy enough. We had line numbers and the gotos were to line numbers instead of labels, that made it a lot easier. I can see where going to labels could make it really difficult in newer basic code.

I still think it is a nice command, it has its places in my opinion, although newer ways eliminated the need for most of them.

Fred Buffington

Donald, I whole-heartedly agree with you and have stated my views on GOTO
many times, including my tips and tricks page for QB.

Care should be used when using a goto because of the tendency for things to
get lost. i.e. resulting in 'spagetti code'. However, when used correctly they
can be invaluable.

Never use a goto to jump from one routine to another, ONLY within a routine.
Most of the time, if this simple 'rule' is adhered to, you won't encounter problems
and code will still be easy to follow. A second 'rule' is keep goto statements to
a minimum.