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

# Feedback Language

The same 22 findings close in 2 review rounds when written actionably, in 4 rounds when no closing criterion is written; in a change merged at the third round, the criterionless style leaves 22 of 22 notes open.

The previous lesson measured the limit of a reviewable change: because attention stayed
fixed at 12 chunks, the same 24 defects were found 22 times at 100 lines and 2 times at
2400. When a change is a readable size, the reviewer finds nearly everything they can.

But a found defect is not a fixed defect. One thing alone closes the gap between them:
the **review comment**. This lesson's question is not the comment's politeness but its
**function** — when the same finding is written two separate ways, how many review rounds
does it take for the defect to leave the code, and which writing leaves it there.

## The Subject of a Note

This course's boundary takes its most concrete form here: **the subject of a review
comment is the change.** The rule is not an etiquette rule; it is a rule of function, and
its justification can be measured.

The test is mechanical. The comment's sentence is looked at and one question is asked:
**what is its subject?** If the subject is a code element — a function, a parameter, a
condition, a test, a documentation line — the comment stands as written. If the subject
is a role, the comment is rewritten and its subject replaced with the code element it is
actually talking about. No content is lost; the sentence now points at the thing that can
be fixed.

The justification is this: a note's **closing criterion** is found in the code. A note in
the form "this condition divides by zero on empty input" closes once that condition
changes, and its closing is visible to everyone. A sentence whose subject is a role has
no such criterion — what change closes it is not written, so it cannot close and it
circulates through review rounds. The measurement will count the cost of this
circulating.

The same boundary applies to the measurement itself. What is counted in this lesson is
not who wrote how many notes but **how many review rounds a given writing takes**. The
subject measured is, again, the change.

## Three Ways to Write a Note

The same finding can be written three ways, and all three are about the change. The
difference is what the note hands the reader.

```text
# taught example note, not executed, not a measurement

observation
  average() misbehaves when given an empty list.

criterionless request
  the empty-list case should be handled more robustly.

actionable
  average() divides the total by the length when given an empty list, and
  raises a division-by-zero error. It should either return 0.0 in this case
  or raise a defined error. Whichever is chosen, the matching case must be
  added to the test file; the note closes when that test passes.
```

**Observation** says what was seen and stops there. It is correct, it points to the spot,
but it does not write what is being asked for; the author has to ask a question and the
reviewer has to answer it. This is one exchange and costs one review round.

**Criterionless request** asks for a change but does not say when the request is met.
"More robust" is not a criterion: the author sends a fix, the reviewer sees it is not
what they meant, the note reopens. This is two exchanges.

**Actionable** note carries three things at once: **what was seen**, **what should
change**, **what closes it**. The third is the most-skipped and the highest-paying. When
the closing criterion is written inside the note, whether the fix is correct needs no
discussion.

```text
# taught example note, not executed, not a measurement

observation
  the new parameter has been added in the middle of the signature.

actionable
  because the threshold parameter was added in the middle of the signature,
  calling code that relies on position silently shifts. The parameter
  should move to the end of the signature and be given a default value, so
  existing calls keep working unchanged. The note closes when the
  signature is at the end and has a default.
```

The second example shows that being actionable is a matter of **information**, not
style. An actionable note is longer because it says **more**: the two pieces observation
does not carry, the requested change and the closing criterion, are written there.
Brevity is not a virtue here; missing information comes back in the next review round.

## A Note Written as a Question

Some notes are written as questions, and this form is not good or bad on its own. What
decides is whether the question **names the situation**.

A question that names the situation is actionable: if which input, which code element,
and which result is expected are written, the author can close the question with either a
fix or a one-sentence justification. A question that does not name the situation is in
the same class as observation — it adds one exchange, because which situation is being
discussed has to be pinned down first.

The question form's real function lies elsewhere: the note itself might rest on a
**wrong assumption**. When such a note is written as a question, one answer corrects the
assumption and it costs one exchange; when the same assumption is written as a firm
request, a wrong fix gets sent first and it costs two exchanges. The measurement shows
this directly: the criterionless-request regime's two extra rounds come from the
misunderstanding only being noticed after the fix arrives. When something is unknown,
writing that it is unknown saves a round.

The measurement's assumptions:

- **RC7** — The configuration is fixed: one reviewer, all five axes, 600 lines, attention
  12 chunks. Found is 22, missed is 2, and both missed are **unwritten requirement**.
  Note style does not change these numbers; what is measured is how the found defects
  close.
- **RC8** — A note is written for every found defect. The note's style sets the number
  of exchanges needed for the defect to leave the code: actionable **2**, observation
  **3**, criterionless request **4**.
