- Read By
- Friday, Sep 14, 2018
- Summary
- We consider reasons and techniques for documenting procedures.

When programmers write code, they also *document* that code; that is, they
write natural language and a bit of mathematics to clarify what their code
does. The computer certainly doesn’t need any such documentation (and even
ignores it), so why should one take the time to write documentation? There
are a host of reasons.

- The design of an algorithm may not be obvious. Documentation can explain how the algorithm works.
- Particular details of the implementation of the algorithm may include subtleties. Documentation can explain those subtleties.
- Programmers who use a procedure (a.k.a. “client programmers”) should be
able to focus more on
*what*the procedure does, rather than*how*the procedure does its job. You can certainly use`sqrt`

,`reduce`

, and a host of other procedures without understanding how they are defined.

As all three examples suggest, when we write code, we write not just for
the computer, but also for a *human reader*. Even the best of code
needs to be checked again on occasion, and lots of code gets modified
for new purposes. Good documentation helps those who must support or
modify the code understand it. And while humans should be able to read
code, most read code more easily if the code has comments.

As you should have learned in Tutorial, every writer needs to keep in
mind not only the topic they are writing about, but also the *audience*
for whom they are writing. This understanding of audience is equally
important when writing documentation.

One way to think about your audience is in terms of how the reader will
be using your code. Some readers will be reading your code to understand
techniques that they plan to use in other situations. Other readers will
be responsible for maintaining and updating your code. Most readers will
be using the procedures you write. You are often your own client. For
example, you are likely to reuse procedures you wrote early in the
semester. The documentation you write for your *client programmers*
is the most important documentation you can write.

When thinking about those clients, you should first remember that
they care most about *what* your procedures do: What values do they
compute? What inputs do they take? Although you will be tempted to
describe *how* you reach your results, most of your clients will not
need to know your process, but only the result.

But you need to think about more than how your audience will use your code. You also need to think about what they know and don’t know. Because you are novices, you should generally plan to write for people like you: Assume that your client programmers know very little about Scheme, the kinds of things your program might do, even the terminology you use.

Different organizations have different styles of documentation. After too many years documenting procedures and teaching students to document procedures, Samuel A. Rebelsky developed a style that we find helps students think carefully about their work. (SamR has also received a few notes from alums and from other folks in industry who see this documentation and praise him for teaching it to students.)

To keep it easy to remember what belongs in the documentation for a procedure, Sam says that students should focus on “the Six P’s”: Procedure, Parameters, Purpose, Produces, Preconditions, and Postconditions.

The *Procedure* section simply names the procedure. Although the name
of the procedure should be obvious from the code, by including the name
in the documentation, we make it possible for the client programmer to
learn about the procedure *only* through the documentation.

The *Parameters* section names the parameters to the procedure and gives
them types. For example, if a procedure operates only on numbers or only
on positive integers, the parameters section should indicate so.

The *Purpose* section presents a few sentences or sentence fragments that
describe what the procedure is supposed to do. The sentences need not
be as precise as what you’d give a computer, but they should be clear to
the “average” programmer. (As you’ve learned in your other writing,
write to your audience.)

The *Produces* section provides a name and type for the result of the
procedure. Often, the result is not named in the code of the procedure. So
why do we both to include such a section? Because naming the result lets
us discuss it, either in the purpose above or in the preconditions and
postconditions below.

These first four P’s give an *informal* definition of what the procedure
does. The last two P’s give a much more *formal* definition of the
purpose of the procedure. You’ve seen at the beginning of this reading
that the preconditions are the conditions that must be met in order for
the procedure to work and that preconditions and postconditions are a
form of contract. Since they are a contract, we do our best to specify
them formally.

The *Preconditions* section provides additional details on the valid
inputs the procedures accepts and the state of the programming system
that is necessary for the procedure to work correctly. For example,
if a procedure depends on a value being named elsewhere, the dependency
should be named in the preconditions section. The preconditions section
can be used to include restrictions that would be too cumbersome to put
in the parameters section. For example, in many programming languages,
there is a cap on the size of integers. In such languages, it would
therefore be necessary for a `square`

procedure to put a cap on the size
of the input value to have an absolute value less than or equal to the
square root of the largest integer.

When documenting your procedures, you may wish to note whether a
precondition is *verified* (in which case you should make sure to print
an error message) or *unverified* (in which case your procedure may
return any value). At this point in your career, we will assume that
most preconditions are unverified.

