Sorry your browser is not supported!

You are using an outdated browser that does not support modern web technologies, in order to use this site please update to a new browser.

Browsers supported include Chrome, FireFox, Safari, Opera, Internet Explorer 10+ or Microsoft Edge.

Newcomers DBPro Corner / Why You Should Avoid Goto And How To Do It

Author
Message
TDK
Retired Moderator
21
Years of Service
User Offline
Joined: 19th Nov 2002
Location: UK
Posted: 28th Dec 2006 14:05 Edited at: 30th Apr 2010 15:57
How To Avoid The Goto Command

Before we start, please let me say that this is not a YOU MUST DO THIS tutorial. After reading this, I want you to be saying to yourself "you know, he's right - I have to do this with my own programs".

I've been programming computers over 35 years using a number of programming languages (including Z80 & 68000 ASM, Forth, Pascal, C and C++). When I first used BASIC, it had line numbers and the only control you had over program flow was GOTO LineNumber. There were no labels, subroutines or functions so Goto was all you had to work with.

These days, subroutines (also known as procedures) and functions totally eliminate the need to use Goto at all. You just need to know how to correctly start off writing your program to avoid using it.

If you still feel the need to use Goto in your programs after reading this tutorial, fine - go ahead. It's your life, your program and your time you are wasting. Yes, your programs may possibly work, but it will take you much, much longer to add to them and track down/fix errors.

And finally, just to make it clear, this tutorial is aimed at newcomers to programming - not experienced coders who know what they are doing and understand the dangers of using Goto.

Besides that, old 'I hate Goto' die-hards like me simply won't respond to your questions on the TGC forums because your programs will simply be too difficult to follow if they use Goto to jump all over the place.

OK, let's get started...


Program Flow Control

To understand best the reasons why the use of Goto is not recommended (or actually necessary), you need to know what's going on when you run your Dark Basic programs.

Essentially, your programs are 'linear' in execution. Take the following small code snippet:



When a computer program is running, something called the 'Program Counter' (from hereon referred to as the PC) keeps track of the current position within the program. It's just a pointer so the program know which instruction it's carrying out and what instruction is next.

When you hit F5 in Dark Basic, the PC points to the very first line and carries out the instructions it finds there. When it's done this, it increments the PC to point to the next line, and carries out the instructions it finds there.

This continues in a linear fashion until there are no more lines to execute and the program simply ends.

So, the linear program flow is: first line, all subsequent lines and after the last line, the program ends. You can't get any linear than that can you!

But, your program just ending after it has done something isn't very user-friendly, so we need to find a way to keep it running.

Here's where loops come in.



This program is still linear - carrying out instructions one at a time in order and starting with the first line, but the addition of a Do...Loop has altered the Program Flow.

This time, when you press F5, the PC starts at the first line, but does things slightly differently from thereon.

The first line is now 'Do' and that triggers the PC to store it's current line position on something called the 'stack'.


The Stack

Think of the stack as a pile of small pieces of paper on a table. When your program runs, the stack is empty so there are no pieces of paper on the table. When required, your computer can write a number on a piece of paper and put it onto the stack, or take a piece of paper from the stack and use the number on it.

First off, the stack is LIFO - Last In, First Out - so there are two important rules:

1. You can only add a piece of paper on the top of the stack
2. You can only remove a piece of paper from the top of the stack

So, the only piece of paper you can ever pull of the top of the stack is the last one you put on it (LIFO).

Back to our little code snippet...



The PC therefore, writes (Line) 1 on a piece of paper and puts it on the stack. It increments as before and executes the following lines in order but when it gets to the last line, it reaches the 'Loop' bit.

This time, 'Loop' tells the PC to retrieve a piece of paper from the Stack. It does so and sees that on the paper it says 'Line 1'. So, it throws that piece of paper in the bin and jumps back up to the first line of the program - where the 'Do' gets it to immediately create a new piece of paper with 'Line 1' on it and puts that on the stack.

The result is that your program goes round in a loop executing all the lines between the Do and the Loop lines - not ending after the last line has been executed. In other words, an endless loop.

