Kevlin Henney - The Forgotten Art of Structured Programming
Kevlin Henney - The Forgotten Art of Structured Programming
Structured Programming
@KevlinHenney
https://twitter.com/tackline/status/757562488363843584
A familiar example of a paradigm of
programming is the technique of
structured programming, which
appears to be the dominant paradigm
in most current treatments of
programming methodology.
goto
/ WordFriday
snowclone, noun
▪ clichéd wording used as a template, typically
originating in a single quote
▪ e.g., “X considered harmful”, “These aren't
the Xs you're looking for”, “X is the new Y”,
“It’s X, but not as we know it”, “No X left
behind”, “It’s Xs all the way down”, “All
your X are belong to us”
if ((err = ReadyHash(&SSLHashSHA1, &hashCtx)) != 0)
goto fail;
if ((err = SSLHashSHA1.update(&hashCtx, &clientRandom)) != 0)
goto fail;
if ((err = SSLHashSHA1.update(&hashCtx, &serverRandom)) != 0)
goto fail;
if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
goto fail;
goto fail;
if ((err = SSLHashSHA1.final(&hashCtx, &hashOut)) != 0)
goto fail;
Mike Bland
“Goto Fail, Heartbleed, and Unit Testing Culture”
https://martinfowler.com/articles/testing-culture.html
Take Advantage
of Code Analysis
Tools
Sarah Mount
Testing Is the
Engineering
Rigor of Software
Development
Neal Ford
These bugs are as instructive as they
were devastating: They were rooted
in the same programmer optimism,
overconfidence, and haste that strike
projects of all sizes and domains.
Mike Bland
https://martinfowler.com/articles/testing-culture.html
These bugs arouse my passion because I've
seen and lived the benefits of unit testing,
and this strongly-imprinted experience
compels me to reflect on how unit testing
approaches could prevent defects as high-
impact and high-profile as these SSL bugs.
Mike Bland
https://martinfowler.com/articles/testing-culture.html
FUNCTION ISLEAP(YEAR)
LOGICAL ISLEAP
INTEGER YEAR
IF (MOD(YEAR, 400) .EQ. 0) GOTO 20
IF (MOD(YEAR, 100) .EQ. 0) GOTO 10
IF (MOD(YEAR, 4) .EQ. 0) GOTO 20
10 ISLEAP = .FALSE.
RETURN
20 ISLEAP = .TRUE.
END
FUNCTION ISLEAP(YEAR)
LOGICAL ISLEAP
INTEGER YEAR
IF (MOD(YEAR, 400) .EQ. 0) GOTO 20
IF (MOD(YEAR, 100) .EQ. 0) GOTO 10
IF (MOD(YEAR, 4) .EQ. 0) GOTO 20
10 ISLEAP = .FALSE.
RETURN
20 ISLEAP = .TRUE.
RETURN
END
FUNCTION ISLEAP(YEAR)
LOGICAL ISLEAP
INTEGER YEAR
IF (MOD(YEAR, 400) .EQ. 0) GOTO 20
IF (MOD(YEAR, 100) .EQ. 0) GOTO 10
IF (MOD(YEAR, 4) .EQ. 0) GOTO 20
10 ISLEAP = .FALSE.
GOTO 30
20 ISLEAP = .TRUE.
30 RETURN
END
FUNCTION ISLEAP(YEAR)
LOGICAL ISLEAP
INTEGER YEAR
IF (MOD(YEAR, 400) .EQ. 0) GOTO 20
IF (MOD(YEAR, 100) .EQ. 0) GOTO 10
IF (MOD(YEAR, 4) .EQ. 0) GOTO 20
10 ISLEAP = .FALSE.
GOTO 30
20 ISLEAP = .TRUE.
GOTO 30
30 RETURN
END
FUNCTION ISLEAP(YEAR)
LOGICAL ISLEAP
INTEGER YEAR
IF (MOD(YEAR, 400) .EQ. 0) GOTO 20
IF (MOD(YEAR, 100) .EQ. 0) GOTO 10
IF (MOD(YEAR, 4) .EQ. 0) GOTO 20
10 ISLEAP = .FALSE.
GOTO 30
20 ISLEAP = .TRUE.
GOTO 30
30 CONTINUE
RETURN
END
FUNCTION ISLEAP(YEAR)
LOGICAL ISLEAP
INTEGER YEAR
IF (MOD(YEAR, 400) .EQ. 0) THEN
ISLEAP = .TRUE.
ELSE IF (MOD(YEAR, 100) .EQ. 0) THEN
ISLEAP = .FALSE.
ELSE IF (MOD(YEAR, 4) .EQ. 0) THEN
ISLEAP = .TRUE.
ELSE
ISLEAP = .FALSE.
END IF
END
FUNCTION ISLEAP(YEAR)
LOGICAL ISLEAP
INTEGER YEAR
IF (MOD(YEAR, 400) .EQ. 0) THEN
ISLEAP = .TRUE.
ELSE IF (MOD(YEAR, 100) .EQ. 0) THEN
ISLEAP = .FALSE.
ELSE IF (MOD(YEAR, 4) .EQ. 0) THEN
ISLEAP = .TRUE.
ELSE
ISLEAP = .FALSE.
END IF
END
A goto completely
invalidates the high-level
structure of the code.
Taligent's Guide to Designing Programs
send(to, from, count)
register short *to, *from;
register count;
{
register n=(count+7)/8;
switch(count%8){
case 0: do{ *to = *from++;
case 7: *to = *from++;
case 6: *to = *from++;
case 5: *to = *from++;
case 4: *to = *from++;
case 3: *to = *from++;
case 2: *to = *from++;
case 1: *to = *from++;
}while(--n>0);
}
}
send(to, from, count)
register short *to, *from;
I feel a combination of
register count;
{
register n=(count+7)/8;
http://en.wikipedia.org/wiki/Fizz_buzz
Players generally sit in a circle. The player
designated to go first says the number “1”, and
each player thenceforth counts one number in
turn. However, any number divisible by three is
replaced by the word fizz and any divisible by five
by the word buzz. Numbers divisible by both
become fizz buzz. A player who hesitates or
makes a mistake is eliminated from the game.
http://en.wikipedia.org/wiki/Fizz_buzz
Players generally sit in a circle. The player
designated to go first says the number “1”, and
each player thenceforth counts one number in
turn. However, any number divisible by three is
replaced by the word fizz and any divisible by five
by the word buzz. Numbers divisible by both
become fizz buzz. A player who hesitates or
makes a mistake is eliminated from the game.
http://en.wikipedia.org/wiki/Fizz_buzz
Players generally sit in a circle. The player
designated to go first says the number “1”, and
each player thenceforth counts one number in
turn. However, any number divisible by three is
replaced by the word fizz and any divisible by five
by the word buzz. Numbers divisible by both
become fizz buzz. A player who hesitates or
makes a mistake is eliminated from the game.
http://en.wikipedia.org/wiki/Fizz_buzz
Adults may play Fizz buzz as a
drinking game, where making
a mistake leads to the player
having to make a drinking-
[citation needed]
related forfeit.
http://en.wikipedia.org/wiki/Fizz_buzz
Fizz buzz has been used as an
interview screening device for
computer programmers.
http://en.wikipedia.org/wiki/Fizz_buzz
FizzBuzz was invented to avoid
the awkwardness of realising
that nobody in the room can
binary search an array.
https://twitter.com/richardadalton/status/591534529086693376
string fizzbuzz(int n)
{
string result;
if (n % 3 == 0)
result += "Fizz";
if (n % 5 == 0)
result += "Buzz";
if (result.empty())
result = to_string(n);
return result;
}
string fizzbuzz(int n)
{
if (n % 15 == 0)
return "FizzBuzz";
else if (n % 3 == 0)
return "Fizz";
else if (n % 5 == 0)
return "Buzz";
else
return to_string(n);
}
string fizzbuzz(int n) string fizzbuzz(int n)
{ {
string result; if (n % 15 == 0)
if (n % 3 == 0) return "FizzBuzz";
result += "Fizz"; else if (n % 3 == 0)
if (n % 5 == 0) return "Fizz";
result += "Buzz"; else if (n % 5 == 0)
if (result.empty()) return "Buzz";
result = to_string(n); else
return result; return to_string(n);
} }
string fizzbuzz(int n) string fizzbuzz(int n)
{ {
string result; if (n % 15 == 0)
if (n % 3 == 0) ...
... else if (n % 3 == 0)
if (n % 5 == 0) ...
... else if (n % 5 == 0)
if (result.empty()) ...
... else
return result; ...
} }
string fizzbuzz(int n) string fizzbuzz(int n)
{ {
string result; if (n % 15 == 0)
if (n % 3 == 0) return "FizzBuzz";
result += "Fizz"; else if (n % 3 == 0)
if (n % 5 == 0) return "Fizz";
result += "Buzz"; else if (n % 5 == 0)
if (result.empty()) return "Buzz";
result = to_string(n); else
return result; return to_string(n);
} }
string fizzbuzz(int n)
{
if (n % 15 == 0)
return "FizzBuzz";
else if (n % 3 == 0)
return "Fizz";
else if (n % 5 == 0)
return "Buzz";
else
return to_string(n);
}
string fizzbuzz(int n)
{
return
(n % 15 == 0) ?
"FizzBuzz" :
(n % 3 == 0) ?
"Fizz" :
(n % 5 == 0) ?
"Buzz" :
to_string(n);
}
The default action is executed only if some
previous actions were not executed.
Maciej Piróg
“FizzBuzz in Haskell by Embedding a Domain-Specific Language”
string fizzbuzz(int n)
{
static const string fizzed[] { "", "Fizz" };
static const string buzzed[] { "", "Buzz" };
const string result =
fizzed[n % 3 == 0] + buzzed[n % 5 == 0];
return result.empty() ? to_string(n) : result;
}
The default action is executed only if some
previous actions were not executed.
We ask if we can accomplish this without
having to check the conditions for the
previous actions twice; in other words, if we
can make the control flow follow the
information flow without loosing modularity.
Maciej Piróg
“FizzBuzz in Haskell by Embedding a Domain-Specific Language”
string fizzbuzz(int n)
{
auto fizz =
[=](function<string(string)> f)
{
return
n % 3 == 0
? [=](auto) { return "Fizz" + f(""); }
: f;
};
auto buzz =
[=](function<string(string)> f)
{
return
n % 5 == 0
? [=](auto) { return "Buzz" + f(""); }
: f;
};
auto id = [](auto s) { return s; };
return fizz(buzz(id))(to_string(n));
}
string fizzbuzz(int n)
{
auto test =
[=](auto d, auto s, function<string(string)> f)
{
return
n % d == 0
? [=](string) { return s + f(""); }
: f;
};
auto fizz = bind(test, 3, "Fizz", _1);
auto buzz = bind(test, 5, "Buzz", _1);
auto id = [](auto s) { return s; };
return fizz(buzz(id))(to_string(n));
}
A work of art is the
unique result of a
unique temperament.
Oscar Wilde
void * realloc(
void * ptr,
size_t new_size);
subroutine
subroutine
procedure
function
function
Everything should be built
top-down, except the first
time.
Alan Perlis
You cannot teach beginners
top-down programming,
because they don’t know
which end is up.
C A R Hoare
Trees sprout up just
about everywhere in
computer science.
Donald Knuth
Arboricide is the murder of trees.
The victims of arboricide are the
descriptive tree structures that are
so often found in software, holding
together many individual elements
in one coherent and immediately
understandable harmony.
Software development should not
be a trade of constructing difficulty
from simplicity. Quite the contrary.
So where there are trees to be shown
you should show them, and refrain
from turning the relationships they
describe into a puzzle.
Aboricide, then, is using a smaller
description span when a larger one
would be better.
One of the most powerful
mechanisms for program
structuring [...] is the block
and procedure concept.
Ole-Johan Dahl and C A R Hoare
“Hierarchical Program Structures”
begin
ref(Book) array books(1:capacity);
integer count;
procedure Push(top); ...
procedure Pop; ...
boolean procedure IsEmpty; ...
boolean procedure IsFull; ...
integer procedure Depth; ...
ref(Book) procedure Top; ...
count := 0
end;
A procedure which is capable of
giving rise to block instances which
survive its call will be known as a
class; and the instances will be
known as objects of that class.
Ole-Johan Dahl and C A R Hoare
“Hierarchical Program Structures”
class Stack(capacity);
integer capacity;
begin
ref(Book) array books(1:capacity);
integer count;
procedure Push(top); ...
procedure Pop; ...
boolean procedure IsEmpty; ...
boolean procedure IsFull; ...
integer procedure Depth; ...
ref(Book) procedure Top; ...
count := 0
end;
λ-calculus was the
first object-oriented
language.
William Cook
“On Understanding Data Abstraction, Revisited”
https://dl.acm.org/citation.cfm?id=1640133
const newStack = () => {
const items = []
return {
depth: () => items.length,
top: () => items[0],
pop: () => { items.shift() },
push: newTop => { items.unshift(newTop) },
}
}
const newStack = () => {
const items = []
return {
depth: () => items.length,
top: () => items[items.length - 1],
pop: () => { items.pop() },
push: newTop => { items.push(newTop) },
}
}
Concatenation is an operation
defined between two classes A
and B, or a class A and a block C,
and results in the formation of a
new class or block.
Ole-Johan Dahl and C A R Hoare
“Hierarchical Program Structures”
Concatenation consists in a
merging of the attributes of both
components, and the composition
of their actions.
Barbara Liskov
“Data Abstraction and Hierarchy”
What is wanted here is something like the
following substitution property: If for each
object o1 of type S there is an object o2 of type
T such that for all programs P defined in terms
of T, the behavior of P is unchanged when o1 is
substituted for o2, then S is a subtype of T.
Barbara Liskov
“Data Abstraction and Hierarchy”
const nonDuplicateTop = base => {
const push = base.push
return Object.assign(base, {
push: newTop => {
if (base.top() !== newTop)
push(newTop)
},
})
}
tests = {
...
'A non-empty stack becomes deeper by retaining a pushed item as its top':
() => {
const stack = newStack()
stack.push(‘C++/C')
stack.push(‘2019')
stack.push(‘2019')
assert(stack.depth() === 3)
assert(stack.top() === ‘2019')
},
...
}
const newStack =
() => compose(clearable, stackable)({})
tests = {
...
'A non-empty stack becomes deeper by retaining a pushed item as its top':
() => {
const stack = newStack()
stack.push(‘C++/C')
stack.push(‘2019')
stack.push(‘2019')
assert(stack.depth() === 3)
assert(stack.top() === ‘2019')
},
...
}
const newStack =
() => compose(nonDuplicateTop, clearable, stackable)({})
tests = {
...
'A non-empty stack becomes deeper by retaining a pushed item as its top':
() => {
const stack = newStack()
stack.push(‘C++/C')
stack.push(‘2019')
stack.push(‘2019')
assert(stack.depth() === 3)
assert(stack.top() === ‘2019')
},
...
}
What is wanted here is something like the
following substitution property: If for each
object o1 of type S there is an object o2 of type
T such that for all programs P defined in terms
of T, the behavior of P is unchanged when o1 is
substituted for o2, then S is a subtype of T.
Barbara Liskov
“Data Abstraction and Hierarchy”
reasonable
reason
Coding with Reason
Avoid using goto statements, as
they make remote sections highly
interdependent.
Yechiel Kimchi
Coding with Reason
Avoid using modifiable global
variables, as they make all sections
that use them dependent.
Yechiel Kimchi
tests = {
...
'A non-empty stack becomes deeper by retaining a pushed item as its top':
() => {
const stack = newStack()
stack.push(‘C++/C')
stack.push(‘2019')
stack.push(‘2019')
assert(stack.depth() === 3)
assert(stack.top() === ‘2019')
},
...
}
tests = {
...
'A non-empty stack becomes deeper by retaining a pushed item as its top':
() => {
const stack = newStack() // Arrange
stack.push(‘C++/C’) // Act
stack.push(‘2019')
stack.push(‘2019')
assert(stack.depth() === 3) // Assert
assert(stack.top() === ‘2019’)
},
...
}
tests = {
...
'A non-empty stack becomes deeper by retaining a pushed item as its top':
() => {
const stack = newStack() // Given
stack.push(‘C++/C’) // When
stack.push(‘2019')
stack.push(‘2019')
assert(stack.depth() === 3) // Then
assert(stack.top() === ‘2019’)
},
...
}
{P} Q {R}
Communications of the ACM, 12(10), October 1969
P {Q} R
If the assertion P is true before
initiation of a program Q, then
the assertion R will be true on
its completion.
sequence
selection
iteration
https://twitter.com/mattpodwysocki/status/393474697699921921
We shall use the binary semaphore “mutex”
only for the purpose just described.
We aim to make the critical sections,
governed by “mutex” rather short and we
won’t shed a tear if some critical section is
shorter than necessary.
Edsger W Dijkstra
“Cooperating Sequential Processes”
I’ve often joked that instead of picking
up Dijkstra’s cute acronym we should
have called the basic synchronization
object the bottleneck.
David Butenhof
comp.programming.threads
Threads and locks —
they’re kind of a dead
end, right?
Bret Victor
“The future of programming”
So, I think if [...] we’re still using
threads and locks [in 2013], we
should just, like, pack up and go
home, ’cause we’ve clearly failed
as an engineering field.
Bret Victor
“The future of programming”
Mutable
Unshared mutable Shared mutable
data needs no data needs
synchronisation synchronisation
Unshared Shared
Unshared immutable Shared immutable
data needs no data needs no
synchronisation synchronisation
Immutable
The Synchronisation Quadrant
Mutable
Unshared mutable Shared mutable
data needs no data needs
synchronisation synchronisation
Unshared Shared
Unshared immutable Shared immutable
data needs no data needs no
synchronisation synchronisation
Immutable
Procedural Comfort Zone
Mutable
Unshared mutable Shared mutable
data needs no data needs
synchronisation synchronisation
Unshared Shared
Unshared immutable Shared immutable
data needs no data needs no
synchronisation synchronisation
Immutable
Procedural Comfort Zone Procedural Discomfort Zone
Mutable
Unshared mutable Shared mutable
data needs no data needs
synchronisation synchronisation
Unshared Shared
Unshared immutable Shared immutable
data needs no data needs no
synchronisation synchronisation
Immutable
Procedural Comfort Zone
Mutable
Unshared mutable Shared mutable
data needs no data needs
synchronisation synchronisation
Unshared Shared
Unshared immutable Shared immutable
data needs no data needs no
synchronisation synchronisation
Immutable
no shared mutable state
coordination
single-threaded activity
We should have some ways of
coupling programs like garden
hose--screw in another segment
when it becomes necessary to
massage data in another way.
This is the way of IO also.
N
bounded
buffered
asynchronous
N=∞
unbounded
buffered
asynchronous
promise future
N=1
bounded
buffered
asynchronous
N=0
bounded
unbuffered
synchronous
We shall not cease from exploration
And the end of all our exploring
Will be to arrive where we started
And know the place for the first time.
T S Eliot