---
title: 'Documentation Review'
source: 'https://academia.sh/en/courses/code-review/documentation-review'
course: 'Code Review and Team Process'
language: en
updated: '2026-08-17T18:10:45+00:00'
license: 'CC BY-SA 4.0'
---

# Documentation Review

Closing the five axes one at a time always loses the size of the closed class — 3, 6, 3, 3, and 7 defects; closing the documentation axis drops found from 22 to 19, and two unwritten-requirement defects sitting in the same chunk keep being missed even with the axis open.

The previous three lessons measured three axes, and all three had code as their object:
signature, body, test. The fourth axis's object sits outside the code. When a change
alters a behavior, the text **describing** that behavior has to change too; if it does
not, what is left is a text that contradicts what the code says.

This lesson measures that axis, and it widens the measurement by one step: the five
axes are closed one at a time, and each closure's loss is placed side by side. Then the
documentation axis's own particular limit is measured — the difference between a
written document being wrong and a behavior never having been written down at all.

## The Change's Reflection in Documentation

The kinds of text the documentation axis looks at are separate, and not all of them are
found in the same change. Comment lines next to the body, the public interface's
reference text, the repository's introduction file and setup instructions, sample code
snippets — each rests on a behavior, and becomes invalid once that behavior changes.

The distinction between document types, reader definitions, and writing discipline are
the subject of the Technical Writing and Documentation course and are not repeated here.
The only thing this lesson measures is **whether documentation is looked at in review,
and what is missed when it is not.**

The documentation axis's distinguishing trait is that its finding is discovered through
a contradiction. The other axes look at a single text: interface looks at the signature,
implementation at the body, test at the assertion. The documentation axis compares
**two texts** — what the code does and what the text says it does. This is why a
documentation defect cannot be found by looking only at the documentation file; reading
the documentation correctly requires having read the code too.

This need to compare makes the documentation axis look expensive, though it can be made
cheaper. What the code does has already been read on the interface and implementation
axes; the documentation axis lays the second text on top of that same reading. Carrying
two axes together in the same reading is the practical counterpart of the
additivity measured in the third lesson — axes do not eat into one another's share and
can be run together.

## Three Findings of the Documentation Axis

**Stale text.** The behavior has changed, the sentence describing it has not. The text
is grammatically flawless and was once true; today it is wrong. Of the three, this is
the one most often missed, because the text itself is **invisible** inside the change —
since it did not change, it does not appear in the change.

**Missing text.** The change introduces a new public name or a new configuration
option, but no text describing it was added. This finding's trace is inside the change:
the added name is visible, the description that should have been added alongside it is
not.

**Contradicting text.** The same behavior is described in two separate places, and one
was updated. Whichever the reader looks at, they believe it; only someone reading both
at once sees that they contradict.

```text
# taught review-comment example, not executed

stale          Function's description line says "returns empty list"; the
               body now returns an empty value. Description needs to be
               updated to match the new behavior.

missing        New configuration option was added, no counterpart in the
               setup instructions. Option needs to be added to the
               instructions along with its default value.

contradicting  Example in the introduction file uses the old call form,
               the reference text describes the new form. One of the two
               texts needs to be updated to match the other.
```

This block is an example and is not run; the lesson's numbers come only from the
measurement block.

## Why the Documentation Axis Closes

Of the five axes, documentation is the one that closes most often. The reasons are
outside the setup, but they change how the table should be read.

**Documentation is left for last.** The change's body and tests are ready,
documentation "will be written after merge." In this case review has not closed the
axis, it has postponed it; for the measurement, the two are the same, because no
documentation defect is found in that round.

**A documentation finding is not treated as a blocker.** An implementation finding
stops a merge; a documentation finding often does not — the finding is written and the
change merges anyway. The table does not carry this distinction: a defect found is
found, whether it closes is the next topics' variable.

**Documentation sits outside the change's scope.** The comment line next to the body is
inside the change; the introduction file, the setup instructions, or the reference text
may sit somewhere else, even in a different repository. Text outside the scope never
reaches review at all, and no finding is produced even with the axis open.

All three are manageable decisions, and the number the table gives is the price of
those decisions: closing the documentation axis means **3** defects are missed that
round. What is useful is not the size of the number but knowing in advance **which
class** the missed defects will belong to.

## The Measurement's Assumptions

- **RA31** — The shared setup is unchanged: 600 lines, 12 chunks, 24 defects, attention
  12 chunks. There is no unread chunk; every missed defect's cause is an axis not
  looked at.
- **RA32** — In the first part, every row is a single reviewer's reading, and the only
  difference between rows is the closed axis. The first row closes no axis.
- **RA33** — The "loss" column is the found count with all five axes open minus that
  row's found count.
- **RA34** — The class breakdown of missed is the count, by class, of defects not found
  in that row, written in descending order.
- **RA35** — A documentation defect is a **written** text in the change contradicting
  the code. The setup keeps not where this text sits, but which chunk it falls into.
