Programs consist of definitions and expressions. Large programs consist of
hundreds and thousands of definitions and expressions. Programmers design
functions, use other programmer's functions, leave, start on the
project. Without a strong discipline we cannot hope to produce software of
high quality. The key to programming discipline is to understand the design
of programs as a means to describe computations, which, in turn, means to
manipulate data through combinations of basic operations.
For that reason, the design of every program---whether it is small and for
personal use or large and for business use--- must start with an analysis
of the surrounding world of information and a description of the classes of
data that represent the relevant information. If the classes are unusual or
new, we make up examples so we understand the structure of the class
description. After we understand the world of information surrounding our
project and its data representation, we make a plan.
A project plan identifies what data we wish to produce from the data that
the program will be given. In many cases, though, a program doesn't just
process data in one way but in many ways. For example, a program for
managing bank accounts must handle deposits, withdrawals, interest
calculations, tax form generation, and many other tasks. In other cases, a
program may have to compute complex relationships. For example, a program
for simulating a ping-pong game must compute the movement of the ball,
bounces on the table, bounces from the paddle, paddle movements, etc. In
either case, we need to describe what the various ways of processing data
are and how they relate to each other. Then we rank them and start with
the most important one. We develop a working product, make sure that it
meets our specifications, and refine the product by adding more functions
or taking care of more cases or both.
Designing a function requires a rigorous understanding of what it computes.
Unless we can describe its purpose and its effect with concise statements,
we can't produce the function. In almost all cases, it helps to make up
examples and work through the function's computation by hand. For
complicated functions or for functions that use generative recursion, we
should include some examples with the purpose statements. The examples
illustrate the purpose and effect statements for others who may have to
read or modify the program.
Studying examples tends to suggest the basic design recipe. In most cases,
the design of a function is structural, even if it uses an accumulator or
structure mutation. In a few others, we must use generative recursion. For
these cases, it is important to explain the method for generating new
problems and to sketch why the computation terminates.
When the definition is complete, we must test the function. Testing
discovers mistakes, which we are bound to make due to all kinds of
reasons. The best testing process turns independently developed examples
into test suites, that is, a bunch of expressions that apply the function
to select input examples and compare its results and effects with expected
results and effects (mostly) automatically. If a mismatch is discovered,
the test suite reports a problem. The test suite should never be discarded,
only commented out. Every time we modify the function, we must use the test
suite to check that we didn't introduce mistakes. If we changed the
underlying process, we may have to adapt the test suite <#60272#>mutatis mutandis<#60272#>.
No matter how hard we work, a function (or program) isn't done the first
time it works for our test suite. We must consider whether the development
of the function revealed new interesting examples and turn such examples
into additional tests. And we must edit the program. In particular, we must
use abstraction properly to eliminate all related patterns wherever
possible.
If we respect these guidelines, we will produce decent software. It will
work because we understand why and how it works. Others who must modify or
enhance this software will understand it, because we include sufficient
information on its development process. Still, to produce great software,
we must practice following these guidelines and learn a lot more about
computing and programming than a first book can teach.