The stack is used for any Dark Basic command which alters program flow. This includes While...Endwhile, Repeat...Until, For...Next, Subroutines and Functions. These are all known as 'conditional jumps' - in other words, they send the PC to another part of the program, but the return condition is saved so there's somewhere to jump back to later.

BUT THIS IS NOT TRUE FOR THE GOTO COMMAND!!! Remember this as it's important and we'll come back to it later...


No Confusion Here Mate!

I can hear some of you querying how this works correctly. If your program has lots of program flow altering commands - including many nested For...Next loops (inside each other), how can the PC possibly be sure the correct piece of paper is on the top of the stack when it takes one? Why no confusion?

Well, the LIFO part keeps everything running smoothly. Let's see an otherwise useless example snippet of code purely to demonstrate the principle:



Note: You'll notice I've added line numbers to make explaining what's going on a whole lot easier for me to do!

OK, we start with the stack empty. On line 1, the 'Do' has the PC put '1' on the top of stack. The next line prints Hello World to the screen.

On line 3, the 'Repeat' has the PC put '3' on the top of stack. Lines 5, 6 and 8 add 5, 6 and 8 respectively to the stack. Notice that so far we've not encountered a line which retrieves a value from the stack.

Line 10 however is Next X so that tells the PC to go and get the top item off the stack - which just happens to be 8 - so the PC jumps to line 8 - the For X= line. As long as X is less than 799, that little piece of paper with 8 on it will keep getting put on and taken off the top of the stack!

Once however, the loop has reached 799, the paper with 8 on it is not placed on the stack, making the number 6 the top value. And when we reach line 11 - Next Y, the top stack value is grabbed, sending the PC back to line 6... which just happens to be correct, the For Y= line.

When that loop is completed, it leaves 5 on the top of the stack which just happens to be the For N= line which pairs up with the next line to retrieve the top stack number - line 12 (Next N).

Line 13 is the Until line and the value off the stack sends the PC back to line 3, which is correct - the Repeat line.

By the time we get to line 14 - Loop - all the other values on the stack have been removed and the last piece of paper says 1 which sends the PC back to the start of the program - the Do on line 1.

So, as you can see, it doesn't matter where program flow commands (loop blocks) in your program are, there's no way program flow can need the next value off the top of the stack until the current loop has finished.

Here's a graphical representation of the way that the example code snippet would use the stack:



And, you should now understand why the following still works without error in Dark Basic:



Having said that though, For A= should ALWAYS be paired up with an associated Next A, but to be honest, it's only to make your programs easier to follow for you. The truth is, that when your program is running, the PC already knows it has to jump back to the For A line - without even looking at the 'Next A' part.


A Big But!

OK, OK, stop the giggling - there's only one 't' in the but I'm talking about!

But, what happens if the natural program flow is interrupted? Like for example using that nasty Goto command...

Well, that's where all the problems arise. Let's corrupt our little code snippet:



This time, all is exactly as before apart from line 11 which tests for Y being equal to 3. If it is, then Goto sends the PC to the new line 15 where the label TheOtherPlace is.

Unlike the other program flow control commands mentioned earlier, Goto is an 'unconditional jump' command. No conditions are stored so the command cannot jump back automatically like the others.

You have to know where to jump back to and remember to put all the other Gotos and labels in manually in order to keep control. This can be a real nightmare and is inevitably the main reason newcomers programs do not work when Goto is mis-used and their programs grow in size.

But, even worse than that is the fact that in the background, our little program is heading for a major catastrophe which could see the end of the world as we know it..

OK, a bit too dramatic? All right, only kidding... let's just say it could crash your computer and need a re-boot. Still worth avoiding though right?

You see, our little corrupted program doesn't contain any syntax errors, it compiles and it does what we want it to. So, what's the big problem?

Well, the problem is the stack. Our program screws it up. Following it through like with the working one, at the point the PC reaches line 11, the number 6 is currently on top of the stack.

Our despicable Goto command sends the PC to line 15 - completely bypassing the Next Y, Next N and Until A=10 lines completely. What that means is that skipping those lines have left values on the top of the stack - waiting to cause havoc!

