8.8.0.8
Preface
Many professions require some form of programming. Accountants program
spreadsheets; musicians program synthesizers; authors program word
processors; and web designers program style sheets. When we wrote these
words for the first edition of the book (1995–2000), readers may have considered
them futuristic; by now, programming has become a required skill and
numerous outlets—
need, always with the goal of enhancing people’s job prospects.
The typical course on programming teaches a “tinker until it works”
approach. When it works, students exclaim “It works!” and move on. Sadly,
this phrase is also the shortest lie in computing, and it has cost many
people many hours of their lives. In contrast, this book focuses on habits
of good programming, addressing both professional and vocational
programmers.
By “good programming,” we mean an approach to the creation of software
that relies on systematic thought, planning, and understanding from the
very beginning, at every stage, and for every step. To emphasize the
point, we speak of systematic program design and systematically
designed programs. Critically, the latter articulates the rationale
of the desired functionality. Good programming also satisfies an
aesthetic sense of accomplishment; the elegance of a good program is
comparable to time-tested poems or the black-and-white photographs of a
bygone era. In short, programming differs from good programming like
crayon sketches in a diner from oil paintings in a museum.
No, this book won’t turn anyone into a master painter. But, we would not
have spent fifteen years writing this edition if we didn’t believe that
everyone can design programs
and
everyone can experience the satisfaction that comes with creative design.
Indeed, we go even further and argue that
program design—
but not programming— deserves the same role in a liberal-arts education as mathematics and language skills.
A student of design who never touches a program again will still pick up
universally useful problem-solving skills, experience a deeply creative
activity, and learn to appreciate a new form of aesthetic. The rest of
this preface explains in detail what we mean with “systematic design,” who
benefits in what manner, and how we go about teaching it all.
Systematic Program Design
A program interacts with people, dubbed users, and other programs, in
which case we speak of server and client
components. Hence any reasonably complete program consists of many building
blocks: some deal with input, some create output, while some bridge the gap
between those two. We choose to use functions as fundamental building
blocks because everyone encounters functions in pre-algebra and because
the simplest programs are just such functions. The key is to discover
which functions are needed, how to connect them, and how to build them
from basic ingredients.
In this context, “systematic program design” refers to a mix of two
concepts: design recipes and iterative refinement.We drew
inspiration from Michael Jackson’s method for creating COBOL programs plus
conversations with Daniel Friedman on recursion, Robert Harper on type
theory, and Daniel Jackson on software design. The design recipes are a
creation of the authors, and here they enable the use of the latter.
From Problem Analysis to Data Definitions
Identify the information that must be represented and how it is
represented in the chosen programming language. Formulate data definitions
and illustrate them with examples.Signature, Purpose Statement, Header
State what kind of data the desired function consumes and
produces. Formulate a concise answer to the question what the
function computes. Define a stub that lives up to the signature.Functional Examples
Work through examples that illustrate the function’s purpose.
Function Template
Translate the data definitions into an outline of the function.
Function Definition
Fill in the gaps in the function template. Exploit the purpose
statement and the examples.Testing
Articulate the examples as tests and ensure that the function
passes all. Doing so discovers mistakes. Tests also supplement examples in
that they help others read and understand the definition when the need
arises—and it will arise for any serious program.
Design Recipes apply to both complete programs and individual
functions. This book deals with just two recipes for complete programs:
one for programs with a graphical user interface (GUI) and one for batch
programs. In contrast, design recipes for functions come in a wide variety
of flavors: for atomic forms of data such as numbers; for enumerations of
different kinds of data; for data that compounds other data in a fixed
manner; for finite but arbitrarily large data; and so on.
The function-level design recipes share a common design
process. Figure 1 displays its six essential
steps. The title of each step specifies the expected outcome(s); the
“commands” suggest the key activities. Examples play a central role at
almost every stage.Instructors Have students copy
figure 1 on one side of an index card. When
students are stuck, ask them to produce their card and point them to the
step where they are stuck. For the chosen data representation in step 1,
writing down examples proves how real-world information is encoded as data
and how data is interpreted as information. Step 3 says that a
problem-solver must work through concrete scenarios to gain an
understanding of what the desired function is expected to compute for
specific examples. This understanding is exploited in step 5, when it is
time to define the function. Finally, step 6 demands that examples are
turned into automated test code, which ensures that the function works
properly for some cases. Running the function on real-world data may
reveal other discrepancies between expectations and results.
Each step of the design process comes with pointed questions, which are introduced across the six parts of the book. For certain
steps—
questions may appeal to the data definition. The answers almost
automatically create an intermediate
product.Instructors The most important questions are
those for steps 4 and 5. Ask students to write down these questions in
their own words on the back of their index card. This scaffolding pays off
when it comes time to take the one creative step in the process: the
completion of the function definition. And even then, help is available in
almost all cases.
The novelty of this approach is the creation of intermediate products for
beginner-level programs. When a novice is stuck, an expert or an
instructor can inspect the existing intermediate products. The inspection
is likely to use the generic questions from the design process and thus
drive the novice to correct himself or herself. And this self-empowering
process is the key difference between programming and program design.
Iterative Refinement addresses the issue that problems are complex
and multifaceted. Getting everything right at once is nearly
impossible. Instead, computer scientists borrow iterative refinement from
the physical sciences to tackle this design problem. In essence, iterative
refinement recommends stripping away all inessential details at first and
finding a solution for the remaining core problem. A refinement step adds
in one of these omitted details and re-solves the expanded problem, using
the existing solution as much as possible. A repetition, also called an
iteration, of these refinement steps eventually leads to a complete
solution.
In this sense, a programmer is a miniscientist. Scientists create
approximate models for some idealized version of the world to make
predictions about it. As long as the model’s predictions come true,
everything is fine; when the predicted events differ from the actual ones,
scientists revise their models to reduce the discrepancy. In a similar
vein, when programmers are given a task, they create a first design, turn
it into code, evaluate it with actual users, and iteratively refine the
design until the program’s behavior closely matches the desired product.
This book introduces iterative refinement in two different ways. Since
designing via refinement becomes useful even when the design of programs becomes
complex, the book introduces the technique explicitly in the fourth part,
once the problems acquire a certain degree of difficulty. Furthermore, we
use iterative refinement to state increasingly complex variants of the
same problem over the course of the first three parts of the book. That
is, we pick a core problem, deal with it in one chapter, and then pose a
similar problem in a subsequent chapter—
introduced concepts.
DrRacket and the Teaching Languages
Learning to design programs calls for repeated hands-on practice. Just as
nobody becomes a piano player without playing the piano, nobody
becomes a program designer without creating actual programs and getting
them to work properly. Hence, our book comes with a modicum of software
support: a language in which to write down programs and a program
development environment with which programs are edited like word
documents and with which readers can run programs.
Many people we encounter tell us they wish they knew how to code and then
ask which programming language they should learn. Given the press
that some programming languages get, this question is not surprising. But
it is also wholly inappropriate.Instructors For
courses not aimed at beginners, it may be possible to use an off-the-shelf
language with the design recipes. Learning to program in a currently
fashionable programming language often sets up students for
eventual failure. Fashion in this world is extremely short lived. A typical
“quick programming in X” book or course fails to teach principles that
transfer to the next fashion language.