- **RA36** — An unwritten-requirement defect is the defect of what was never written at
  all; falling into a chunk does not make it visible — the chunk only marks where the
  defect belongs.
- **RA37** — When the documentation axis is open, only the documentation class is
  found; the sixth class is on no axis's search list, and this measurement does not
  change that.
- **RA38** — Resolution at the defect level is **1/24 = 0.042**, at the chunk level
  **1/12 = 0.083**.

## Measurement

```python
"""Documentation review: what escapes when an axis is closed.

Part 1 - the five axes closed one at a time; found, missed, and the class breakdown of missed.
Part 2 - the split between a written document's defect and a never-written requirement.
"""
SEED = 20260815
AXES = ("interface", "implementation", "test", "documentation", "style")
UNWRITTEN = "unwritten requirement"
CLASSES = AXES + (UNWRITTEN,)
ATTENTION = 12
CHUNK = 50


def rng(seed):
    state = seed % 2147483646 + 1

    def draw(n):
        nonlocal state
        state = (state * 48271) % 2147483647
        return state % n
    return draw


def change(lines, defect_count=24, seed=SEED):
    draw, defects = rng(seed), []
    chunk_count = max(1, lines // CHUNK)
    for i in range(defect_count):
        defects.append({"no": i + 1, "class": CLASSES[draw(6)],
                         "chunk": draw(chunk_count)})
    return {"lines": lines, "chunks": chunk_count, "defects": defects}


def review(d, axes, attention=ATTENTION):
    read = set(range(min(attention, d["chunks"])))
    return {k["no"] for k in d["defects"]
            if k["class"] in axes and k["chunk"] in read}


FULL = set(AXES)
d = change(600)

print(f"{'closed axis':<16s} {'open':>4s} {'found':>7s} {'missed':>6s} "
      f"{'loss':>5s} {'missed class breakdown':<40s}")
full = review(d, FULL)
for a in ("-",) + AXES:
    open_axes = FULL if a == "-" else FULL - {a}
    b = review(d, open_axes)
    missed = [k for k in d["defects"] if k["no"] not in b]
    breakdown = {}
    for k in missed:
        breakdown[k["class"]] = breakdown.get(k["class"], 0) + 1
    text = ", ".join(f"{s} {n}" for s, n in
                      sorted(breakdown.items(), key=lambda x: -x[1]))
    print(f"{a:<16s} {len(open_axes):4d} {len(b):7d} {len(missed):6d} "
          f"{len(full) - len(b):5d} {text:<40s}")

print()
documentation = [k for k in d["defects"] if k["class"] == "documentation"]
unwritten = [k for k in d["defects"] if k["class"] == UNWRITTEN]
print(f"{'class':<20s} {'defects':>7s} {'chunks':<14s} {'found by documentation axis':>28s}")
print(f"{'documentation':<20s} {len(documentation):7d} "
      f"{str(sorted(k['chunk'] for k in documentation)):<14s} {'yes':>28s}")
print(f"{UNWRITTEN:<20s} {len(unwritten):7d} "
      f"{str(sorted(k['chunk'] for k in unwritten)):<14s} {'no':>28s}")
for name, open_axes in (("documentation open", FULL), ("documentation closed",
                                                        FULL - {"documentation"})):
    b = review(d, open_axes)
    missed = [k for k in d["defects"] if k["no"] not in b]
    print(f"{name:<22s} documentation missed "
          f"{sum(1 for k in missed if k['class'] == 'documentation')}, "
          f"unwritten missed "
          f"{sum(1 for k in missed if k['class'] == UNWRITTEN)}")
```

```
closed axis      open   found missed  loss missed class breakdown                  
-                   5      22      2     0 unwritten requirement 2                 
interface           4      19      5     3 interface 3, unwritten requirement 2    
implementation      4      16      8     6 implementation 6, unwritten requirement 2
test                4      19      5     3 test 3, unwritten requirement 2         
documentation       4      19      5     3 documentation 3, unwritten requirement 2
style               4      15      9     7 style 7, unwritten requirement 2        

class                defects chunks          found by documentation axis
documentation              3 [2, 2, 3]                               yes
unwritten requirement       2 [2, 10]                                  no
documentation open     documentation missed 0, unwritten missed 2
documentation closed   documentation missed 3, unwritten missed 2
```

## Closing the Five Axes One at a Time

The top table is this topic's closing table: same change, same single reviewer, the
only difference is which axis is closed.

The **loss** column confirms a single rule in all five rows: **3, 6, 3, 3, 7.** These
numbers are the class distribution itself. The number of defects lost when an axis is
closed equals the number of defects in that axis's class — neither more nor less. The
third lesson's order-independence measurement showed this going forward; this table
shows it going backward.

