Documenting Programs & Historical Notes

Gries’ closing chapters make a quiet but radical point: the assertions you invented to prove a program correct are its best documentation — and they sit at the end of a lineage that runs Floyd → Hoare → Dijkstra → Gries, from “verify after” to “derive correct by construction.”

This is an original study summary for quick reference, covering the documentation and historical-notes material near the end of David Gries’ The Science of Programming (1981). Notation follows Gries and Dijkstra: {P} S {Q} for a Hoare triple, wp for weakest precondition, invariants and bound functions for loops. See the book for the full, rigorous treatment.

Contents

  1. Assertions ARE the documentation
  2. How to document a loop
  3. Documentation guidelines
  4. Assertions as executable checks
  5. Historical notes — the lineage
  6. What to take away
  7. Summary

Assertions ARE the documentation

Ask what a comment is worth and you get an uncomfortable answer: a comment records what someone hoped the code did at the moment they wrote it. It is prose, unchecked, and it rots the instant the code beneath it changes. Gries’ whole method hands you something categorically better. The precondition, the postcondition, and above all the loop invariant are not decoration bolted on afterward — they are the very predicates the program was derived from. They state intent precisely, in the same logic the code obeys, and they are checkable.

A comment says what someone hoped is true.
An assertion says what must be true — or the program is wrong.

That is the shift in status. The pre/postcondition pair is the contract: it tells a reader exactly what the code assumes and exactly what it promises, without their having to read the body. The invariant tells them the idea of the loop — the single fact that stays true across every iteration — which is the one thing raw code never reveals. These assertions are the most valuable documentation a program can carry precisely because they are the documentation the compiler, the test suite, and a proof can all hold you to.

The reframe: in Gries’ discipline documentation is not something you add to finished code. It is the material the code was built from. Keeping the assertions is keeping the design.

How to document a loop

A loop is the hardest construct to read cold, because its meaning lives in what accumulates over many iterations, not in any single line. Gries’ prescription is exact: next to every loop, record four things — the precondition that holds on entry, the invariant that holds before and after each iteration, the bound function that proves it terminates, and the postcondition it establishes on exit. Those four are not optional annotations; they are the loop’s specification.

✗ A comment that says nothing
q = 0
r = a
# loop through items
while r >= b:
    r = r - b
    q = q + 1
# why does this compute a / b ?
# what stays true each pass?
# why does it stop? the reader
# must reverse-engineer all of it.
✓ Annotated with invariant + bound
# pre  Q: a ≥ 0 and b > 0
q = 0
r = a
# invariant P: a = q*b + r  and  0 ≤ r
# bound     t: r        (t ≥ 0, t decreases)
while r >= b:          # guard B
    r = r - b
    q = q + 1
# post R: a = q*b + r  and  0 ≤ r < b

Read the two versions side by side. The left tells you the shape of the code you can already see. The right tells you the four things the code cannot tell you: what it needs (a ≥ 0, b > 0), the exact relationship it maintains (a = q*b + r ∧ 0 ≤ r), why it halts (r is a non-negative integer that strictly drops each pass), and what it delivers (0 ≤ r < b). Anyone can now verify the loop without running it — on exit, invariant plus negated guard (0 ≤ r and r < b) yields the postcondition directly. The same skeleton documents a summation loop: invariant s = (sum of A[0..i-1]) ∧ 0 ≤ i ≤ n, bound n - i, post s = sum of A[0..n-1].

Documentation guidelines

The discipline distills into a handful of rules. None are about volume — more comments is not better documentation. They are about recording the things the code genuinely cannot say for itself, and never the things it already says plainly.

RuleWhySmell it replaces
Document the WHAT / the intent, not the obvious HOW.The mechanics are already in the code. The intent — the relationship being maintained, the contract being met — is not.i = i + 1 # increment i
Keep assertions honest; update them with the code.A stale assertion is worse than none — it actively misleads. When the code changes, the invariant changes with it, or the derivation no longer holds.A postcondition that describes last quarter’s behaviour.
Name variables for their role in the invariant.If i is “number of elements already processed,” the invariant reads itself. Good names make the assertions almost redundant — the best outcome.x, tmp, flag carrying real meaning.
A procedure is documented by its spec, not its body.A caller should need only the pre/postcondition to use it correctly. If they must read the implementation, the specification has failed.“See the code for what it returns.”
Avoid comments that merely restate code.They double the maintenance surface and add zero information. Every comment must earn its place by saying something the code cannot.# set total to zero above total = 0.
The through-line: comment the reasoning, assert the truth. A comment explains why a non-obvious choice was made; an assertion states what must hold. Neither should ever narrate the how — the code owns that.

Assertions as executable checks

Here the documentation stops being passive. The very predicate that proves a loop correct can be dropped into the running program as an assert — a runtime check that fails loudly the moment reality diverges from the proof. The invariant that lived in a comment becomes an executable guard against regression.