When the Loop on line 16 is reached, the value it gets off the stack is now 6 when it should have been the first piece of paper with the 1 on it. At the worst, bang - one dead program.

However you look at it, corrupt the stack and you are on the way to a faulty program at some stage in the future.

It's worth noting also that the stack is a fixed size - it isn't a bottomless pit, or to continue the analogy, the table is in a room with a ceiling and there's a limit to how high you can pile those pieces of paper up.

If you reach the ceiling, you'll see a special error message on your screen - one you've probably seen or heard of before:

Stack Overflow

If you keep adding items to the stack but don't take them off again, you'll fill the stack and get that error - sometimes with unpredictable results.

If you use Goto, then this sort of thing can occur on a regular basis, so please take my advice and do not use it unless you are 1000% sure there is no alternative.


So How Do I Avoid Using Goto?

We've been arguing about the use of Goto for years. Some like me say it should never be used and others say it's OK to use - if you know what you are doing. My point is that most newcomers don't actually know what they are doing and for that reason should learn how NOT to use it first.

I have to admit that there may exist a situation where it might only be possible to achieve something using Goto to that can't be achieved without it. But in over thirty years of programming I've never seen such a situation. Despite frequently requests asking to be shown a code snippet which cannot be written without Goto, I have to date not been shown one single example.

So, to avoid using Goto we have a very simple solution: Modular Programming.

The idea is very simple. Split your program into lots of small tasks and place each one into a separate module (subroutine or function), calling them when required from a single main program loop.

Adding new tasks to the program is made much easier and finding faults is many times quicker as your attention is focused on the module which does the task the error is in.

For example, if your program has a display of gauges you would create a module called UpdateGauges and call it if and when the values changed.

If you had a bug where one of the gauge values was displaying incorrectly, instead of searching through hundreds or thousands of lines of code, you would just jump straight to the UpdateGauges module in your code knowing that the problem was within those few lines.

Another module might be called Setup where you set up the screen and initialise the program variables.

You could have yet another module which just resets all the variables ready to start the game and call it when the player hits the Play button. Compare this to the 'Goto method' which jumps to a label at the start of the program. Very tacky!

So, your program ends up with lots of modules for everything and adding a new option is as simple as slotting a new module in the correct place and adding a single calling line where it's required.

Your game's Options screen, Main Menu and any other task you can think of can all be separate modules.

In order to do this however, you need to give your programs good foundations - in other words, you need to start every program with the same basic skeleton program and fill in the gaps as you go along.

Before that though, you need to know how to create a module and that is done by using Subroutines and if necessary Functions. So let's cover them next:


Subroutines

Subroutines (or procedures) are nothing more than sections of normal code in a sort of container. The container has a label at the start to identify the start of the block of code (usually named after what the subroutine does) and a 'Return' marker on the last line to define the end of the block.

Whenever you want to run the section of code you have placed in a subroutine, you use the GoSub command with the name you used for the respective label.

Note: Labels can NOT contain spaces. Stick to A-Z characters and the _ (underscore).

So, you might create a subroutine called 'SomeSubroutine' as follows:



...and to call it you would simply use:



When you use Gosub, the program jumps to the label you supplied, carries out all the lines of code it finds and when it reaches the Return on the last line, automatically jumps back to the line with the calling Gosub and continues with the line immediately following it.

Pros And Cons

The good thing about subroutines is that they are classed as being part of the main body of your program and therefore have access to all the variables used in the main body - including the ability to change their contents. In other words, all variables are global without having to define them as such.

The only very minor down side is that you can only call them by using Gosub SubroutineName. This isn't really a down side, but will become a lot clearer when you have read about calling Functions.


Functions

Functions are nothing more than sections of normal code in a sort of container. The container has a label at the start to identify the start of the block of code (usually named after what the function does) and an 'EndFunction' marker on the last line to define the end of the block.

Sound familiar?

That's right, it's essentially the same as the description for the Subroutine above. However, there are a number of subtle - but very important - differences which you can read about here if you are not sure about them:

