Skip to content
academia.sh

Lesson 09 / 15

Disagreement Resolution

When six of twenty-two findings turn into a design disagreement, having no rule produces 6 review rounds and 36 units of wait and leaves 8 defects in the code; tying the decision to a criterion takes 3 rounds and 18 units and leaves only 2.

Contents

The previous lesson measured how a review comment is written and found a single source: information absent from the note got asked back in the next review round. Actionable writing closed in 2 review rounds, writing with no closing criterion in 4. The measurement’s assumption was this — given enough exchanges, a note closes.

This assumption is not true for every note. In some findings what is missing is not information but a decision: two designs are defended for the same code element, both work, both pass their tests. In a note like this, adding exchanges does not move it closer to closing; the same two justifications get rewritten every review round. This lesson’s question is: how many review rounds does an unresolved finding get carried for, and what does the rule that resolves it add to wait.

Disagreement Versus Missing Information

Two notes look alike but behave differently, and the distinction can be made mechanically. The test is one question: does either side hold the information that would close the note?

If it does, the note is a missing-information note. The author knows what should happen on empty input, or the reviewer knows why the signature broke; the information crosses over in one exchange and the note closes. The previous lesson’s entire measurement was in this class.

If it does not, the note is a design disagreement. What is missing is information nobody has: which design is better cannot be settled with the criteria on hand. In a note like this, an exchange carries no information, only repeats justifications. The measurement will count the cost of this repeating.

Which axes disagreement shows up on is not random either. Once the style axis is handed off to a tool, no argument remains; whether a test covers a case or a documentation line exists is settled by observation. Disagreement mainly shows up on the interface and implementation axes, because these two axes have more than one defensible solution. The measurement draws disagreements only from these two classes.

Organizational decision-making arrangements, authority distribution, and team structure are not this lesson’s subject; those were established in the Process, Team, and Delivery course. What is measured here is review’s own numbers: review round, wait, and defects left in the code.

Four Resolution Rules

The measurement runs the same 22 findings through four rules. The rules substitute for one another; a team picks one ahead of time and applies it once disagreement shows up.

No rule. Disagreement is re-argued every review round. Since the argument has no closing mechanism, the change fills the most review rounds it can stay open for and the disagreements stay open.

Tied to criterion. The argument is taken to a written criterion: a rule in the interface contract, a measured number, whether a test passes. The criterion is independent of either side’s preference, and the decision follows from it.

Owner decides. If code ownership is defined, the owner gives the decision for a disagreement a criterion cannot resolve. The decision arrives in one exchange, but the owner is not inside the review; bringing them into the process charges extra wait.

Separate work item. The disagreement is pulled out of this change and moved to its own work item. The change closes immediately; the design argument continues, and the defect is not fixed in this change.

# taught example note, not executed, not a measurement

disagreement
  Two designs are defended for the same function: collecting the options
  into a single dictionary parameter, or leaving them as separate named
  parameters. Both writings work, and both writings pass their tests.

tied to a criterion
  The decision criterion is the backward-compatibility rule in the
  interface contract: existing calls must keep working unchanged when a
  new option is added. The dictionary parameter satisfies this rule;
  separate parameters change the signature on every addition. The note
  closes when the dictionary parameter is chosen and written to the docs.

The example shows what tying to a criterion does: what ends the argument is not either side being persuaded, it is where the decision gets made changing. The decision now comes from the contract, not the note, and the note gains a closing criterion that passes the previous lesson’s test.

The measurement’s assumptions:

  • RC13 — The configuration is fixed: one reviewer, five axes, 600 lines, attention 12 chunks. Found is 22, missed is 2, and both missed are unwritten requirement. The resolution rule does not change these numbers.
  • RC14 — Disagreement can arise only among findings in the interface and implementation classes. Which of the 9 notes found in these classes turn into disagreement is chosen with rng.
  • RC15 — A non-disagreement note is written actionably and closes in 2 exchanges. The previous lesson’s best regime is taken as the baseline in this lesson.
  • RC16 — With no rule, disagreement never closes: it is re-argued every review round. Tied to criterion and owner decides close it in 3 exchanges; separate work item closes it in 2 exchanges but does not remove the defect from the code.
  • RC17 — One review round adds 6 units to wait. Owner decides additionally adds 6 units of owner wait: the owner is outside the review and has to enter the queue.
  • RC18 — A change can stay open for at most 6 review rounds. Defects of notes still open at that limit stay in the code.
  • RC19 — The set’s resolution is 1/24 = 0.042 at 24 defects; wait is measured per unit, and a difference smaller than one review round cannot be defended with this set.

