Skip to main content

Documenting your procedures

Due
Friday, 1 February 2019
Summary
We consider reasons and techniques for documenting procedures.
Prerequisites
Writing your own procedures
Note
In this reading, we consider the documentation format we use in this course. Although this reading introduces that format, you will not be expected to fully document your procedures until you’ve developed a bit more experience in writing Racket code.

Introduction

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, above, string-split, 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.

The audience for documentation

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 read 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 use the procedures you write. We call the people who use our procedures “clients”. 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.

As we noted in the discussion of procedures, one of the main benefits of writing procedures is that procedures provide a form of abstaction, when you use a procedure, you care more about what the procedure does than how. Hence, the focus of most procedure documentation is the abstract behavior not the detailed process. If your client cares most about what your procedures do, your main goals are to explain the kinds of input the procedure expects, what the procedure computes, and what the results look like.

Of course, 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 Racket or the broader context in which you’ve written your procedures.

Documenting procedures with the Six P’s

Different organizations have different styles of documentation. After too many years documenting procedures and teaching students to document procedures, we’ve developed a style that we find helps students think carefully about their work. While it does not correspond to the “standard” style of documentation, it has proven useful.

To keep it easy to remember what belongs in the documentation for a procedure, you 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 inputs 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 underlying code. 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. Your client programmer will often care more about the type of the result: Do you create an image, a number, a string, a procedure, something else? Particularly as clients need to use the output of one procedure for the input of another procedure, it’s helpful to know as much as possible about the output.

The first four P’s are a good starting point. As you document your procedure, they encourage you to think carefully about the inputs to your procedure, the purpose of your computation, and the type of your output.

However, these first four P’s give only an informal definition of what the procedure does. But informal definitions are often vague. What happens if the reader does not understand all of the terms you’ve used or associates a different meaning with those terms? The Preconditions and Postconditions help address the informality by employing a much more formal definition of what the procedure does, often describing the output in terms of a formula or a piece of code.

An example

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” or “results 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, the integers can take on arbitrarily large values, so there’s no reason to add that precondition. However, we do want to think more carefully about types. If we limit ourselves to exact integers, we know that our computated values 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

But that choice seems, well, limiting. We want to be able to square inexact integers, real numbers (both exact and inexact), and perhaps even complex numbers. In case you’ve forgotten, inexact numbers are not represented precisely. That means that for example, the square root of the square of an inexact number may not be the original number, but instead an approximation thereof. We need to accommodate that case. 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

In this documentation, you’ll note that we spent extra effort to discuss the type and accuracy 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.

It takes time and practice to get postconditions right. It also takes some familiarity with the langauge we use to describe values in Racket (e.g., “exact” vs “inexact”). For the time being, you will not need to write preconditions and postconditions. However, when you see them appear along with code, you should take a moment to read through them to reflect upon the level of detail we are using and the ways in which we describe postconditions formally.

Acknowledgements

This section draws upon a reading entitled “Documenting your procedures” from Grinnell College’s CSC 151. The original version of that reading appeared in Fall 2007.