- **RC9** — One review round consumes one exchange of every currently open note at once.
  Notes are handled together, not one at a time; this is why the **slowest note** sets
  the review round count.
- **RC10** — The time a review round adds to wait is **6 units**, independent of style.
  **Note-round** counts one unit for every review round a note stays open; it is the
  count of total load.
- **RC11** — In the mixed regime, every note's style is chosen with `rng`; it is a sample
  of how notes end up distributed when no rule is enforced.
- **RC12** — In the second measurement, the change merges at the third review round.
  Defects whose notes are still open at that point stay in the code and add to the 2
  defects never found.

## Measurement

```python
"""Feedback language: same 22 findings, four note styles.

Part 1 - how many review rounds each style closes in, wait and note-round carried.
Part 2 - which notes stay open if the change merges at the third round.
"""
SEED = 20260815
AXES = ("interface", "implementation", "test", "documentation", "style")
UNWRITTEN = "unwritten requirement"
CLASSES = AXES + (UNWRITTEN,)
ATTENTION = 12
CHUNK = 50
ROUND_WAIT = 6        # units of wait one review round adds
# Exchanges needed to close a note.
STYLES = {"actionable": 2, "observation": 3, "criterionless request": 4}


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

    def r(n):
        nonlocal d
        d = (d * 48271) % 2147483647
        return d % n
    return r


def change(lines, defect_count=24, seed=SEED):
    r, defects = rng(seed), []
    chunk_count = max(1, lines // CHUNK)
    for i in range(defect_count):
        defects.append({"no": i + 1, "class": CLASSES[r(6)],
                         "chunk": r(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}


def cycle(needed, cap=6, wait_per_round=ROUND_WAIT):
    """Every review round consumes one exchange of every open note."""
    open_, rnd, wait, note_round = list(needed), 0, 0, 0
    while open_ and rnd < cap:
        rnd += 1
        wait += wait_per_round
        note_round += len(open_)
        open_ = [g - 1 for g in open_ if g > 1]
    return rnd, wait, note_round, len(open_)


FULL = set(AXES)
d = change(600)
found = sorted(review(d, FULL))
missed = [k for k in d["defects"] if k["no"] not in set(found)]
r = rng(SEED)
mixed = [list(STYLES)[r(3)] for _ in found]
REGIMES = {name: [name] * len(found) for name in STYLES}
REGIMES["mixed"] = mixed

print(f"found {len(found)}, missed {len(missed)}, its unwritten class "
      f"{sum(1 for k in missed if k['class'] == UNWRITTEN)}")
print("mixed regime distribution: " +
      ", ".join(f"{a} {mixed.count(a)}" for a in STYLES))
print()
print(f"{'note style':<22s} {'round':>5s} {'wait':>6s} {'note-round':>10s} "
      f"{'unclosed':>8s} {'left in code':>12s}")
for name, notes in REGIMES.items():
    rnd, wait, nr, open_ = cycle(STYLES[n] for n in notes)
    print(f"{name:<22s} {rnd:5d} {wait:6d} {nr:10d} {open_:8d} "
          f"{open_ + len(missed):12d}")

print()
print("if the change merges at the third review round")
print(f"{'note style':<22s} {'round':>5s} {'wait':>6s} {'closed':>6s} "
      f"{'unclosed':>8s} {'left in code':>12s}")
for name, notes in REGIMES.items():
    rnd, wait, nr, open_ = cycle((STYLES[n] for n in notes), cap=3)
    print(f"{name:<22s} {rnd:5d} {wait:6d} {len(notes) - open_:6d} "
          f"{open_:8d} {open_ + len(missed):12d}")
```

```
found 22, missed 2, its unwritten class 2
mixed regime distribution: actionable 2, observation 11, criterionless request 9

note style             round   wait note-round unclosed left in code
actionable                 2     12         44        0            2
observation                3     18         66        0            2
criterionless request      4     24         88        0            2
mixed                      4     24         73        0            2

if the change merges at the third review round
note style             round   wait closed unclosed left in code
actionable                 2     12     22        0            2
observation                3     18     22        0            2
criterionless request      3     18      0       22           24
mixed                      3     18     13        9           11
```

## Where the Round Comes From

The top table's first columns give the cost of the same 22 findings. In the actionable
style, the process closes in **2** review rounds and **12** units of wait. Observation is
**3** rounds / **18** units, criterionless request **4** rounds / **24** units. Found
never changes: every regime has 22 findings, 2 missed, both missed unwritten requirement.
**Note style does not change what is found; it changes when the found leaves.**