Measurement

"""Disagreement resolution: same 22 findings, four resolution rules.

Part 1 - which findings turn into disagreement.
Part 2 - how many review rounds, how much wait, what each rule leaves in the code.
"""
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
CAP = 6                # most review rounds a change can stay open
CONTESTABLE = ("interface", "implementation")   # classes disagreement can occur in
ACTIONABLE = 2          # exchanges to close a non-disagreement note
# exchanges is None if disagreement is re-argued every round and never closes.
RULES = {
    "no rule": {"exchanges": None, "owner": 0, "defers": False},
    "tied to criterion": {"exchanges": 3, "owner": 0, "defers": False},
    "owner decides": {"exchanges": 3, "owner": ROUND_WAIT, "defers": False},
    "separate work item": {"exchanges": 2, "owner": 0, "defers": True},
}


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=CAP, 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 = [k for k in d["defects"] if k["no"] in review(d, FULL)]
missed = [k for k in d["defects"] if k not in found]
r = rng(SEED)
for k in found:
    k["disagreement"] = k["class"] in CONTESTABLE and r(2) == 0
dis = [k for k in found if k["disagreement"]]

print(f"found {len(found)}, missed {len(missed)}, its unwritten class "
      f"{sum(1 for k in missed if k['class'] == UNWRITTEN)}")
print(f"found in contestable class "
      f"{sum(1 for k in found if k['class'] in CONTESTABLE)}"
      f", turned to disagreement {len(dis)}")
print("disagreement classes: " +
      ", ".join(f"{s} {sum(1 for k in dis if k['class'] == s)}"
                for s in CONTESTABLE))
print()
print(f"{'rule':<20s} {'round':>5s} {'wait':>7s} {'note-round':>10s} "
      f"{'disagreement note-round':>24s} {'closed':>6s} {'deferred':>8s} "
      f"{'left in code':>13s}")
for name, rule in RULES.items():
    needed = [rule["exchanges"] or CAP + 1 if k["disagreement"]
              else ACTIONABLE for k in found]
    rnd, wait, nr, open_ = cycle(needed)
    dis_nr = sum(min(g, rnd) for g, k in zip(needed, found)
                 if k["disagreement"])
    deferred = len(dis) if rule["defers"] else 0
    closed = len(found) - open_ - deferred
    print(f"{name:<20s} {rnd:5d} {wait + rule['owner']:7d} {nr:10d} "
          f"{dis_nr:24d} {closed:6d} {deferred:8d} "
          f"{open_ + deferred + len(missed):13d}")
found 22, missed 2, its unwritten class 2
found in contestable class 9, turned to disagreement 6
disagreement classes: interface 1, implementation 5

rule                 round    wait note-round  disagreement note-round closed deferred  left in code
no rule                  6      36         68                       36     16        0             8
tied to criterion        3      18         50                       18     22        0             2
owner decides            3      24         50                       18     22        0             2
separate work item       2      12         44                       12     16        6             8

What an Unresolved Finding Carries

Of the twenty-two findings, 9 are in the contestable class, and 6 of those turn into disagreement: 1 on interface, 5 on implementation. The remaining 16 notes are missing-information notes and, in the previous lesson’s baseline regime, close in 2 exchanges.

With no rule, the table collapses. The process climbs to 6 review rounds and 36 units of wait; in the end, 16 notes close, 6 disagreements stay open, and defects left in the code are 8. Of the eight, 2 are the class no axis searches for — unwritten requirement, a defect review could never see. The remaining 6 were seen, written, argued, and stayed in the code because they were never resolved.

The disagreement note-round column gives where the cost comes from in a single number: 36. Each of the six disagreements stayed open across all six review rounds; 6 × 6 = 36. Of the total load’s 68, 36 — more than half — is repetition that produced nothing. All the missing-information notes had already closed by the second round; the four rounds from the third to the sixth were spent on the six disagreements alone.

This pair of numbers is familiar. The shared fixture’s worst axis configuration also produced 6 review rounds / 36 wait. Two different causes, the same bill: there, a narrow axis list kept defects from being seen in the first round; here, seen defects cannot be resolved. In both cases, what lengthens the review round count is the amount of work that does not close in the first round.