The size of the loss varies threefold from axis to axis. The most expensive closure is
the **style** axis: **7** defects, found drops to **15**. Next comes
**implementation**: **6** defects, found **16**. The interface, test, and documentation
axes each cost **3** defects, and all three leave found sitting at **19**.

The documentation axis looks like one of the cheapest closures in this table, and that
appearance is the lesson's warning. The table counts **how many defects are missed**; it
does not count **when the cost of a missed defect is paid.** An implementation defect
is paid once, when run. A documentation defect is paid every time it is read, and every
reader gets the same wrong information. Two numbers land in the same column but are paid
at two separate times. This distinction is outside the setup and cannot be extracted
from the table; the table only gives found and missed counts.

One row of the table overlaps exactly with the first lesson's table. When the style
axis is closed, found is **15**; in the first lesson, the "three reviewers, separate
axes" configuration also found **15**, and that panel's covered axes were interface,
implementation, test, and documentation — the four axes other than style. Two separate
measurements, two separate paths, arrive at the same number. This is not a coincidence,
it is the result of additivity: whatever number of people the same set of axes is spread
across, it finds the same set.

The table's practical use is a price list. An axis closing is often not a decision
explicitly made; it happens on its own, from no one being assigned to that axis or that
axis's finding not being treated as a blocker. The table gives that decision's price in
advance, and the prices are not equal: closing the style axis costs **7**, implementation
**6**, the others **3** each. The choice between "open one more axis" and "add one more
person" sits inside the same measurement too: the second decision's return was measured
at **0** in the third lesson, the first's is between **3 and 7** here.

The breakdown column on the right carries the same two words in every row: **unwritten
requirement 2.** It stands there in all six rows; no axis removes it.

## A Written Document and an Unwritten Requirement

The bottom table places these two classes side by side and shows where the difference
is **not**.

The **documentation** class's three defects sit in chunks **2, 2, and 3**. The
**unwritten requirement** class's two defects sit in chunks **2 and 10**. The two
classes' defects meet in the **same chunk**: chunk two holds both two documentation
defects and one unwritten-requirement defect. Same fifty lines, same reading, same
reviewer.

When the documentation axis is open, **documentation missed is 0**; the two
documentation defects in the second chunk are found. The unwritten-requirement defect in
that same chunk is not found, and **unwritten missed stays at 2**. When the
documentation axis is closed, documentation missed climbs to **3**, and unwritten missed
is still **2**.

The difference is therefore not a difference of **location**. The two defects sit in the
same chunk and the reading passes over both. The difference is **whether there is a text
to look at.** In the documentation defect, a written sentence exists and can be compared
against the code; in the unwritten-requirement defect, no sentence to compare ever got
written.

This is the documentation axis's most confused trait. When it is open, a review saying
"documentation was looked at too" is correct — but what was looked at is **what was
written**. An unwritten requirement is outside the documentation axis too, because the
documentation axis, in the end, also compares two texts, and if one of the two texts
does not exist, no comparison can be made. All five axes stay inside the same
definition: **review looks at what was written.**

This gives the documentation axis's boundary case. If a behavior was never documented at
all, is this a documentation defect or an unwritten requirement? The distinction looks
at whether the text was **expected**. If the change introduces a new public name, that
name's documentation is expected, and its absence falls into the documentation class —
this is exactly the second finding type, and the documentation axis finds it. In
contrast, a case the change never addresses at all produces no text the documentation
axis would expect, and stays in the sixth class.

The boundary's practical counterpart is a single sentence: **the documentation axis asks
whether what the change introduces is documented; it does not ask what it failed to
introduce.** The thing that asks the second question is not review, it is the decision
that sets the change's scope.

## Summary

- The documentation axis compares not one text but **two**: what the code does and what
  the text says it does. Its finding is a contradiction and cannot be found by reading
  the documentation alone.
- There are three finding types: **stale text** (invisible in the change because it did
  not change), **missing text** (the added name has no counterpart), **contradicting
  text** (two places describe two separate behaviors).
- The loss from closing an axis equals its class size: **3, 6, 3, 3, 7.** The most
  expensive closure is the style axis, dropping found to **15**.
- Closing the documentation axis drops found from **22** to **19**. The table counts
  missed defects, not when the cost is paid: a documentation defect is repaid every time
  it is read.
- Documentation defects sit in chunks **2, 2, 3**, unwritten-requirement defects in
  chunks **2 and 10**; both are present in the second chunk. With the documentation axis
  open, documentation missed is **0**, unwritten missed stays **2**.
- The difference is not location but the presence of a text: a documentation defect has
  a sentence to compare, an unwritten requirement does not.

## Next Step

The table's most expensive row is the style axis: closing it misses **7** defects and
found drops to **15**. This is the five axes' most crowded class and, at the same time,
the one requiring the least information to see. The two traits together open a separate
question: does this axis require a human's reading at all. The next lesson measures the
style axis handed off to a tool — how the total found changes, what the class
distribution of the human's finding becomes, and how many reviewers it takes to produce
the same result.
