Anti-Goto Sentiment Considered Harmful
Root / Site Discussion / [.]
well i must admit that your turn to my statement brought a laugh out of me but i do seem to realize that its not about having a brain, its about progressing at your own pace. if dev 234 wanted to make his game with a goto loop, it was his choice. i do notice you mentioned a gosub for animation? i do find that kind of outrageous and for that i share your point of view. i manage my sprite animations with a def for 4 directions and a chunk of code before the button imputs. i dont use spanim, at least not yet, but i never used goto for sprite animations.My point is that it's so easy to learn loops that you shouldn't consciously stay with label loops.
It is important to write clean code. But i find that there are some who become obsessed with it. To the point where anyone using a keyword for example GOTO is a complete noob and their code is horrible becuase of using this.
In truth, i like GOTO’s because they are extremely simple. They are perfect for your prototype code which requires a simple design so that you can get the bigger picture of how your code works.
Most importantly, i believe that every person has their own way of programming and no one should force their way on others. Let the beginners experience programming and learn things for themselves. That is how they will learn best.
I am sorry, but using GOTO in your code when you don’t have to, IS a very loud and large klaxon ringing out that you are indeed a noob. That you need to go back and learn the basics. That you don’t know what you are doing, and that your code is automatically suspect (even if it works).
I warned everyone earlier in this thread I would could make a big giant document about why GOTO is bad. Well this thread has gone on long enough, so hold on to your butts, it is a long one. Here we go. If the site wants to copy and paste this into one of those nifty pages, feel free. If I have the ability to do so myself, please let me know what links to click to do so.
GOTO isn’t inherently evil in and of itself. It is nicely named and does what it says on the box. Such clarity can be nice to someone just starting out.
That being said there are only a few situations where you would want to actually use it, or consider using GOTO to be OK.
- You are coding in Assembly Language. At that point you simply don’t have the luxury of higher level constructs.
- You are coding on an old retro system or language that does not support needed looping constructs. For example: BASIC on the Commodore 64.
- A higher level language is compiling down to an intermediate form that is not meant to be human readable or editable. For example code generated by LEX/YACC.
- You are in a low level language like C and need to escape from a nested loop several levels deep. (You may need to defend use in a code review for this one.)
- Everything else, use a function or loop instead.
- Item 1
- GOTO use is defended as a lifestyle choice
- You are too lazy to learn available looping/function constructs in the language.
- You maliciously teach it to new programmers as though it was a good thing to use.
I = 0 @LABEL_START: IF I >= 10 THEN GOTO @LABEL_EXIT PRINT I I = I + 1 GOTO @LABEL_START @LABEL_EXIT:You should use a FOR loop. (That sure looks more readable to me.)
I = 0 FOR I = 0 TO 9 PRINT I NEXT IIf your code looks like this:
I = 0 J = 10 @LABEL_START: IF I == J THEN GOTO @LABEL_EXIT PRINT I I = I + 1 GOTO @LABEL_START @LABEL_EXIT:Try a WHILE loop.
I = 0 J = 10 WHILE I != J PRINT I I = I + 1 WENDIf your code looks like this.
I = 0 @LABEL_START: PRINT I I = I + 1 IF I < 10 THEN GOTO @LABEL_STARTTry a REPEAT loop.
I = 0 REPEAT PRINT I I = I + 1 UNTIL I >= 10If you code looks like this
A = 1 B = 2 GOSUB @ADD_TWO PRINT C END @ADD_TWO: C = A + B RETURNTry a function
PRINT ADD_TWO(1, 2) END DEF ADD_TWO(A, B) RETURN A + B ENDAnd if your code looks like this.
THE_NAME$ = “BOBBY” GOSUB @PRINT_NAME END @PRINT_NAME PRINT THE_NAME$ RETURNTry a subroutine. Note the lack of parenthesis on the parameters and no return value.
PRINT_NAME “BOBBY” END DEF PRINT_NAME THE_NAME$ PRINT THE_NAME$ ENDI find it hard to see any of the GOTO statement examples as easier to read or write. But there is more to it than that. After all, I use % on the end of my numeric variables, and that is uglier AND harder to write. Problems with GOTO: Tons of Line Labels are needed when you use GOTO/GOSUB. A for loop requires no line labels. You might need one or two when using GOTO. Making line labels requires that they are all distinct. You can easily run out of useful line label names and having them peppered through the code makes things harder to read. If you want to reuse code in a different file, you now need to make sure that the line labels are still unique. This is extra overhead you don’t get with looping and function call syntax. Everything is at the global scope if you are using GOTO/GOSUB. Inside of a function/subroutine, you can make variables with the same name as variables at the global level. If you want to have ten different functions that have a variable called “I” that is used as a looping index you can do it. When you reference a variable it looks first at the current scope, and if it finds it uses it. Otherwise it looks through parent scopes until it either finds one or hits the global scope. Likewise the parameters passed in don’t have to be the same name as they were at the global scope. They can be whatever you want to name them and it won’t affect code outside of the function/subroutine. So, how does scoping help? Because, now you can re-use code. If you want to take the function out of one file and put it in another you can do it without affecting anything else (assuming you aren’t accessing globals). If you wanted to try this with code that uses GOTO/GOSUB, you will have far more problems. First you have to also copy over any variables that are used as input or output. If they are already present and used by something else you are going to need to rename things in your code. You may need to rename line labels too. Since everything would be at the global scope one mistake could mean that one GOSUB call is messing up a variable used by another GOSUB call. That would be a miserable problem to find and fix. Functions can be recursive, you have to fake it with a stack if you are using GOSUB. Yes recursion is a bit of an advanced topic for new programmers, but it is still worth noting. With recursion, a function can call itself, and use the results. Each call gets its own stack frame that holds the variables for that specific call of the function. So calling a function from itself won’t break the state of the parent call. A simple example is probably factorial (yes you could easily do this in a loop, it is a simple example). If you tried doing that with GOSUB, you would run into problems unless you make your own stack manually. If you didn’t make your own stack manually, one call would stomp over top of its own variables.
DEF FACTORIAL(X) IF X <= 1 THEN RETURN 1 ELSE RETURN X * FACTORIAL(X – 1) ENDIF ENDThere are a whole host of algorithms that lend themselves nicely to recursion, and not being able to do it easily with GOTO/GOSUB is a problem, and a lot more work. GOTO/GOSUB calls can branch to literally anywhere in the code. This is really the be number one problem. If you are in a FOR, WHILE, or REPEAT loop you have well defined areas where the program can jump to. You can only jump to the top of the loop or exit the loop entirely. Even with things like CONTINUE and BREAK (which I am fine with by the way) are limited to those two points. Likewise for functions and subroutines. They have well defined entry and exit points. However, if you are using GOTO/GOSUB, you can jump to any line of code in the whole program. This property lends to the dreaded creation of “Spaghetti Code” where it is difficult to trace where code came from, and where it is going. This is an uncertainty you just don’t have with a loop, because it encourages cleaner coding. Spaghetti code leads to write-once code that can be near impossible to maintain. In a real programming environment you spend 80% of your coding time on code maintenance. Do yourself a favor and don’t make your job harder. Spaghetti code can easily crush a new programmer just getting started. You shouldn’t teach GOTO because you are making life hard for them, they will quickly burn out and can give up entirely. This isn't matter of personal style, it is a matter of maintainability and readability. GOTO doesn't scale well with code length. To make my point, below is a maze generation program from the book “Basic Computer Games” compiled by David Ahl back when computers were still new (https://www.atariarchives.org/basicgames/showpage.php?page=3). It makes extensive use of GOTO. Here are some questions about the code: Q: What algorithm is it using to generate the maze? Q: Does GOTO make the code easier to read or harder to read and understand? Q: If you wanted to modify the program how comfortable would you be trying to modify it? Or would you rather start over from scratch?
10 PRINT TAB(28);"AMAZING PROGRAM" 20 PRINT TAB(15);"CREATIVE COMPUTING MORRISTOWN, NEW JERSEY" 30 PRINT:PRINT:PRINT:PRINT 100 INPUT "WHAT ARE YOUR WIDTH AND LENGTH";H,V 102 IF H<>1 AND V<>1 THEN 110 104 PRINT "MEANINGLESS DIMENSIONS. TRY AGAIN.":GOTO 100 110 DIM W(H,V),V(H,V) 120 PRINT 130 PRINT 140 PRINT 150 PRINT 160 Q=0:Z=0:X=INT(RND(1)*H+1) 165 FOR I=1 TO H 170 IF I=X THEN 173 171 PRINT ".--";:GOTO 180 173 PRINT ". "; 180 NEXT I 190 PRINT "." 195 C=1:W(X,1)=C:C=C+1 200 R=X:S=1:GOTO 260 210 IF R<>H THEN 240 215 IF S<>V THEN 230 220 R=1:S=1:GOTO 250 230 R=1:S=S+1:GOTO 250 240 R=R+1 250 IF W(R,S)=0 THEN 210 260 IF R-1=0 THEN 530 265 IF W(R-1,S)<>0 THEN 530 270 IF S-1=0 THEN 390 280 IF W(R,S-1)<>0 THEN 390 290 IF R=H THEN 330 300 IF W(R+1,S)<>0 THEN 330 310 X=INT(RND(1)*3+1) 320 ON X GOTO 790,820,860 330 IF S<>V THEN 340 334 IF Z=1 THEN 370 338 Q=1:GOTO 350 340 IF W(R,S+1)<>0 THEN 370 350 X=INT(RND(1)*3+1) 360 ON X GOTO 790,820,910 370 X=INT(RND(1)*2+1) 380 ON X GOTO 790,820 390 IF R=H THEN 470 400 IF W(R+1,S)<>0 THEN 470 405 IF S<>V THEN 420 410 IF Z=1 THEN 450 415 Q=1:GOTO 430 420 IF W(R,S+1)<>0 THEN 450 430 X=INT(RND(1)*3+1) 440 ON X GOTO 790,860,910 450 X=INT(RND(1)*2+1) 460 ON X GOTO 790,860 470 IF S<>V THEN 490 480 IF Z=1 THEN 520 485 Q=1:GOTO 500 490 IF W(R,S+1)<>0 THEN 520 500 X=INT(RND(1)*2+1) 510 ON X GOTO 790,910 520 GOTO 790 530 IF S-1=0 THEN 670 540 IF W(R,S-1)<>0 THEN 670 545 IF R=H THEN 610 547 IF W(R+1,S)<>0 THEN 610 550 IF S<>V THEN 560 552 IF Z=1 THEN 590 554 Q=1:GOTO 570 560 IF W(R,S+1)<>0 THEN 590 570 X=INT(RND(1)*3+1) 580 ON X GOTO 820,860,910 590 X=INT(RND(1)*2+1) 600 ON X GOTO 820,860 610 IF S<>V THEN 630 620 IF Z=1 THEN 660 625 Q=1:GOTO 640 630 IF W(R,S+1)<>0 THEN 660 640 X=INT(RND(1)*2+1) 650 ON X GOTO 820,910 660 GOTO 820 670 IF R=H THEN 740 680 IF W(R+1,S)<>0 THEN 740 685 IF S<>V THEN 700 690 IF Z=1 THEN 730 695 Q=1:GOTO 830 700 IF W(R,S+1)<>0 THEN 730 710 X=INT(RND(1)*2+1) 720 ON X GOTO 860,910 730 GOTO 860 740 IF S<>V THEN 760 750 IF Z=1 THEN 780 755 Q=1:GOTO 770 760 IF W(R,S+1)<>0 THEN 780 770 GOTO 910 780 GOTO 1000 790 W(R-1,S)=C 800 C=C+1:V(R-1,S)=2:R=R-1 810 IF C=H*V+1 THEN 1010 815 Q=0:GOTO 260 820 W(R,S-1)=C 830 C=C+1 840 V(R,S-1)=1:S=S-1:IF C=H*V+1 THEN 1010 850 Q=0:GOTO 260 860 W(R+1,S)=C 870 C=C+1:IF V(R,S)=0 THEN 880 875 V(R,S)=3:GOTO 890 880 V(R,S)=2 890 R=R+1 900 IF C=H*V+1 THEN 1010 905 GOTO 530 910 IF Q=1 THEN 960 920 W(R,S+1)=C:C=C+1:IF V(R,S)=0 THEN 940 930 V(R,S)=3:GOTO 950 940 V(R,S)=1 950 S=S+1:IF C=H*V+1 THEN 1010 955 GOTO 260 960 Z=1 970 IF V(R,S)=0 THEN 980 975 V(R,S)=3:Q=0:GOTO 1000 980 V(R,S)=1:Q=0:R=1:S=1:GOTO 250 1000 GOTO 210 1010 FOR J=1 TO V 1011 PRINT "I"; 1012 FOR I=1 TO H 1013 IF V(I,J)<2 THEN 1030 1020 PRINT " "; 1021 GOTO 1040 1030 PRINT " I"; 1040 NEXT I 1041 PRINT 1043 FOR I=1 TO H 1045 IF V(I,J)=0 THEN 1060 1050 IF V(I,J)=2 THEN 1060 1051 PRINT ": "; 1052 GOTO 1070 1060 PRINT ":--"; 1070 NEXT I 1071 PRINT "." 1072 NEXT J 1073 ENDAnd here is my implementation of a maze generation program in SmileBasic. It is has a game loop added and is longer, but I still say it is much easier to read, modify, and learn from than the first one. Would you rather modify my code, or the one with GOTOs in it? Or do you have trouble with both? (yes, I know everyone hates the %’s on variable names, I just like specifying my data types when possible). If you skip the game loop, they are actually about the same length (that would be the line PRINT_MAZE starts on). (I manually typed this up, there may be a few bugs because of that.)
OPTION STRICT VAR LEVEL% FOR LEVEL% = 1 TO 3 VAR WIDTH% = 49 VAR HEIGHT% = 27 DIM MAZE%[0, 0] MAZE% = GENERATE_MAZE(WIDTH%, HEIGHT%) VAR START_X%, START_Y% VAR EXIT_X%, EXIT_Y% START_Y% = 1 REPEAT START_X% = RND(WIDTH%) UNTIL MAZE%[START_X%, START_Y%] == 0 EXIT_Y% = HEIGHT% - 2 REPEAT EXIT_X% = RND(WIDTH%) UNTIL MAZE%[EXIT_X%, EXIT_Y%] == 0 PRINT_MAZE% MAZE%, WIDTH%, HEIGHT%, START_X%, START_Y%, EXIT_X%, EXIT_Y% VAR BTN% REPEAT BTN% = BUTTON() IF BTN% AND #UP THEN IF START_Y% >= 1 AND MAZE%[START_X%, START_Y% - 1] == 0 THEN MOVE_GUY START_X%, START_Y%, START_X%, START_Y% - 1 START_Y% = START_Y% - 1 ELSE BEEP 23 ENDIF ELSEIF BTN% AND #DOWN THEN IF START_Y% < HEIGHT% - 1 AND MAZE%[START_X%, START_Y% + 1] == 0 THEN MOVE_GUY START_X%, START_Y%, START_X%, START_Y% + 1 START_Y% = START_Y% + 1 ELSE BEEP 23 ENDIF ELSEIF BTN% AND #LEFT THEN IF START_X% >= 1 AND MAZE%[START_X% - 1, START_Y%] == 0 THEN MOVE_GUY START_X%, START_Y%, START_X% - 1, START_Y% START_X% = START_X% - 1 ELSE BEEP 23 ENDIF ELSEIF BTN% AND #RIGHT THEN IF START_X% < WIDTH% - 1 AND MAZE%[START_X% + 1, START_Y%] == 0 THEN MOVE_GUY START_X%, START_Y%, START_X% + 1, START_Y% START_X% = START_X% + 1 ELSE BEEP 23 ENDIF ENDIF UNTIL START_X% == EXIT_X% AND START_Y == EXIT_Y% NEXT LEVEL% CLS PRINT "YOU WIN" END DEF MOVE_GUY OLD_X%, OLD_Y%, NEW_X%, NEW_Y% COLOR #TWHITE, #TBLACK LOCATE OLD_X%, OLD_Y% + 1 PRINT " "; COLOR #TCYAN, #TBLACK LOCATE NEW_X%, NEW_Y% + 1 PRINT "O"; COLOR #TWHITE, #TBLACK END DEF PRINT_MAZE MAZE%, WIDTH%, HEIGHT%, START_X%, START_Y%, EXIT_X%, EXIT_Y% VAR X% VAR Y% LOCATE 0, 0 COLOR #TWHITE, #TBLACK PRINT "LEVEL: " + STR$(LEVEL%) ‘PRINT THE MAZE OUT FOR Y% = 0 TO HEIGHT% - 1 FOR X% = 0 TO WIDTH% - 1 IF MAZE%[X%, Y%] == 0 THEN IF X% == START_X% AND Y% == START_Y% THEN COLOR #TCYAN PRINT "O"; ELSEIF X% == EXIT_X% AND Y% == EXIT_Y% THEN COLOR #TYELLOW PRINT "X"; ELSE PRINT " "; ENDIF ELSE COLOR #TRED PRINT "M"; ENDIF NEXT X% PRINT NEXT Y% COLOR #TWHITE, #TBLACK END DEF GENERATE_MAZE(WIDTH%, HEIGHT%) VAR H% VAR W% VAR CELL_W% VAR CELL_H% VAR STACK_X%[0] VAR STACK_Y%[0] DIM VISIT%[4] VAR X%, Y% VAR START_X%, START_Y% VAR DIR% VAR I%, J% VAR UNVISITED% CELL_W% = CEIL((WIDTH% - 1) / 2) CELL_H% = CEIL((HEIGHT% - 1) / 2) W% = (CELL_W% * 2) + 1 H% = (CELL_H% * 2) + 1 DIM MAZE%[W%, H%] FILL MAZE%, 1 Y% = 0 X% = RND(CELL_W%) START_X% = X% * 2 + 1 START_Y% = Y% * 2 + 1 UNVISITED% = CELL_W% * CELL_H% MAZE%[X% * 2 + 1, Y% * 2 + 1] = 0 UNVISITED% = UNVISITED% - 1 WHILE UNVISITED% > 0 DIR% = RND(4) VISIT%[0] = IS_VISITED(MAZE%, X%, Y% - 1, CELL_W%, CELL_H%) VISIT%[1] = IS_VISITED(MAZE%, X% - 1, Y%, CELL_W%, CELL_H%) VISIT%[2] = IS_VISITED(MAZE%, X% + 1, Y%, CELL_W%, CELL_H%) VISIT%[3] = IS_VISITED(MAZE%, X%, Y% + 1, CELL_W%, CELL_H%) IF VISIT%[0] + VISIT%[1] + VISIT%[2] + VISIT%[3] == 0 THEN ‘ALL NEIGHBORS VISITED BACK TRACK IF LEN(STACK_X%) > 0 THEN X% = POP(STACK_X%) Y% = POP(STACK_Y%) ELSE BREAK ENDIF ELSE ‘PICK UNVISITED NEIGHBOR WHILE VISIT%[DIR%] == 0 DIR% = (DIR% + 1) MOD 4 WEND PUSH STACK_X%, X% PUSH STACK_Y%, Y% ‘REMOVE WALL IF DIR% == 0 THEN ‘NORTH MAZE%[X% * 2 + 1, Y% * 2] = 0 Y% = Y% - 1 ELSEIF DIR% == 1 THEN ‘WEST MAZE%[X% * 2, Y% * 2 + 1] = 0 X% = X% - 1 ELSEIF DIR% == 2 THEN ‘EAST MAZE%[X% * 2 + 2, Y% * 2 + 1] = 0 X% = X% + 1 ELSE ‘SOUTH MAZE%[X% * 2 + 1, Y% * 2 + 2] = 0 Y% = Y% + 1 ENDIF MAZE[X% * 2 + 1, Y% * 2 + 1] = 0 UNVISITED% = UNVISITED% - 1 ENDIF WEND RETURN MAZE% END DEF IS_VISITED(MAZE%, X%, Y%, WIDTH%, HEIGHT%) VAR Z% IF X% >= 0 AND X% < WIDTH% AND Y% >= 0 AND Y% < HEIGHT% THEN RETURN MAZE%[X% * 2 + 1, Y% * 2 + 1] ELSE RETURN 0 ENDIF ENDLong ago, I was actually going to port the first version to SmileBasic and got fed up untangling the spaghetti code. So instead I looked up maze generation on Wikipedia and made my own. So in my case, I would have rather started over than try to make sense of the old code. Unmaintainable code may run but it isn’t very valuable. I still don’t know what algorithm the first one was using. I assume it is something different than what I chose. Here is the pseudo code I worked from. Maze generation algorithm from Wikipedia Recursive backtracker The depth-first search algorithm of maze generation is frequently implemented using backtracking:
- 1. Make the initial cell the current cell and mark it as visited
- 2. While there are unvisited cells
- 1. If the current cell has any neighbours which have not been visited
- 1. Choose randomly one of the unvisited neighbours
- 2. Push the current cell to the stack
- 3. Remove the wall between the current cell and the chosen cell
- 4. Make the chosen cell the current cell and mark it as visited
- 2. Else if stack is not empty
- 1. Pop a cell from the stack
- 2. Make it the current cell
- 1. If the current cell has any neighbours which have not been visited
I am sorry, but using GOTO in your code when you don’t have to, IS a very loud and large klaxon ringing out that you are indeed a noob. That you need to go back and learn the basics. That you don’t know what you are doing, and that your code is automatically suspect (even if it works). I warned everyone earlier in this thread I would could make a big giant document about why GOTO is bad. Well this thread has gone on long enough, so hold on to your butts, it is a long one. Here we go. If the site wants to copy and paste this into one of those nifty pages, feel free. If I have the ability to do so myself, please let me know what links to click to do so. GOTO isn’t inherently evil in and of itself. It is nicely named and does what it says on the box. Such clarity can be nice to someone just starting out. That being said there are only a few situations where you would want to actually use it, or consider using GOTO to be OK.Holy heck that was long. Don't worry, I cut most of it out in the quote. Anyway, just one thing you forgot in the acceptable section: You are a beginner and know only GOTO loops. Sure, beginners should learn the loops that were meant to be loops, but that doesn't mean tell them they shouldn't use GOTO. They don't know what to use instead. Also, don't tell them to use while/repeat loops instead and nothing else. They probably won't know how to use those. Direct them to a thingumajig (that was in my dictionary for some reason) that tells them how to use while/repeat/for loops. So let them find out for themselves instead of telling them their code is crappy. Sure, you're trying to use constructive critisizm, but critisizm will discourage them and they won't think they're good at programming. Tell them they are doing good, but that they can improve. Or, don't tell them not to use a code.Times when GOTO use is not acceptable.
- You are coding in Assembly Language. At that point you simply don’t have the luxury of higher level constructs.
- You are coding on an old retro system or language that does not support needed looping constructs. For example: BASIC on the Commodore 64.
- A higher level language is compiling down to an intermediate form that is not meant to be human readable or editable. For example code generated by LEX/YACC.
- You are in a low level language like C and need to escape from a nested loop several levels deep. (You may need to defend use in a code review for this one.)
Times when GOTO use is abhorent.
- Everything else, use a function or loop instead.
- Item 1
- GOTO use is defended as a lifestyle choice
- You are too lazy to learn available looping/function constructs in the language.
- You maliciously teach it to new programmers as though it was a good thing to use.
Let’s just agree on yes, it’s inevitable that beginners will find their way to GOTO (probably through one of the YouTube tutorials they search for). If the beginner is willing to learn deeper into SB then they will find FOR and WHILE, if not they’re’nt going to find those features then they probably wouldn’t’ve made it far into the concepts of programming and probably lost interest in it as a whole and that’s ok. The whole “GOTO is an evil virus of satan” deal shouldn’t be brought up to beginners because it is up to them to search for a better and less tedious way of doing things, and they don’t find it, simply tell them there’s a better method and they should go find it.
Let’s just agree on yes, it’s inevitable that beginners will find their way to GOTO (probably through one of the YouTube tutorials they search for). If the beginner is willing to learn deeper into SB then they will find FOR and WHILE, if not they’re’nt going to find those features then they probably wouldn’t’ve made it far into the concepts of programming and probably lost interest in it as a whole and that’s ok. The whole “GOTO is an evil virus of satan” deal shouldn’t be brought up to beginners because it is up to them to search for a better and less tedious way of doing things, and they don’t find it, simply tell them there’s a better method and they should go find it.Yes.