Should I use GOTO in my BASIC programs ?

Started by kevin, January 22, 2007, 07:27:41 AM

Previous topic - Next topic

kevin





Quote ok, what is so bad about the goto command ?

    There's really nothing wrong with the command, it's more the misuse/over reliance that tends to produce code that is less than desirable.  Which is commonly referred to as spaghetti code.  

   Goto's purpose is an explicit change of control.  While it can be used to create loops, sub routines etc etc, there's really little point in emulating (via goto) the functionality of the language you're using.  As modern implementations of BASIC already offer these mechanisms (ie. Do/Loop/Exit  -Gosub/Return).    


.Example #1  Using Goto to emulate DO/Loop/Exit structures.

PlayBASIC Code: [Select]
     i=1000

Main:
if I<0 then Goto ExitMainLoop
i=i-1
Goto Main

ExitMainLoop:
print "End of program"
sync
waitkey




#2 Do/Loop/Exit  variation

PlayBASIC Code: [Select]
     i=1000
Do
if I<0 then Exit
i=i-1
loop

print "End of program"
sync
WaitKey



  Examples #2 and #1 demonstrate the same program flow using the either method.   The code loops a finite number of times  before exiting and continuing on with the rest of the program.    The main benefit of Do/loop option over goto's is we remove the need for the explicit LABELS.   Now since Labels are unique to a particular scope (Although this does vary between BASIC dialects).  Meaning we can easily clash label names, or worse, create logical errors in our programs, by jumping to the wrong label.

  To demo that,  Here's a variation of Example #1 with intentionally broken logic.

#1b  Faulty Logic

PlayBASIC Code: [Select]
     i=1000

MainLP1:
if I<0 then Goto ExitMainLoop1
i=i-1
Goto MainLP1

ExitMainLoop1:
print "End of Loop #1"

i=1000
MainLP2:
if I<0 then Goto ExitMainLoop1

i=i-1
Goto MainLP2

ExitMainLoop2:
print "End of Loop #2"
print "End of Program"
sync
waitkey



  If you look carefully, following the logic through,  you'll should notice a logic  error in the second loop.  Now when I  is lower than zero in the second loop, we want it to jump out of that loop to it's Exit label  ExitMainLoop2  and thus end the program.  But due to a typo or oversight on the programmers part, the exit label is pointing back to the previous exit point.  So the GOTO jumps back to ExitMainLp1 again.   This causes the program to fall into an infinite loop.   Which is not something the compiler can easily detect.  Its a human error and an all too common mistake



Example #2b  A Do/loop version.

PlayBASIC Code: [Select]
     i=1000

Do
if I<0 then exit
i=i-1
loop

print "End of Loop #1"

i=1000
do
if I<0 then exit
i=i-1
loop
print "End of Loop #2"
print "End of Program"
sync
waitkey



  In this variation of the example,  we're not only been able to remove a few lines/labels from the program, but we rely upon the integrity of the programs logic, to do as we intend.    This is made possible by how EXIT works,  as it's behavior is fixed.   That is to say,  it will always jump us out and to the bottom of a LOOP structure (DO/LOOP,  Repeat/Until - While/EndWhile - For / Next), so control is changed  to the  line following the closing loop statement.  (Which in this case, is the line after LOOP instructions)

Example #3  Using Goto to emulate Gosub/Return

PlayBASIC Code: [Select]
 result=1
i=16
Main:

if I<0 then Goto ExitMainLoop

; jump to our sub routine
goto MySubroutine1

; this Label is where our previous sub routine jumps back to
MainLoopLabel1:

i=i-1
Goto Main

ExitMainLoop:
print "End of program"
print result
sync
waitkey
END


MySubroutine1:
result=result+result
Goto MainLoopLabel1:






#4  Gosub  

PlayBASIC Code: [Select]
  Result=1
i=16
Main:
if I<0 then Goto ExitMainLoop

; jump to our sub routine
gosub MySubroutine1


i=i-1
Goto Main

ExitMainLoop:
print "End of program"
print result
sync
waitkey
END


MySubroutine1:
; Double the value of result
result=result+result
; Tell our program to restore control to after the calling gosub statement
return




 While GOTO / GOSUB might appear to have similar functionally,   GOTO is designed to change control where we don't wish to return to this point, while GOSUB is designed to change control to common piece of code, then upon reaching the RETURN instruction, control will revert back to the point of original call.
 Here's a cleaner version of Example #4.  Using Do/Loop and Gosubs