The *Postconditions* section provides a more formal description of the
results of the procedure. For example, we might say informally that a
procedure reverses a list. In the postconditions section, we provide
formulae that indicate what it means to have reversed the list.

Typically, some portion of the preconditions and postconditions are expressed with formulae or code.

How do you decide what preconditions and postconditions to write? It takes practice to get good at it. We usually start by thinking about what inputs we are sure it works on and what inputs we are sure that it doesn’t work on. We then try to refine that understanding so that for any value someone presents, we can easily decide which category is appropriate.

For example, if we design a procedure to work on numbers, our general sense is that it will work on numbers, but not on the things that are not numbers. Next, we start to think about what kinds of number it won’t work on. For example, will it work correctly with real numbers, with imaginary numbers, with negative numbers, with really large numbers? As we reflect on each case, we refine our understanding of the procedure, and get closer to writing a good precondition.

The postcondition is a bit more tricky. We try to phrase what we expect of the procedure as concisely and clearly as we can, frequently using math, code when it’s clearer than the math, and English when we can’t quite figure out what math or code to write. But we always remember, consciously or subconsciously, that English is ambiguous, so we try to use formal notations whenever possible.

Note: When documenting preconditions, we generally don’t duplicate the
type restrictions given in the *Parameters* section. You can assume that
those are implicit preconditions. At times, those are the only
preconditions.

Let us first consider a simple procedure that squares its input value and that restricts that value to an integer. Here is one possible set of documentation.

```
;;; Procedure:
;;; square
;;; Parameters:
;;; val, an integer
;;; Purpose:
;;; Computes the square of val.
;;; Produces:
;;;; result, an integer
;;; Preconditions:
;;; [No additional]
;;; Postconditions:
;;; (sqrt result) is val
```

You’ll note that we did not say that “result is val*val”. Why not? We generally try to focus on important characteristics of the result, rather than the process used to compute them.

What else might we think about? In Scheme, there’s not an upper limit to
the value of integers. In other languages, such a limit may exist. Let’s
suppose there is such a limit and it is called `MAXINT`

. In that case,
trying to square a value larger than the square root of `MAXINT`

will
necessarily lead to an error. We might therefore add a precondition to
the documentation as follows.

```
;;; Procedure:
;;; square
;;; Parameters:
;;; val, an integer
;;; Purpose:
;;; Computes the square of val.
;;; Produces:
;;;; result, an integer
;;; Preconditions:
;;; (abs val) <= (sqrt MAXINT)
;;; Postconditions:
;;; (sqrt result) is val
```

You will note that the preconditions specified are those described in the
narrative section: We must ensure that * val* is not too large. Here,
we started with the idea of numbers (or integers) and, as we started
to think about special cases, realized that the procedure would not
work with too large numbers. In reacting to the realization, we added
a restriction to the size.

In DrRacket, integers are not restricted, so there’s no reason to add that precondition. However, we do want to think more carefully about types. If we restrict ourselves to exact integers, we know that our computations are both arbitrarily large and do not lose accuracy. Hence, we can write something like the following.

```
;;; Procedure:
;;; square
;;; Parameters:
;;; val, an exact integer
;;; Purpose:
;;; Computes the square of val.
;;; Produces:
;;;; result, an exact integer
;;; Preconditions:
;;; [No additional]
;;; Postconditions:
;;; (sqrt result) = val
```

That seems fairly restrictive. We want to be able to square inexact integers, real numbers (both exact and inexact), and perhaps even complex numbers. So let’s write some more general documentation.

```
;;; Procedure:
;;; square
;;; Parameters:
;;; num, a number
;;; Purpose:
;;; Compute the square of num
;;; Produces:
;;; result, a number
;;; Preconditions:
;;; [No additional]
;;; Postconditions:
;;; If num is exact, (sqrt result) = num
;;; If num is inexact, (sqrt result) approximates num
;;; result has the same "type" as num
;;; If num is an integer, result is an integer
;;; If num is real, result is real
;;; If num is exact, result is exact
;;; If num is inexact, result is inexact
;;; And so on and so forth
```

You’ll note that we spent extra effort to discuss both the result and
the type of the result. When possible, we try to give client programmers
as much useful information as we can. Many programmers care to know
whether a computation produces inexact numbers (like `sqrt`

often does)
or always keeps the exactness the same.