✓ The invariant, made executable
def divide(a, b):
    assert a >= 0 and b > 0        # pre Q
    q, r = 0, a
    while r >= b:
        assert a == q*b + r and r >= 0  # inv P
        r -= b
        q += 1
    assert a == q*b + r and 0 <= r < b  # post R
    return q, r
✓ The same predicate as a test oracle
# property-based test: for ANY valid input
# the postcondition must hold. The oracle is
# the specification itself, not a table of
# hand-picked expected outputs.
def test_divide(a, b):        # a ≥ 0, b > 0
    q, r = divide(a, b)
    assert a == q*b + r
    assert 0 <= r < b
    # one predicate certifies every case

This is the bridge to modern practice. Design by contract (Eiffel, and the require/ensure/invariant constructs it inspired) is Gries’ pre/post/invariant triad promoted to first-class language features that execute. Property-based testing (QuickCheck, Hypothesis) uses the postcondition as an oracle across machine-generated inputs, so the specification checks thousands of cases you would never enumerate by hand. Neither replaces a proof — an assertion that never fires proves nothing about the paths it did not take, and tests still show only the presence of bugs. But the same predicate serves in three roles at once: it proves the code, documents the code, and guards the code against future change. That triple duty is the payoff.

Historical notes — the lineage

Gries did not invent this calculus; he synthesized and taught it. The closing notes trace a fifteen-year arc in which the field’s ambition shifted from checking a finished program to growing a correct one. Four papers carry the story.

YearWho & whatThe contribution
1967Robert W. Floyd
“Assigning Meanings to Programs”
Attaches assertions to the edges of a flowchart and derives verification conditions: if each assertion implies the next across every node, the program is correct. Introduces the well-ordered / bound idea — a quantity that strictly decreases in a well-founded order — to prove termination. The first rigorous framework for what a program means.
1969C.A.R. Hoare
“An Axiomatic Basis for Computer Programming”
Turns Floyd’s idea into a formal logic. The {P} S {Q} triple becomes the central object, and each language construct gets an axiom or inference rule (assignment, composition, conditional, the while-rule with its invariant). This is Hoare logic — correctness as formal deduction.
1975–76Edsger W. Dijkstra
“Guarded Commands, Nondeterminacy and Formal Derivation of Programs” (1975) & A Discipline of Programming (1976)
Replaces verification-after with derivation. The weakest precondition wp(S,R) is a predicate transformer computed backward from the goal; guarded commands (if…fi, do…od) build nondeterminacy in cleanly. Now you calculate the program from its specification rather than guessing and checking.
1981David Gries
The Science of Programming
Synthesizes and teaches the method to working programmers. Supplies the logic prelude, makes the calculational, predicate-transformer discipline learnable, and adds the practical heuristics — inventing invariants by weakening the postcondition — that turn theory into a repeatable way of writing code.

The single most important movement across this table is the change in when correctness enters. Floyd and Hoare give you tools to verify after: write the program, then prove it meets its spec. Dijkstra and Gries invert this into derive correct by construction: let the spec and the emerging proof drive the code so it is right the first time. The bound function that Floyd introduced for termination survives untouched all the way to Gries — a reminder that the ideas accreted rather than replaced one another.

Floyd, Hoare  →  verify AFTER the fact
Dijkstra, Gries  →  DERIVE correct by construction

What to take away

Notation dates; the discipline does not. Few programmers today write do…od or compute a wp by hand, yet the habits of mind the calculus instills are exactly the ones that separate reliable code from hopeful code.

The discipline outlives the specific notation. Whether you write a formal wp derivation or just a one-line invariant comment above a for loop, you are practising the same craft Floyd, Hoare, Dijkstra, and Gries built: making a program’s meaning explicit, checkable, and correct by design.

Summary

IdeaThe one-line takeaway
Assertions as documentationA comment says what someone hoped; an invariant says what must be true — precise and checkable.
Documenting a loopRecord four things beside every loop: precondition, invariant, bound function, postcondition.
Guideline: intent over mechanicsDocument the WHAT, never the obvious HOW; the code already owns the how.
Guideline: honest assertionsA stale assertion misleads — update it with the code or delete it.
Guideline: spec over bodyA procedure is documented by its specification; a caller should never need to read the body.
Executable assertionsThe predicate that proves a loop can run as an assert and serve as a test oracle.
Bridge to modern practiceDesign by contract and property-based testing are Gries’ triad promoted to first-class tooling.
Floyd (1967)Flowchart assertions, verification conditions, and the bound idea for termination.
Hoare (1969)The {P} S {Q} triple and an axiom per construct — correctness as formal deduction.
Dijkstra (1975–76)Weakest preconditions and guarded commands — derive the program, do not just check it.
Gries (1981)Synthesizes and teaches the calculational discipline, with heuristics for inventing invariants.
The historical shiftFrom “verify after” (Floyd, Hoare) to “derive correct by construction” (Dijkstra, Gries).
What outlives the notationThink in invariants, let the spec lead, prove termination, keep assertions living.
Related pages in this series: Foundations for the logic these assertions are written in, The iterative command for the invariant-and-bound machinery, and Developing programs for inventing the invariants that become your documentation.