PlayBASIC Code: [Select]
 Result=1
i=16

; start of Do/loop structure
do

if I<0 then Exit

; jump to our sub routine
gosub MySubroutine1

i=i-1
loop

; end of program
print "End of program"
print result
sync
waitkey
END


MySubroutine1:
; Double the value of result
result=result+result
; Tell our program to restore control to after the calling gosub statement
return





 If you've made it this far, then I commend you. This is certainly a bit of a yawn fest. Anyway the previous example(s) could further simplified by using  FOR/NEXT or  Repeat/UNTIL  loop structures also.  Both of which are better fit for programs flow (logic) shown here.  So here's a version using those controls too.




Repeat Until verion

PlayBASIC Code: [Select]
 Result=1
i=16

; start of REPEAT/UNTIL structure
repeat

; jump to our sub routine
gosub MySubroutine1

i=i-1
until i<0

; end of program
print "End of program"
print result
sync
waitkey
END


MySubroutine1:
; Double the value of result
result=result+result
; Tell out program to restore control to after the calling gosub statement
return




For Next

PlayBASIC Code: [Select]
 Result=1

; start of For/NEXT structure
for i=0 to 16

; jump to our sub routine
gosub MySubroutine1

next i

; end of program
print "End of program"
print result
sync
waitkey
END


MySubroutine1:
; Double the value of result
result=result+result
; Tell out program to restore control to after the calling gosub statement
return









kevin

#1
Quote Never use GOTO to exit any kind of loop (and I mean any kind of loop for-next or otherwise)


  While this was true back in the day, as some older variants of BASIC  did use the stack to store the current loops starting address,  but today however, this is actually False.    

   I.e.

 
PlayBASIC Code: [Select]
Do
cls 0

for lp2=0 to 1000

For InnerLP=1 to 10
goto AvoidStackPop
next Innerlp
AvoidSTackPop:

inc Calls
next lp2

print "Number Of Goto Exit Calls:"+STR$(Calls)
Sync
loop




   I would like to point out that while GOTO is a legal way to exit a loop structure, use EXIT command, that's what it's there for  !  



 
