Lesson 08 / 15
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.
Contents
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.
# 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.
# 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
"""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.
To keep your progress and take notes, Log in
My notes
Log in to take notes.