Go To Statement Considered Harmful
Learning Objectives
- You know of Dijkstra’s famous letter criticizing the GOTO statement.
- You can recognize issues that the use of GOTO can create in code structure.
- You understand the historical context of GOTO and how it fit early programming.
- You know of the Böhm-Jacopini theorem and its implications.
The Famous Letter
In 1968, Edsger W. Dijkstra wrote an opinion piece to the Communications of the ACM titled Go To Statement Considered Harmful. Dijkstra’s opening sentence included the observation that “the quality of programmers is a decreasing function of the density of go to statements in the programs they produce”. In effect, he argued that the more GOTO statements a programmer used, the worse they were at the craft.
The key argument was that GOTO statements lead to unstructured code that is hard to read, reason about, and maintain. The debate, in part, contributed to the evolution of programming languages that followed.
Funnily enough, there are plenty of articles titled “Considered Harmful” that followed Dijkstra’s original letter, applying the same critique to various programming practices. Some folks at Aalto University have even shortened the the phrase to just “Considered”, see Break Statement Considered.
What’s Wrong with GOTO?
You’ve just built an interpreter with GOTO! Was this a mistake? Not at all - understanding GOTO by implementing it gives you deeper appreciation for why the field has moved beyond it.
Let’s explore the problems GOTO creates through concrete examples.
Spaghetti Code
Consider this BASIC program:
10 GOTO 80
20 PRINT "Step 2"
30 GOTO 60
40 PRINT "Step 1"
50 GOTO 20
60 PRINT "Step 3"
70 GOTO 90
80 GOTO 40
90 PRINT "Done"
100 END
To solve the question “What order do the PRINT statements execute?”, you have to trace through the jumps. When you try to read it, your brain has to simulate the execution flow mentally. The steps are not in a linear order, and there are also no clear structures indicating loops or conditionals.
The flow is:
10 -> 80 -> 40 -> Print "Step 1"
-> 50 -> 20 -> Print "Step 2"
-> 30 -> 60 -> Print "Step 3"
-> 70 -> 90 -> Print "Done" -> 100
This is spaghetti code: the code is messy, the control flow is tangled, and the code is unstructed. Reading it requires mentally executing the program.
Reasoning About State and No Clear Loop Invariants
Consider this loop:
10 LET I = 0
20 LET I = I + 1
30 PRINT I
40 IF I < 10 THEN 20
50 END
To solve the question “What is the value of I at line 30?”, you have to trace through the loop iterations. Each time you reach line 30, I has a different value depending on how many times you’ve looped.
You might think that the answer would be “it could be any value from 1 to 10, depending on which iteration you’re in!”. However, in any realistic BASIC program, it’s not as simple. The actual answer is “it could be anything, because with GOTO you could jump into the middle of the loop from anywhere else in the program”.
A loop invariant is a property that’s always true at a specific point in a loop.
With structured loops:
for i in 1..=10 {
// Invariant: 1 <= i <= 10
println!("{}", i);
}
The loop invariant is clear: at the start of each iteration, i is between 1 and 10. After the loop, i no longer exists.
Unstructured Error Handling
Because the programs do not have structured control flow, error handling with GOTO can also become messy. In the program below, the INPUT statement reads a number, and if it’s negative, it jumps to an error handler.
10 LET X = 0
20 INPUT X
30 IF X < 0 THEN 100
40 LET Y = 100 / X
50 PRINT Y
60 GOTO 20
100 PRINT "Error: negative number"
110 GOTO 20
You’d never know that the line 100 is an error handler just by briefly looking at it. There’s no structure indicating “this is error handling code.”
Code Duplication
10 INPUT X
20 IF X < 0 THEN 100
30 LET Y = X * 2
40 PRINT Y
50 GOTO 200
100 REM Error handling
110 PRINT "Error: negative"
120 LET Y = 0
130 PRINT Y
140 GOTO 200
Lines 40 and 130 duplicate PRINT Y. Without functions, you can’t reuse code - you GOTO different locations that repeat the same logic.
GOTO Made Sense
Well, if GOTO is so bad, why did early programming languages include it? There are several historical reasons.
Assembly Language Perspective
First, early programming was done in assembly language, and GOTO built on top of the mentality of assembly language. Assembly has conditional and unconditional jumps:
MOV AX, 0 ; i = 0
loop:
INC AX ; i++
CALL PRINT ; print(i)
CMP AX, 10 ; compare i to 10
JL loop ; jump if less
HLT ; end
This is just GOTO by another name! Early high-level languages like FORTRAN and BASIC were, in a sense, thin wrappers over assembly that sought to make programming easier.
Limited Memory
Early computers had few kilobytes of memory, no stack (or a tiny stack), and function calls were expensive. Structured constructs like loops and functions require more instructions than a simple GOTO.
The GOTO statement could be implemented simply as a jump instruction, which was efficient in terms of both memory and execution time. A function call, on the other hand, requires saving the return address, managing a call stack, and possibly passing parameters, which adds overhead.
Batch Processing
Early programming was based on batch jobs. Programmers wrote code on punch cards, the punch cards were delivered to a data center, a data center operator fed the punch cards into a computer, the computer processed the cards one by one, receiving no user input during execution, and finally the operator collected the printout of the submitted jobs.
Programmers received the results from their code hours or days later. There was no interactive debugging or iterative development.
If a problem was found, the programmer had to fix the code, punch new cards, and resubmit the job. GOTO statements made it easier to write quick-and-dirty hacks to get the job done in this environment; one could, for example, rewrite one card from the middle of a batch job to fix a bug, where the card represented a GOTO statement to skip over faulty code and do the execution in a new set of cards that would be added to the end of the program. At the end of the new set of cards, there would be a GOTO back to the original program flow.
The Böhm-Jacopini Theorem
The original version of BASIC was released in 1964, four years before Dijkstra’s letter. At that point, GOTO was a natural fit for the language design, and structured programming as we currently know it was not yet mainstream.
ALGOL, which is the first language to introduce structured programming concepts like block structure and nested control flow, was also released in 1960. However, its adoption was limited compared to FORTRAN and BASIC.
In 1966, Corrado Böhm and Giuseppe Jacopini proved an important theorem that further supported Dijkstra’s argument. The Böhm-Jacopini theorem states that any program can be written using only three control structures: sequence, selection (if/then/else), and iteration (while loops), without the need for GOTO statements.
This means that GOTO is not necessary for expressing any computation. While removing GOTO can lead to more verbose code in some cases, it encourages better structure and readability.
Summary
In this chapter, we briefly discussed the GOTO controversy. To summarize:
- Dijkstra’s letter critiqued GOTO for leading to unstructured code.
- GOTO creates problems: spaghetti code, unclear state, no invariants.
- Structured programming replaces GOTO with sequence, selection, iteration.
- Historical context: GOTO made sense for early computers due to assembly roots, limited memory, and batch processing.
- Böhm-Jacopini theorem showed that any program can be written without GOTO.