QuoteI'm using Gosub - but my program dies after a while with a Stack OverFlow Error ?

   The Gosub control change allows us to call (change control to) common pieces of code, referred to sub routines.  Once a sub routine is complete, you're expected to use the Return command to restore program execution back to the point after the original gosub call.

  What actually happens is that when the GOSUB command is executed, the language pushes the Address (actually it's the position in memory of the next line of code)  to the STACK (an area where temp data is stored).   After the push, the language jumps to the new code section (the subroutine) in the program.  

  When our program reaches the RETURN command,  it does the opposite.  So this time, it Pop's the last value off the stack.  This value will be address of the next command after the original Gosub call.   Therefore, if you missed calling RETURN or after a gosub, then your program will inevitably die from overflowing the temporary stack area.   As the data is being pushed onto the stack, but never removed.  

 Here's an example of an unresolved Gosub.  This will most certainly cause a Stack overflow sooner or later !

PlayBASIC Code: [Select]
Do
cls 0

For lp=0 to 10
gosub StackOverFlow
StackOverFlow:
inc calls
next lp

print "Approx Stack Size:"+STR$(Calls*4)
Sync
loop









kevin

#2
QuoteHow can I EXIT from a nested loop structure ??

  EXIT as you most probably already know, only forces the program to abort the current loop structure.   So upon execution, control is passed to the next operation outside of the current loop.  But what if we wish to abort execution of more than one loop structure ?

   This is one of the short comings of EXIT command in most languages.   While some languages like PlayBasic for example have various versions of the EXIT mechanism for each loop structure, but . if ALL you have is EXIT then you have a few options.

  The First option comes from my Structured Programming days way back when I was in  high school (mid 1980's).   In those days the EXIT/BREAK weren't common place commands and loop structures were more often than not STACK based.  In this situation,  we'd simulate the EXIT control by either stepping the LOOP counter to Passed the end of loop (in FOR/NEXT 's), or setting the complete loop conditional flags (Repeat/ Until,  While / EndWhile) so the loop finished after this iteration.


Eg #1.  Breaking out of a For/NEXT without a EXIT or GOTO

PlayBASIC Code: [Select]
 For lp=0 to 10
; When lp ='s 5, we'll force the loop the terminate
if lp=5
; set the For Loop counter beyond the END of the declared range
Lp = 10+1
else
; if the lp is <> to 5 we print the loop counter
Print lp
endif
next

; Refresh the screen and wait for a key press
Sync
Waitkey





Eg #1b  This is the equivalent code above using the EXIT to break out of the loop.

PlayBASIC Code: [Select]
 ; Simplified the loop termination using EXIT.
For lp=0 to 10

; When lp ='s 5, we'll force the loop the terminate using exit
if lp=5 then EXIT

; if the lp is <> to 5 we print the loop counter
Print lp

Next

; Refresh the screen and wait for a key press
Sync
Waitkey





  The examples above are only single loops though,  so lets look at how we can break out of Nested For/NEXT loop  situations.  In the following example, the code fills a 2D array with random values,  then preforms a nested linear search upon it looking for the first occurrence of particular value.   Not a very impressive example, but it's something to use as our test frame work.

  First we'll use the setting the loop counters method to eject.  The main difference with the first example, is that in order to eject from both loops, we have force both loop counters beyond their End ranges.



 Eg #2.  Breaking out of  nested For/NEXT without a EXIT or GOTO

PlayBASIC Code: [Select]
 ; Set the Width + Height of our 2D Array
Width=10
Height=10
Dim Table(Width,Height)

; Fill the table Array with some random values,between zero and 20
For Ylp=0 to Height
Row$=""
For Xlp=0 to Width
Table(Xlp,Ylp)=rnd(20)
Row$=Row$+digits$(table(Xlp,ylp),2) +","
next
print trimright$(Row$,",")
next


; Search for the first Find How values in our test array are the
FindValue=15


; Flags to hold info about the first found item
FoundFlag=false
FoundAtX=0
FoundAtY=0

For Ylp=0 to Height

For Xlp=0 to Width

; check if this ccell is the array is our value
if Table(Xlp,Ylp)=FindValue

; Set out Found Flag to TRUE
FoundFlag=true

; remember the location it was within the array
FoundAtX = Xlp
FoundAtY = Ylp

; Force the inner loops counter to END by setting the Xlp counter beyond
; the loops range
Xlp=Width+1

; Force the outter loops counter to END by setting the Ylp counter beyond
; the loops range
Ylp=Height+1
endif

next Xlp
next Ylp


If FoundFlag=true
print "Found "+str$(FindValue)+" at "+str$(FoundAtX)+"x,"+str$(FoundAtY)+"y"
else
print "Value Not Found"
endif


; Refresh the screen and wait for a key press
Sync
Waitkey




 Pros:

  * Assuming there's no code following the inner loop, this method doesn't add any extra status checks to the outer loop.
  * Suitable in languages that use Stack base loop structures.
  * Keeps a One entry -> One Exit flow of your code.   Your teacher will love you :)


 Cons:
    * In order to eject, you have to know the direction and loop counter end boundary.



 Eg #2b.  Breaking out of  nested For/NEXT using EXIT.

 This is where Exit can actually be a little clumsy. As  if use EXIT to break out of the inner loop once a match is found, this will not stop the outer loops from executing.  So if we wish to purely use thr EXIT method, we're force to test our FoundFlag condition and EXIT the outer loop if that condition was met.        

PlayBASIC Code: [Select]
 ; Set the Width + Height of our 2D Array
Width=10
Height=10
Dim Table(Width,Height)

; Fill the table Array with some random values, between zero and 20
For Ylp=0 to Height
Row$=""
For Xlp=0 to Width
Table(Xlp,Ylp)=rnd(20)
Row$=Row$+digits$(table(Xlp,ylp),2) +","
next
print trimright$(Row$,",")
next


; Search for the first Find How values in our test array are the
FindValue=15


; Flags to hold info about the first found item
FoundFlag=false
FoundAtX=0
FoundAtY=0

For Ylp=0 to Height

For Xlp=0 to Width

; check if this ccell is the array is our value
if Table(Xlp,Ylp)=FindValue

; Set out Found Flag to TRUE
FoundFlag=true

; remember the location it was within the array
FoundAtX = Xlp
FoundAtY = Ylp

; exit this Xlp loop
exit
endif
next Xlp

; check if we've found our item, if so, exit this loop also
if FoundFlag then exit
next Ylp