Let’s look at another example. Suppose a colleague in another department comes to us and says “I was too harsh on the last exam; the average was only 60. The average should really be closer to 85. I need a program to scale the grades appropriately.” So, we start by thinking about the documentation. We’ll need to ask a few questions first.

Do you represent grades as whole numbers, or can grades have a fraction?

Oh, grades can have a fractional portion. For example, a student might have 72.4.

When you say that the average should be close to 85, do you mean that we should add 25 (that’s 85-60) or that we should multiply by 85/60?

Let’s just add 25.

Here’s a first attempt at the documentation.

```
;;; Procedure:
;;; scale-grades
;;; Parameters:
;;; grades, a list of real numbers
;;; Purpose:
;;; scale all of the grades in the list so that the average is 85.
;;; Produces:
;;; scaled-grades, a list of real numbers
;;; Preconditions:
;;; [No additional]
;;; Postconditions:
;;; For each grade in the list, we have added 25 points.
```

Is that enough? Probably not. It’s a bit vague how the grades in the second list correspond exactly to the grades in the first list. We could, for example, achieve the “average 85” goal by just creating a list with one element whose value is 85. Even if we cover all the grades, must they be in the same order? Let’s clarify.

```
;;; Procedure:
;;; scale-grades
;;; Parameters:
;;; grades, a list of real numbers
;;; Purpose:
;;; scale all of the grades in the list
;;; Produces:
;;; scaled-grades, a list of real numbers
;;; Preconditions:
;;; [No additional]
;;; Postconditions:
;;; (length scaled-grades) = (length grades)
;;; (average scaled-grades) is approximately 85
;;; For all i, 0 <= i < (length grades)
;;; (list-ref scaled-grades i) = (+ 25 (list-ref grades i))
```

The use of Scheme clarifies things a bit. But we’re not done yet. We should think about the possible limits of grades. Can a student have a negative grade? We’d hope not. Can a student have a grade over 100 (e.g., if they started with a relatively high grade)? Let’s suppose we’ve asked and been told that the maximum grade is 105.

```
;;; Procedure:
;;; scale-grades
;;; Parameters:
;;; grades, a list of non-negative real numbers.
;;; Purpose:
;;; scale all of the grades in the list.
;;; Produces:
;;; scaled-grades, a list of non-negative real numbers.
;;; Preconditions:
;;; All numbers in grades are <= 105.
;;; Postconditions:
;;; (length scaled-grades) = (length grades)
;;; (average scaled-grades) is approximately 85
;;; For all i, 0 <= i < (length grades)
;;; If (list-ref grades i) <= 80
;;; (list-ref scaled-grades i) = (+ 10 (list-ref grades i))
;;; Otherwise
;;; (list-ref scaled-grades i) = 105
```

But wait! If we’re not scaling all grades the same, then we may not achieve the average of 85. We’ll need to drop that guarantee and perhaps think of another one. It’s hard to say clearly what we’ll get. The scaled average has to be higher than the old average. It can’t be higher than 85. So let’s go with the following.

```
;;; 60 <= (average scaled-grades) <= 85
```

But can we really guarantee that? We’re taking their word that the average grade is 60. Maybe we should just rely on what we know.

```
;;; (average grades) < (average scaled-grades) <= 85
```

Are we sure about that? We know that none of the original grades is negative, so adding ten and scaling will make them larger. We know that none of them start greater than 105, so we can’t accidentally reduce one to 85. Yes, that’s safe. Of course, if the average started out higher than 85, this doesn’t work. So maybe we want to add that as a precondition.

```
;;; Preconditions:
;;; All numbers in grades are <= 105.
;;; (average grades) = 60
```

That would fix our earlier postcondition problem, too. But we’ll leave the postcondition as is. Are we done? Not quite. We’ve “hard coded” the strategy in our documentation. What if they decide to have us use a different formula, such as adding 10 and then multiplying by 85/70? Perhaps we should guarantee less.

```
;;; Procedure:
;;; scale-grades
;;; Parameters:
;;; grades, a list of non-negative real numbers.
;;; Purpose:
;;; scale all of the grades in the list.
;;; Produces:
;;; scaled-grades, a list of non-negative real numbers.
;;; Preconditions:
;;; All numbers in grades are <= 105.
;;; (average grades) = 60
;;; Postconditions:
;;; (length scaled-grades) = (length grades)
;;; (average grades) < (average scaled-grades) <= 85
;;; For all i, 0 <= i < (length grades)
;;; (list-ref scaled-grades i) > (list-ref grades i)
;;; (list-ref scaled-grades i) = 105
```

