What is Structured Programming?
Structured programming seeks to make programs easier to understand through the use of control structures, subroutines, and blocks.
The paradigm encourages the use of subroutines and modules to break up large codebases and make general-use code easily portable. These aspects of structured programming are often attributed to it being often used interchangeably with modular programming.
Structured Programming also introduces a proof known as the structured program theorem, which states that any possible function can be derived by combining three types of control structures:
- Sequence: instructions must follow a strict order from one instruction to the next. This forbids the use of goto's and jumps that can arbitrarily set the execution point.
- Selection: certain instructions are only executed if a condition is met, typically seen with
if-else
statements. - Iteration: executing a set of instructions repetitively until a condition is met. This usually takes the form of
for
andwhile
loops.
> The structured program theorem is also known as the Böhm–Jacopini theorem, named after scientists Corrado Böhm and Giuseppe Jacopini who created the theorem in 1966.
Characteristics of Structured Programming
Structured programming has three key aspects: control structures, subroutines, and blocks. We've already been familiarized with general types of control structures we might encounter, but we'll have to dive deeper to see how these types of control structures have been popularized in modern programming languages.
Control Structures
If-Else Statements
If-else statements are a selection control structure that help direcct the flow of logic and choice using boolean checks. If the condition being checked evaluates to true
, then the instructions within are executed. Multiple if
conditions can be chained together, where only the first condition to evaluate to true will be executed, like so:
var temperature = -21
if (temperature > 100) {
print("it's hot!")
} else if(temperature > 60) {
print("it's nice out!")
} else if(temperature > 32) {
print("it's chilly!")
} else {
print("it's freezing!")
}
Switch Statements
Switch statements are another form of selection control, similar to if-else statements, but are sometimes preferable due to their readability:
var direction = "north"
switch(direction) {
case "north":
print("You are facing north")
break
case "east":
print("You are facing east")
break
case "south":
print("You are facing south")
break
case "west":
print("You are facing west")
break
default:
print("I don't know that way!")
}
While Loop
A while loop is an iteration structure that repeats a block of code until an exit condition is met. In other words, while the condition is true, it will repeat the loop.
Typically, while loops and for loops can be used interchangeably, however programmers need to make sure that they're careful that the exit condition is always met so that they don't find themselves in an infinite loop. One case where a while loop may be preferable over a for loop is when you do not know ahead of time how many times you should repeat a loop.
var counter = 0
while(counter < 100) {
counter = counter + 1
}
print(counter)
Do-While Loop
A do-while loop is another iteration control similar to a while loop, except that it is guaranteed to run at least once. In a regular while loop, if the exit condition is already met before the loop starts, the loop will be skipped over.
var goalText = "g"
var score = 10
var i = 0
do {
goalText = goalText + "o"
i = i + 1
} while (i < score)
goalText = goalText + "al!"
print(goalText) // "gooooooooooal!"
For Loop
A for loop is similar to a while loop, but is generally used when you know how many times you're going to be looping ahead of time, like when iterating an array.
var list = ["Hello", "World!", "Goodbye!"]
for(int i = 0; i < list.size; i++) {
print(list[i])
}
Goto: The Forbidden Fruit
A Go-To is a statement that allows the programmer to define a specific part of a program and jump back to it and resume execution from that earlier point.
The Go-to statement is now typically seen as a relic of ancient computing history, and something that many veteran programmers are happy to never have to think of again. These days, unless you end up writing low-level machine code, it is unlikely that you'll ever encounter code in modern languages that utilize goto statements. Even in languages which offer the statement in their syntax, they are generally advised against using due to their ability to make the program difficult to follow, maintain, and debug.
Lastly, goto's were often used as an early solution to what would eventually become subroutines, which we know casually as functions.
Subroutines
Over the years, the subroutine has seen many similar, but essentially synonymous terms used in its place. You may be most familiar with function, method, or even procedure. In essence, a subroutine is a set of instructions that are bundled up and given a name that loosely describes the action that takes place when called. These have the advantage of making code modular.
Apart from saving time having to retype a block of code, subroutines help to abstract code into ideas. This helps computer scientists read the code and have a better understanding of what is currently taking place within a program. Additionally, modularized code makes programs less error prone: if an algorithm is incorrect, the subroutine only needs to be fixed once, as opposed to everywhere it was used.
Another advantage of subroutines is that they can call other subroutines from within them. This is helpful when you have a large task that needs to be decomposed into many smaller tasks. It is even possible for a subroutine to call itself from within; when this happens, we call it recursion. Like with while
loops, we need to be careful that our recursive subroutines have an exit condition to ensure we don't run into an infinite recursion.
Different languages often have their own syntax to define a subroutine, but most every language that adopts the structural paradigm adopts a similar pattern to subroutine calling:
do_cool_thing_fn (type formal_parameter) returns return_type
{
var local_variable;
/*...*/
return some_value;
}
/* calling our subroutine */
var x = do_cool_thing_fn(1234);
This example provides a lot of useful information that we can pick apart. Above, we have a function with the name do_cool_thing_fn
: when naming a subroutine, it's advised to choose a verb that helps describe what the subroutine is doing.
A subroutine will frequently (but not always) have parameters that can be input to provide context. Parameters allow us to specify which item we want to put into a specific list, or calculate the circumference of a circle given a specific radius. Without parameters, it would be difficult to accomplish anything useful within a subroutine, as we wouldn't have any variable data to work with.
Next, a subroutine typically has a value that it returns, or outputs as a result of the calculation done inside. There is a special value called void that many languages use to denote that a subroutine returns nothing. In these cases, the subroutine typically is used to produce "side effects", which we'll learn more about when going over functional programming. The return type of a subroutine allows us to treat the entire algorithm as a single, custom statement, like we see when we call the subroutine in the example.
Sometimes we'll need to make use of temporary storage within a subroutine to hold values while we work through the solution. Because these variables are only needed in the context of a specific subroutine, we wouldn't want these cluttering up the entire program. As a result, structured programming provides local variables that only exist within a specific block of code. But what is considered a "block" of code?
Blocks
Simply put, a block is a region of code that is separated by a begin and an end identifier. Even more simply put, in most languages, a block is just code that exists between a {
and a }
. There are a few exceptions for this, however, but we'll cover those shortly.
When you write an if { ... }
, a routine() { ... }
, a loop { ... }
, or any other code that gets wrapped up in curly braces, you are using a block! Blocks of code can be nested, and each "level" of the nesting is referred to as a deeper scope. Let's use an example to help understand:
// x exists at the highest scope: the GLOBAL scope.
// These are typically a code smell,
// but for example purposes, we'll allow it.
var x = 1;
// y, z, w DON'T exist at this "scope".
void coolBeansFunction()
{
var y = 0;
// x and y exist. z and w do not!
// Going into the while loop is a new block, and deeper scope!
while(y <= 10)
{
var z = y * 2;
// x, y, and z exist, w does not!
x = x + z;
y = y + 1;
}
// We've exited the block, so z doesn't exist anymore.
if(y > 10)
{
var z = 100;
// z exists, *but it isn't the SAME z!*
y = y + z;
}
// x, y exist, z and w do not.
var w = y;
// x, y, w exist, z does not.
}
// x exists, but y, z, and w do not!
What we can take away from this example is that variables that have been declared in a "higher" scope can be accessed in blocks within a "lower" scope, but the same is not true the other way around.
Even though we declare the variable z
within the while loop, we can't use it after the while loop is finished, because it is local only to the scope of the loop. However, you'll notice that we've declared it again within the if
statement. This is possible because z
doesn't yet exist in the scope of the coolBeansFunction, so it won't yet exist either within the scope of the statement.
Lastly, when the coolBeansFunction
is finished executing, all the variables that we've declared inside of it also cease to exist!
Notable exceptions with blocks
As mentioned earlier, not all languages adopted {curly braces} as the go-to for defining blocks. For instance, VisualBasic uses an end identifier to show that a code block is finished:
Sub YesNo()
Dim result As MsgBoxResult
result = MsgBox("Yes or No?", MsgBoxStyle.YesNo)
If answer = MsgBoxResult.Yes Then
MsgBox("You said yes");
End
Else
MsgBox("You said no");
End If
End Sub
Another example of an exception to curly braces is Python, which uses indentation to specify scope:
def myFunction():
x = 3;
if x > 2:
print("Hello World!");
print("Goodbye!");
A common criticism of whitespace-sensitive languages is that they can be strict on code style, and sometimes lead to tricky bugs where the developer was using one space too many, or too few.
Wrap Up
Structured programming offers many improvements over non-structured approaches to development, and has been so widely adopted that you won't find many programming languages still in use that are non-structured. An important note about structured programming is that the idea of using a subroutine is often shared, and provides many overlapping details, with procedural programming, which prioritizes breaking code up into functions and modules to be made portable into libraries.