If FoundFlag=true
print "Found "+str$(FindValue)+" at "+str$(FoundAtX)+"x,"+str$(FoundAtY)+"y"
else
print "Value Not Found"
endif


; Refresh the screen and wait for a key press
Sync
Waitkey



 Pros:

  * You don't need to know the loop direct/ or length in order to use this method
  * Suitable in languages that use Stack base loop structures.
  * Keeps a One entry -> One Entry line to your code.

 Cons:

    * Using EXIT to eject from both loops requires, trapping the inner and outter loop condition and exiting.  So it adds a state check to the Outer loop ion this case.   You can combine methods to avoid this however (Ie.  Exiting the inner loop and setting the outer loops counter beyond it's end point)
     
 Note:  PlayBASIC has a ExitFOR command purely for this purpose. The command allows you to exit from a user defined for/next level.   In this case, when a match is found, you'd simple Exit from the YLP using  ExitFor Ylp)  


 Eg #2c.  Breaking out of  nested For/NEXT loops using the dreaded GOTO.

  This version of the example uses GOTO to eject from the pair of loops once a match is found.  

PlayBASIC Code: [Select]
 ; Set the Width + Height of our 2D Array
Width=10
Height=10
Dim Table(Width,Height)

; Fill the table Array with some random values,between zero and 20
For Ylp=0 to Height
Row$=""
For Xlp=0 to Width
Table(Xlp,Ylp)=rnd(20)
Row$=Row$+digits$(table(Xlp,ylp),2) +","
next
print trimright$(Row$,",")
next


; Search for the first Find How values in our test array are the
FindValue=15


; Flags to hold info about the first found item
FoundFlag=false
FoundAtX=0
FoundAtY=0

For Ylp=0 to Height

For Xlp=0 to Width

; check if this ccell is the array is our value
if Table(Xlp,Ylp)=FindValue

; Set out Found Flag to TRUE
FoundFlag=true

; remember the location it was within the array
FoundAtX = Xlp
FoundAtY = Ylp

; jump out of both loops and continue on.
goto FoundItemContinue
endif

next Xlp
next Ylp

FoundItemContinue:


If FoundFlag=true
print "Found "+str$(FindValue)+" at "+str$(FoundAtX)+"x,"+str$(FoundAtY)+"y"
else
print "Value Not Found"
endif


; Refresh the screen and wait for a key press
Sync
Waitkey





 Pros:

   * You don't need to know the loop direction or length in order to use this method
   * Does NOT Keep a One entry -> One Entry flow to you code.   Your teacher will fail you ! :)
   * Allows immediate ejection of any number of nested loop types

 Cons:

   * Not Suitable in languages that use Stack base loop structures.

   * Requires a Hard code label as the ejection point.  Which if your not careful can lead to logic errors.
     

  In terms of performance, there really isn't going to be any significant difference.  It's more a question a style, which every body has a different opinion about ! :)






   

kevin

#3
How NOT to use GOTO


 While on the subject of GOTO..   The following is one of my favorite Bad examples of  a 'goto at all costs' design.   I don't recall who sent it, or the program per verbatim for that matter, but the design used a kind of marker system to select the jump back point when calling subroutines via goto.   Here I've simulated this with a DrawCircle routine.  If you follow it through you'll see that upon conclusion of DrawCircle routine, we're using a select/case to direct control back to the main loop..   NASTY

Eg 1a

PlayBASIC Code: [Select]
 ;  Here's an example of how you should ___NOT____ use GOTO..


MainLoop:
Cls rgb(0,0,0)


ReturnPoint=1

Xpos=mouseX()
Ypos =Mousey()
ink rgb(255,0,0)
Goto DrawCircle


MainLoop1:

ReturnPoint=2

Circle1X =(Circle1X+1) and 255
Circle1Y =(Circle1Y+1) and 255

Xpos=Circle1X
Ypos=Circle1Y
ink rgb(0,255,0)
Goto DrawCircle


MainLoop2:


ReturnPoint=3
While Circle2SpeedX=0
Circle2SpeedX =rndrange(-5,5)
endwhile

While Circle2SpeedY=0
Circle2SpeedY =rndrange(-5,5)
endwhile

if Circle2Init=false
circle2X=rnd(800)
circle2Y=rnd(800)
Circle2Init=true
endif

Circle2X =Circle2X+Circle2SpeedX
Circle2Y =Circle2Y+Circle2SpeedY