No, that doesn’t seem quite right. We haven’t, for example, guaranteed that the scaling is “fair”, in that grades retain their order. We’ll add one more postcondition.

```
;;; For all i,j, 0 <= i,j < (length grades)
;;; if (list-ref grades i) <= (list-ref grades j) then
;;; (list-ref scaled-grades i) <= (list-ref scaled-grades j)
```

That’s better. Will it stop the programmer from just giving everyone a grade of 105? Yes, since that would fail to meet the average postcondition.

There’s still more we might consider as we think about what to guarantee. For example, it could be useful to make the desired average a parameter to the procedure so that we can generalize it further. But we will leave such generalization for the future.

It took a bit of effort to get the documentation right, or close enough to right. We hope that it was useful effort. First, it required us to carefully think through what we wanted the procedure to do and to differentiate aspects of our current implementation from the more general goals. Second, it required us to think about special cases. We’ll find that many of the procedures we write work fine on many cases, but not on the more extreme cases, which we will often call “edge cases” or “corner cases”. In this instance, the procedure behaved differently on large components. Finally, we had to balance the needs of the client programmer and the implementing programmer. You’ll find that a lot of procedure design requires such a balancing act.

As noted above, the preconditions and postconditions form a *contract*
with the client programmer: If the client programmer meets the type
specified in the parameters section and the preconditions specified in
the preconditions section, then the procedure must meet the postconditions
specified in the postconditions section.

As with all contracts, there is therefore a bit of adversarial balance between the preconditions and postconditions. The implementer’s goal is to do as little as possible to meet the postconditions, which means that the client’s goal is to make the postconditions specify the goal in such a way that the implementer cannot make compromises. Similarly, one of the client’s goals may be to break the procedure (so that he may sue or reduce payment to the implementer), so the implementer needs to write the preconditions and parameter descriptions in such a way that she can ensure that any parameters that meet those descriptions can be used to compute a result.

*Just in case you weren’t sure: The way we’ve described the adversarial
relationship is clearly hyperbole. Nonetheless, it’s useful to think
hyperbolically to ensure that we write the best possible preconditions
and postconditions.*

As the examples above suggest, the preconditions and postconditions help you think more carefully about exactly what you want the procedure to do and to help others understand that, too. But preconditions and postconditions can take a lot of thought. In many cases when you are writing “obvious” procedures or procedures whose primary audience is you, you may choose to write 4P’s, and do without preconditions and postconditions. We will try to indicate when only 4P’s are appropriate.

Although we typically suggest using the basic six P’s (procedure, parameters, purpose, produces, preconditions, and postconditions) to document your procedures, there are a few other optional sections that you may wish to include. For the sake of alliteration, we also begin those sections with the letter P.

In a *Package* section, you might name the group of code the procedure
is associated with. For example, `list-ref`

is in the package
`csc151/lists`

.

In a *Problems* section, you might note special cases or issues that
are not sufficiently covered. Typically, all the problems are handled by
eliminating invalid inputs in the preconditions section, but until you
have a carefully written preconditions section, the problems section
provides additional help (e.g., “the behavior of this procedure is
undefined if the parameter is 0”).

In a *Practicum* section, you can give some sample interactions with your
procedure. We find that many programmers best understand the purpose
of programs through examples, and the Practicum section gives you the
opportunity to give clarifying examples.

In a *Process* section, you can discuss a bit about the algorithm you
use to go from parameters to product. In general, the client should not
know your algorithm, but there are times when it is helpful to reveal
a bit about the algorithm.

In a *Philosophy* section, you can discuss the broader role of
this procedure. Often, procedures are written as part of a larger
collection. This section gives you an opportunity to specify these
relationships.

At least one of the faculty who uses the six-P notation often adds
a *Ponderings* section for assorted notes about the procedure, its
implementation, or its use.

In an overly-ambitious attempt to stick within the constraints of this
notation, at least one faculty member adds a *Props* section to provide
citations and other crediting of work.

You may find other useful P’s as your program. Feel free to introduce them into the documentation. (Feel free to use terms that do not begin with P, too.)

`bound-grade`

Write the 6P documentation for the `bound-grade`

procedure you recently
wrote.

We tend to focus on 6Ps. But there are others. How many P’s total? And what are they? Can you think of even more that might be useful?