Powerbasic Museum 2020-B

General Category => General Discussion => Topic started by: Donald Darden on August 03, 2007, 10:16:31 AM

Title: The GOTO Gotcha!
Post by: Donald Darden on August 03, 2007, 10:16:31 AM
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.
Title: Re: The GOTO Gotcha!
Post by: Theo Gottwald on August 03, 2007, 10:35:56 AM
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?

Title: Re: The GOTO Gotcha!
Post by: Charles Pegge on August 03, 2007, 12:53:03 PM
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.
Title: Re: The GOTO Gotcha!
Post by: Eros Olmi on August 03, 2007, 03:47:02 PM
Never used GOTO.
I've also avoided to introduce GOTO and GOSUB in thinBasic.

I like "spaghetti" but not "spaghetti code" ;)
Title: Re: The GOTO Gotcha!
Post by: Charles Pegge on August 03, 2007, 05:23:15 PM
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.
Title: Re: The GOTO Gotcha!
Post by: Theo Gottwald on August 03, 2007, 06:55:19 PM
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 :-).
Title: Re: The GOTO Gotcha!
Post by: Eros Olmi on August 03, 2007, 07:19:45 PM
For sure you are better that me on GOTO usage!
I get lost quite soon using GOTO.
Title: Re: The GOTO Gotcha!
Post by: Theo Gottwald on August 03, 2007, 09:13:23 PM
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   
                       
Title: Re: The GOTO Gotcha!
Post by: Eros Olmi on August 03, 2007, 09:26:38 PM
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 ;)
Title: Re: The GOTO Gotcha!
Post by: Theo Gottwald on September 08, 2007, 07:18:28 AM
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 :-).
Title: Re: The GOTO Gotcha!
Post by: Eros Olmi on September 08, 2007, 01:23:47 PM
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
Title: Re: The GOTO Gotcha!
Post by: Edwin Knoppert on September 08, 2007, 08:17:34 PM
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"

:)
Title: Re: The GOTO Gotcha!
Post by: Charles Pegge on September 08, 2007, 10:21:37 PM
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.
Title: Re: The GOTO Gotcha!
Post by: Kent Sarikaya on September 09, 2007, 06:10:50 AM
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.
Title: Re: The GOTO Gotcha!
Post by: Fred Buffington on September 14, 2007, 12:24:02 AM
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.

Title: Re: The GOTO Gotcha!
Post by: MikeTrader on September 14, 2007, 03:28:31 AM
>Is here anyone who never uses GOTO?

I try to eliminate all GOTO's

The main reason is that in a big program I might have ASM and Basic. In the ASM code there are lots of them so if your naming is not unique, you are going to have problems!

I use a lot of cut an paste ASM and have to rename most ofthe jmp/GOTO's
In basic code I make use of the DO/LOOP for ways to jump out - a technique I learned from Jose here. I have yet to encounter a situation that cannot be handled quite cleanly with an IF or DO/LOOP combo.

Personally i hate deeply nested IF statements. I try to code using the reverse logic

instead of
IF condition = %TRUE THEN

  ' code to execute
END IF

I prefer:

IF NOT Condition then EXIT DO/FUNCTION
' code to execute
'
'
'
'

This avoids code becoming so far indented you cant figure out what code is inside which Condition...


Title: Re: The GOTO Gotcha!
Post by: Donald Darden on September 14, 2007, 04:16:06 AM
Indenting is a very useful tool for showing the structure of code and making it easier to follow.  However, I typically limit my indentation step to 2 places, not the four or more that others use.  This means I can show more indented code than those others can.  I tried 1 step, but it's easier to follow 2 step increments vertically when you are looking for the beginning or end point of an indent stage.

I don't really care if people use GOTO or not.  I think most of the resistance to using it are either outmoded or without due consideration, which was my point in the first place.  To say that you also believe (like a religious thing) that the use of GOTO is either immoral or increases your risk of somehow reducing your program to a pile of cold spagetti code seems rather redundant.  The use of GOTOs is only an excuse for writing bad code; not a cause for it.

I might also point out that avoiding the explicit use of GOTO is to ignore that the compiler implements it everywhere, or at least the assembler equivalent.  For instance, every THEN, ELSE, ITERATE, EXIT, LOOP, and CASE statement has a goto structure involved.
Title: Re: The GOTO Gotcha!
Post by: José Roca on September 14, 2007, 04:50:57 AM
 