http://forum.thegamecreators.com/?m=forum_view&t=96040&b=7

I'm not going to cover them again here in any more detail because in this tutorial we only have a need for subroutines. I firmly believe that many people use functions wrongly as they don't understand what they are for. Anyone who writes a program in Dark Basic using only functions and no subroutines obviously doesn't actually know what functions are for.

These people are often the ones complaining about the fact that you can't return more than a single variable from a function and that you have to create lists of global variables to use them - not realising that the whole point of a function is to calculate and return a single value. That is why they exist.

Think of all the functions which are built into DB - the ones with () on the end of them like RGB(), Timer(), File Exist(), Sound Playing(), Object Angle X(), Object Position Y(), Point() or any of the hundreds of others. How many of those functions return more than a single value?

The answer is NONE because functions are supposed to return a single value. Quite simply, if you need to calculate and return more than a single variable, (or indeed don't need to alter or return any variables at all), you should be using a subroutine. Period!

Because of the way they return a single data item, function calls can be used as parameters in conditions like:



If the code in the function PlayerScore() was in a subroutine that we had to Gosub, we wouldn't be able to do this quite so easily.

On the other side of the coin however, take the following simple function:



For this, all the variables used would have to be calculated outside the function then passed to it - or made global.

Nothing is returned here so it would actually be better as a subroutine because the variables it uses already exist and a subroutine would see them and be able to use them.

Also, as a subroutine, if lots of variable were needed to be changed, they are seen by the main program - getting around the 'only one variable returned' limit of functions.

So, now you know how to turn a section of code for a task into a subroutine, we can now continue on with the subject of planning your program layout in a modular fashion...


Program Layout

How you lay out your program is vital to prevent it from degenerating into a tangled mess - known as 'spaghetti code'.

You could write a linear program in one continuous lump which starts at the beginning and ends at the end with lots of Goto's jumping all over the place. Very difficult to follow, debug and maintain, so please avoid at all costs!

Instead, you should write procedural code with a main loop and lots of well-commented and named subroutines (and functions only when relevant), each one handling different tasks. That way, if you have a problem with say the ball movement in your program, then you only have to look in the Move_Ball subroutine for example to find the error - rather than ploughing through that aforementioned tangled mess.

While the game is being played, your program goes continually round the main loop jumping out to do tasks using the Gosub command - only to return automatically afterwards to continue round the loop.

The Main Menu would also be a loop inside a subroutine which is only exited when the game starts - at which point program control drops out to the main program loop. At the end of the game, program control passes back to the Main Menu subroutine loop and waits there to start the next game.

Choosing Exit from the main menu would end the program there and then without dropping out of the subroutine back to the main loop.

So, a typical program layout for a simple game of Pong might look something like this:



Note: This is obviously not real code - just pseudo code to explain the principles.

The main thing to notice here is that you have total control of the program flow and unlike using Goto, whenever you use Gosub, the Return at the end of the subroutine returns the program flow back to where it was called from to continue on the next line.

You can actually start on the first line and follow how the program runs on the computer, making the process of tracking down problems that much faster.

You will also see that after the gosub setup, the first thing the program does is gosub to the main menu. This would display the menu and sit in a loop until the player selects Play Game - at which point the subroutine is exited and the program drops back into the main program loop (playing the game).

If the player dies, (runs out of lives in the LoseLifeCheck subroutine), we gosub the ShowGameOver subroutine and then automatically gosub the main menu where once again it sits there until the player wants to play another game, exit the program, read the credits, alter the options... or whatever other option you give them on the main menu.

Most importantly, when we come to add something to the program, we need to do only two things:

1. Create a new subroutine in the section where all the subroutines are placed.

2. Add a Gosub to call the subroutine where it is needed.

This program layout can be used by absolutely any Dark Basic program you could ever want to write. It is easy to follow, add sections to and debug. And, as you can see has no use for the dreaded Goto command.

I rest my case!

TDK

Login to post a reply

Server time is: 2024-04-18 17:53:15
Your offset time is: 2024-04-18 17:53:15