if POintInBox(Circle2X,circle2y,0,0,800,600)=false
Circle2init=0
Circle2SpeedX=0
Circle2SpeedY=0
endif

Xpos=Circle2X
Ypos=Circle2Y
ink rgb(0,0,255)
Goto DrawCircle


MainLoop3:



Sync
Goto Mainloop




DrawCircle:

c=getink()
Circle Xpos,Ypos,50,true
Circlec Xpos,Ypos,25,true,rgbfade(C,50)


; Handle Return
select ReturnPOint
case 1 : Goto MainLoop1
case 2 : Goto MainLoop2
case 3 : Goto MainLoop3
endselect

; unknown return point.. so we error
Cls rgb(255,0,0)
print "Call Back Failed"
sync
waitkey
end







 Obviously the flow of the program outlined above can be recreated using more appropriate constructs  (Gosub/Return,  Functions).  

Eg 1b.

PlayBASIC Code: [Select]
 ;  Here's a cleaner version using using subroutines via GOSUB/RETURN


Do
Cls rgb(0,0,0)


; Mouse Control circle 1 ---------------------------

Xpos=mouseX()
Ypos =Mousey()
ink rgb(255,0,0)
Gosub DrawCircle


; Control circle 2 ---------------------------------

Circle1X =(Circle1X+1) and 255
Circle1Y =(Circle1Y+1) and 255

Xpos=Circle1X
Ypos=Circle1Y
ink rgb(0,255,0)
gosub DrawCircle



; Control circle #3 -------------------------------

While Circle2SpeedX=0
Circle2SpeedX =rndrange(-5,5)
endwhile
While Circle2SpeedY=0
Circle2SpeedY =rndrange(-5,5)
endwhile
if Circle2Init=false
circle2X=rnd(800)
circle2Y=rnd(800)
Circle2Init=true
endif

Circle2X =Circle2X+Circle2SpeedX
Circle2Y =Circle2Y+Circle2SpeedY

if POintInBox(Circle2X,circle2y,0,0,800,600)=false
Circle2init=0
Circle2SpeedX=0
Circle2SpeedY=0
endif

Xpos=Circle2X
Ypos=Circle2Y
ink rgb(0,0,255)
Gosub DrawCircle


Sync
loop




DrawCircle:

c=getink()
Circle Xpos,Ypos,50,true
Circlec Xpos,Ypos,25,true,rgbfade(C,50)

return







Eg 1c.   (Function Version)

PlayBASIC Code: [Select]
 ;  Here's another version using FUNCTIONS.. As you can see it's cleaner again


Do
Cls rgb(0,0,0)


; Mouse Control circle 1 ---------------------------

DrawCircle(MouseX(),MouseY(),Rgb(255,0,0))


; Control circle 2 ---------------------------------

Circle1X =(Circle1X+1) and 255
Circle1Y =(Circle1Y+1) and 255

DrawCircle(Circle1X,Circle1Y,Rgb(0,255,0))




; Control circle #3 -------------------------------

While Circle2SpeedX=0
Circle2SpeedX =rndrange(-5,5)
endwhile
While Circle2SpeedY=0
Circle2SpeedY =rndrange(-5,5)
endwhile
if Circle2Init=false
circle2X=rnd(800)
circle2Y=rnd(800)
Circle2Init=true
endif

Circle2X =Circle2X+Circle2SpeedX
Circle2Y =Circle2Y+Circle2SpeedY

if POintInBox(Circle2X,circle2y,0,0,800,600)=false
Circle2init=0
Circle2SpeedX=0
Circle2SpeedY=0
endif

DrawCircle(Circle2X,Circle2Y,Rgb(0,0,255))


Sync
loop




Function DrawCircle(xpos,Ypos,ThisColour)
Circlec Xpos,Ypos,50,true,ThisColour
Circlec Xpos,Ypos,25,true,rgbfade(ThisColour,50)
EndFunction







Articles On The Dreaded GOTO

    Goto is one of those commands that invokes such passionate  responses from people (pro & con).   I often find stemming from where/whom the person learned programming from.   When I was high school in the 80's,  one our computer science teachers required everything streamlined to be one entry->one exit.   A friend of mine took great pleasure in getting into such debates with him.  But anyway..  The following article causes a bit of stir when it written.      

  Donald E. Knuth (Structured programming with go to statements (1974)
  http://en.wikipedia.org/wiki/Goto