This match also carries a warning about reading metrics. Looked at from outside, two processes show the same two numbers; which one comes from a narrow axis list and which from an unresolved argument is not written in the table itself. The same bill requires two different fixes.

Two Ways to Resolve

Tied to criterion is the most balanced row: 3 review rounds, 18 units of wait, 22 closed, and 2 left in the code. Both are unwritten requirement — the class review can never reach. This is the one rule that delivers everything review is able to find.

Its cost is negative relative to no rule: it falls from 36 units to 18. A resolution rule does not raise wait, it lowers it. This is the measurement’s most practical conclusion — disagreement resolution is not an extra process, it is what removes the process’s most expensive line item.

Owner decides gives the same result: 22 closed, 2 left in the code. The difference is in the wait column: 24 units, 6 more than tied to criterion. The gap is exactly one review round, and its cause is structural — the owner is not inside the review and has to be brought in. If this rule is reserved for disagreements with no criterion or with conflicting criteria, the extra 6 units get paid only where they are actually needed.

Which Criterion Binds

Tying to a criterion works because of a quality of the criterion: it is independent of either side’s preference and it is written in advance. If either condition is missing, the argument turns into an argument about the criterion, and the table’s 18 units do not come back.

Binding criteria are well known. The interface contract closes backward-compatibility questions; what the contract says is written before the argument, not during it. A measured number closes performance disagreements: which of two designs is faster is not a preference, it is a measurement result. Whether a test passes closes behavior disagreements. A rule written in the documentation — naming convention, layer boundary, dependency direction — closes structural disagreements.

Non-binding criteria are also well known, and what they share is being produced at the moment of the argument: personal habit, unmeasurable adjectives like “cleaner” or “more readable,” and the observation that it was done differently elsewhere. Clean code principles were established in the Clean Code course as a list of rules there; for them to close a disagreement, that list has to be written and binding on the team. If it is not written, it is not a criterion, it is a second preference.

A practical conclusion follows: a criterion is written before the argument, not during it. The measurement’s 36-unit bill is the bill for a missing criterion — and that gap cannot be closed from inside the review.

Deferral Is a Decision

Separate work item is the table’s fastest row: 2 review rounds, 12 units of wait, total load 44 note-rounds. These numbers are not a coincidence; a process where disagreements never get argued gives the same numbers as the previous lesson’s actionable regime.

The price paid is in the two right columns: 6 deferred and 8 defects left in the code. Deferred notes are not lost; they stand in their own work items — but once that change merges, the code carries those 6 defects on it. In terms of left-in-code count, deferral and no rule give the same result: 8 in both. The difference is that deferral does this with 24 fewer units of wait and leaves a record.

This is why deferral is not an escape but a decision — but only when the record is actually kept. Deferral done without opening a work item lands in the same place as no rule, in the measurement: 6 defects in the code, no record anywhere. Deferral’s limit follows from this too: the disagreement itself can be deferred, but if the defect underneath the disagreement is a bug, it cannot be. Which form a design takes can wait; dividing by zero on empty input cannot.

Summary

  • A design disagreement is separated from a missing-information note mechanically: if either side holds the information that would close it, the note is missing-information; if neither does, it is disagreement.
  • Disagreement shows up on the interface and implementation axes; in the measurement, 6 of the 9 findings in the contestable class turn into disagreement.
  • With no rule, the process costs 6 review rounds / 36 units of wait, 16 notes close, and 8 defects stay in the code. Of the total load’s 68 note-rounds, 36 are nothing but the six disagreements repeating.
  • Tied to criterion closes all 22 notes in 3 rounds / 18 units; the 2 left in the code are the unwritten requirement class no axis searches for. Owner decides gives the same result but costs 24 units, since the owner is outside the review.
  • Separate work item is the fastest row (2 rounds / 12 units) but leaves 8 defects in the code — the same number as no rule, with far less wait and a record.

Next Step

Three lessons produced the same two numbers for three separate reasons: review round and wait. Change size set the chunks read, note style set the closing speed, the resolution rule set whether it closes at all. The next lesson takes these two numbers directly as metrics: what axis assignment itself does to review round and wait, how the health of the review queue is read, and why these metrics are not reported per person.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ Esc navigate · open · close