What delays the closing is written into the number itself. Observation adds one exchange
because the missing information — what is being asked for — gets asked and answered in
the next round. Criterionless request adds two exchanges because the missing information
only surfaces once a wrong fix arrives. **Every piece of missing information asks for
itself back in a review round.**

The note-round column shows the load better: **44**, **66**, **88**. Criterionless
writing produces exactly twice the load of actionable writing, and all of it is carrying
the same 22 findings. There is no difference in the code; the difference is only in what
the notes say.

The mixed regime exposes how the review round count is actually set. The distribution is
2 actionable, 11 observation, 9 criterionless request; total load is **73** note-rounds,
roughly the average of the three regimes. Review round count, in contrast, is **4** — the
slowest regime's count. **In notes handled together, the slowest note sets the round
count, not the average.** Even if thirteen of twenty-two notes are written flawlessly,
the remaining nine still carry the process to four rounds.

## Merged Before the Notes Close

The bottom table adds a real constraint: a change does not stay open forever. Merged at
the third review round, every regime pays the same wait but delivers different things.

Actionable writing had already finished at the second round; the limit does not touch it.
Observation writing closes exactly at the third round. In the criterionless-request
regime, closed is **0**, unclosed is **22**, and defects left in the code are **24** —
all of them. This is the table's harshest row: the review **found** 22 of 24 defects and
**delivered none**. A process measured by found count alone would call this review a
success.

The mixed regime gives a more realistic result: **13** closed, **9** unclosed, **11**
left in the code. Of the eleven, **2** are the class no axis searches for — unwritten
requirement, never something review could see. The remaining **9** were seen, written,
and failed to close because of **how they were written**. The difference between the two
losses matters: the first is review's limit, the second is a loss review **produced
itself** and could have eliminated entirely.

The set's resolution is **1/24 = 0.042** at 24 defects. A **9**-defect difference is
twenty times that, and sits comfortably inside the measurement band. So does the drop in
closed count from 22 to 13.

The bottom table's wait column is also notable: observation, criterionless request, and
mixed all spend **18** units. Three processes hitting the limit consume the same time;
the only thing that differs is how many defects actually left the code by the end of
it — **22**, **0**, and **13**, respectively. **A reading that looks only at wait cannot
tell these three regimes apart.**

## Habits That Make a Note Actionable

Three habits follow directly from the measurement, and all three add one piece of
information into the note.

**Write the closing criterion.** A note should say which observable state it closes on: a
test passing, a signature reaching a specific form, a documentation line being added. The
criterionless-request regime's two extra rounds come from here.

**Separate the request from the observation.** Observation points to the spot, request
gives direction. When both are not in the same note, whatever is missing gets asked in
the next review round.

**Mark what is not binding.** Not every note is a blocker; some notes are suggestions
that can merge unfixed. Stating this in the note itself keeps non-binding notes from
adding to the review round count — the mixed regime's rise to four rounds came exactly
from a few slow notes.

**Write the same finding once.** In a change where one pattern repeats in ten places,
writing a separate note for every repeat grows the note count but not the information;
all ten notes wait for the same exchange. Writing the pattern once and stating its
scope — which files, which elements — gives the same closing criterion in a single note.

What the four habits share is that all of them make the note **more informed**. The
measurement had one source of slowness: information absent from the note being asked for
in the next review round. Politeness shows up in none of this table's columns; what shows
up is where the information stands.

## Summary

- A review comment's subject is the change; the test is mechanical — if the subject is
  not a code element, the note is rewritten so the code element it is discussing becomes
  the subject.
- The justification is measurable: a note's **closing criterion** is found in the code. A
  note with no criterion does not say what closes it and circulates through review
  rounds.
- The same 22 findings cost **2** rounds / **12** wait / **44** note-rounds when written
  actionably, **3 / 18 / 66** as observation, **4 / 24 / 88** as criterionless request.
  Found and missed never change: 22 and 2, both missed unwritten requirement.
- Because notes are handled together, the review round count is set by the **slowest
  note**, not the average: in the mixed regime, even with 13 of 22 notes written fast,
  the process still takes 4 rounds.
- Merged at the third review round, criterionless writing closes **0** notes and leaves
  **24** defects in the code; mixed writing closes **13**, leaving **11** in the code —
  **2** of those are the never-found class, **9** are found but unclosed.

## Next Step

The measurement assumed one thing: given enough exchanges, a note closes. This is not
always true. In some notes what is missing is not information but **agreement** — the
author and the reviewer defend different designs for the same code element, and adding
more exchanges does not bring the two sides closer. The next lesson measures how many
review rounds such a finding is carried for, and what four separate resolution rules do
to wait, closed notes, and defects left in the code.