The DO/LOOP technique mentioned by Mike is useful to avoid multiple nested IFs.

For example, instead of:


   ' Create an instance of the HTTP service
   ppWHttp = WinHttpCreateObject("WinHttp.WinHttpRequest.5.1")
   IF ISTRUE ppWHttp THEN
      ' Open an HTTP connection to an HTTP resource
      IWinHttpRequest_Open(ppWHttp, "GET", "http://www.powerbasic.com/")
      IF ISFALSE WinHttpError THEN
         ' Send an HTTP request to the HTTP server
         IWinHttpRequest_Send ppWHttp
         IF ISFALSE WinHttpError THEN
            ' Get the response entity body as a string
            strResponseText = IWinHttpRequest_GetResponseText(ppWHttp)
            IF ISFALSE WinHttpError THEN
               MSGBOX strResponseText
            END IF
         END IF
      END IF
   END IF


I sometimes use:


   DO
      ' Create an instance of the HTTP service
      ppWHttp = WinHttpCreateObject("WinHttp.WinHttpRequest.5.1")
      IF ISFALSE ppWHttp THEN EXIT DO
      ' Open an HTTP connection to an HTTP resource
      IWinHttpRequest_Open(ppWHttp, "GET", "http://www.powerbasic.com/")
      IF WinHttpError THEN EXIT DO
      ' Send an HTTP request to the HTTP server
      IWinHttpRequest_Send ppWHttp
      IF WinHttpError THEN EXIT DO
      ' Get the response entity body as a string
      strResponseText = IWinHttpRequest_GetResponseText(ppWHttp)
      IF WinHttpError THEN EXIT DO
      MSGBOX strResponseText
      ' Exit
      EXIT DO
   LOOP


But I'm not afraid of GOTO, if judiciously used, so sometimes I use:


   ' Create an instance of the HTTP service
   ppWHttp = WinHttpCreateObject("WinHttp.WinHttpRequest.5.1")
   IF ISTRUE ppWHttp THEN GOTO LExit
   ' Open an HTTP connection to an HTTP resource
   IWinHttpRequest_Open(ppWHttp, "GET", "http://www.powerbasic.com/")
   IF ISTRUE ppWHttp THEN GOTO LExit
   ' Send an HTTP request to the HTTP server
   IWinHttpRequest_Send ppWHttp
   IF ISFALSE WinHttpError THEN GOTO LExit
   ' Get the response entity body as a string
   strResponseText = IWinHttpRequest_GetResponseText(ppWHttp)
   IF ISFALSE WinHttpError THEN GOTO LExit
   MSGBOX strResponseText

LExit:


And sometimes I use exception handling.

For some reason, some people always write clean code no matter which technique or language uses, whereas others don't.
Title: Re: The GOTO Gotcha!
Post by: Theo Gottwald on September 14, 2007, 10:00:38 AM
I do it like Jose.

The Reason is, that I want my Sub-Programs (as well as my programs) to have a defined "Exit-point".

If I have a lot of "EXIT SUB" everywhere in my code, this would not be the case.
Title: Re: The GOTO Gotcha!
Post by: Donald Darden on September 15, 2007, 10:42:54 PM
Ond style QBasic error trapping using ON ERROR GOTO caused a lot of people to write massive multi-error detection processes, and they even had to use ERL to determine which line number was involved when the error occurred.

VERY awkward.

I found it much better to either check for and process the error directly, or use the ON ERROR GOTO statement to jump to a unique process that only checked for an error that was related to the current task.

Another method was to use a variable to capture the error state if one occurred.  Thus, I would use ON ERROR GOTO to jump to the following statement:

goterror:
  errstat = ERR
  IF errstat = 0 THEN errstat = -1
  RESUME NEXT

Trying different methods can be useful, allowing you the chance to try and get a balance between common code and specific needs.  I noted that the particular compiler being used, and the manner in which it supported error trapping, was the most important determinant in which method worked best.  In fact, error processing can be one of the more difficult portions of code to convert when adapting source code from one BASIC compiler to another. It's been one of the areas in PowerBasic that has changed the most since the early versions.