Lesson 02 / 15
Meaning of Interface
The sets the five axes find are disjoint and the sum of pairwise intersections is 0; found drops from 22 to 19 when the interface axis is closed, and ten reviewers without an interface axis find 3 fewer defects than a single reviewer covering all five axes.
Contents
The previous lesson showed that a five-axis reading finds 22 of the 24 defects and a single-axis reading finds 6. The difference came from axis count; but the table assumed something and did not verify it: that the axes’ shares are disjoint from one another. If the five axes sum to 22, that means no defect is being counted by two axes at once.
This lesson measures that assumption, and it does the measuring on the first axis, the interface axis. Its question is this: is an interface defect really found only by the eye looking at the interface, or does it become visible from another axis too if enough people read carefully enough?
The Contract of a Public Interface
A piece of code’s public interface is the set of externally callable names and their behaviors: function signatures, type fields, return values, error forms, and the names themselves. This set is called the interface contract, because the calling side writes code trusting it.
Breaking the contract does not have to be a compile error. Versioning strategies and the classification of breaking versus non-breaking changes were established in the Web API Design course’s Errors, Versions, and Documentation topic; they are not repeated here. The only thing this lesson measures is how it is noticed in review: which axis’s eye sees the trace, and which does not.
Five Ways to Break It, and Their Trace in Review
Adding a required parameter. The trace is in the signature itself and is read inside the change. Calling code does not compile; the defect is also caught at compile time after merge. Review’s contribution here is not finding it, it is finding it early.
Changing a parameter’s meaning. The signature stays the same — same name, same type — but the expected value is different; a duration field’s unit has moved from seconds to milliseconds. The only thing visible in the change is a multiplier in the body; there is no trace in the signature.
Changing the return value’s behavior. Returning an empty value instead of an empty list, a default instead of an error, unordered instead of ordered. The trace is in the body; the calling side compiles and silently runs wrong.
Changing the class of thrown error. Catch blocks no longer match; the error escapes uncaught and the calling side does not learn this at compile time.
Removing or renaming a name. This is the most visible form and is read directly inside the change; who uses the removed name, however, is outside the change.
What the middle three forms share is this lesson’s starting point: the compiler is silent, the tests pass, and review remains as the only defense — because the tests too were updated in the same change to match the new behavior.
The Trace Is Inside the Change, Its Consequence Is Outside
The distinguishing trait of an interface defect in review is this: its trace is small, its consequence sits outside the change. What is visible in the submitted change is a single line — a signature. The code that line breaks is not inside the change; it is in other files, other repositories, or calls not yet written.
This explains why the interface axis requires a separate reading. The eye looking at the implementation reads the function’s body, and the body is correct; the eye looking at the test reads the tests, and the tests pass, because the tests were also updated to match the new signature. Seeing the trace requires comparing the signature against its previous form, and the only reading that does this is the one from the interface axis.
# taught review-comment example, not executed
subject signature of the public function
before parse(text)
after parse(text, mode)
note Second parameter added without a default; a call made with the
previous signature is no longer valid. This is a change that
cannot be published without touching calling code. Two paths
exist: give the mode parameter a default that reproduces the
previous behavior, or keep the previous signature and offer the
new behavior under a separate name.
The note points at a spot in the change, names the broken condition, and proposes two closing paths. This block is an example and is not run; the lesson’s numbers come only from the measurement block.
Three Sources Available to Review
A reading working on the interface axis can look at three sources, and their scope differs.
The change itself is the added and removed lines. The contract’s new form is here, and its previous form sits in the removed lines; comparing these two forms is the interface axis’s actual job.
The change’s title and description carry the author’s intent. The difference between “added a mode parameter” and “added a mode parameter as required” determines which question review asks. If this source is missing, the reading is forced to interpret the signature on its own.
The rest of the repository contains calling code but is not part of the change. The interface defect’s consequence is here, and review is not required to look at this source; doing so is a separate decision and generates a separate reading request.
The measurement does not model these three sources separately; if the interface axis is open, the defect is found, if closed, it is not. This simplicity in the model exists to keep what is measured in a single variable: which axis is being looked at.
The Measurement’s Assumptions
- RA9 — The shared setup is unchanged: 600 lines, 12 chunks, 24 defects, attention 12 chunks. The entire change is read; there is no unread chunk in this lesson either.
- RA10 — A defect belongs to a single class. Two classes of defect can be found on the same line, but these are two separate defects and are counted separately.
- RA11 — The intersection of two axes is the set of defect numbers both find. Because classes exclude one another, this intersection is expected to come out empty; the measurement does not assume this, it counts it.
- RA12 — The “only this axis” column is the number of defects an axis finds that none of the other four finds.
- RA13 — In panels without an interface reviewer, no reviewer carries the interface axis; the remaining four axes are distributed freely and the number of people can be increased.
- RA14 — In the “ten reviewers, no interface” panel, each of the four axes gets two people, plus two more people who carry all four together; ten readings are done in total.
- RA15 — The measurement is still single-round. Finding an interface defect later, once calling code breaks, is outside this table and is not counted as a review finding.
Measurement
"""Interface axis: does another axis find what one axis finds. Part 1 - five single-axis readings; intersection and unique-to-this-axis. Part 2 - configurations with and without the interface axis. """ 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} def panel(d, assignments, attention=ATTENTION): found = set() for axes in assignments: found |= review(d, axes, attention) return found FULL = set(AXES) d = change(600) single = {a: review(d, {a}) for a in AXES} print(f"{'axis':<15s} {'found':>7s} {'also in other four':>19s} " f"{'only this axis':>15s}") for a in AXES: other = set().union(*(single[o] for o in AXES if o != a)) print(f"{a:<15s} {len(single[a]):7d} {len(single[a] & other):19d} " f"{len(single[a] - other):15d}") union = set().union(*single.values()) intersection = sum(len(single[a] & single[b]) for a in AXES for b in AXES if a < b) print(f"union of five single readings {len(union)}, " f"sum of pairwise intersections {intersection}") print() FOUR = FULL - {"interface"} PANELS = { "one reviewer, five axes": [FULL], "one reviewer, no interface": [FOUR], "three reviewers, interface included": [{"interface"}, {"implementation"}, {"test", "documentation"}], "three reviewers, interface replaced": [{"implementation"}, {"implementation"}, {"test", "documentation"}], "ten reviewers, no interface": [{a} for a in sorted(FOUR)] * 2 + [FOUR] * 2, } print(f"{'panel':<36s} {'reviewers':>9s} {'found':>7s} {'missed':>6s} " f"{'interface missed':>17s} {'unwritten missed':>16s}") for name, assignment in PANELS.items(): b = panel(d, assignment) missed = [k for k in d["defects"] if k["no"] not in b] iface = sum(1 for k in missed if k["class"] == "interface") unwritten = sum(1 for k in missed if k["class"] == UNWRITTEN) print(f"{name:<36s} {len(assignment):9d} {len(b):7d} {len(missed):6d} " f"{iface:17d} {unwritten:16d}")
axis found also in other four only this axis interface 3 0 3 implementation 6 0 6 test 3 0 3 documentation 3 0 3 style 7 0 7 union of five single readings 22, sum of pairwise intersections 0 panel reviewers found missed interface missed unwritten missed one reviewer, five axes 1 22 2 0 2 one reviewer, no interface 1 19 5 3 2 three reviewers, interface included 3 15 9 0 2 three reviewers, interface replaced 3 12 12 3 2 ten reviewers, no interface 10 19 5 3 2
The Shares Are Disjoint
The top table’s middle column gives 0 in all five rows, and the sum of pairwise intersections is also 0. None of the five axes finds even a single defect another axis finds.
The column on the right is the mirror image of this: all of the defects each axis finds appear only in that axis — 3 in interface, 6 in implementation, 3 in test, 3 in documentation, 7 in style. Their total is 22, equal to the union of the five single readings. So an axis’s contribution does not depend on which axes are already open.
Being disjoint does not mean two defects cannot sit in the same file or the same line range; it means the classes are separate. A signature defect and a style defect can both stand on the same line of the same function, and the setup counts these as two separate defects. Two eyes reading the same text find separate things because they are searching for separate things.
The practical counterpart is exact: the number of defects lost when an axis is closed equals the number of defects in that axis’s class. There is no leftover that compensates for a closed axis. If the interface axis is closed, three defects are gone; no amount of reading added to other axes brings those three back. The bottom table tests this sentence.
Once the Interface Axis Is Closed
The bottom table’s first two rows are the measurement’s core. Same single reviewer, same change, the only difference is whether the interface axis is open: found drops from 22 to 19, missed climbs from 2 to 5, and the difference is exactly 3 interface defects. The loss is exactly the size of the class.
The third and fourth rows repeat the same measurement on a three-person panel. With the interface axis open, three reviewers find 15; when a second implementation reading replaces the person on the interface axis, that drops to 12. Reviewer count did not change, reading count did not change — the second implementation reading is even extra effort. The result is 3 fewer defects. The return on looking at the same axis twice is 0, and this measurement makes that cost visible.
The last row is the lesson’s sharpest result. Ten reviewers work without an interface axis: two people on each of the four axes, plus two more people reading all four together. Found is 19. A single reviewer covering five axes finds 22. A ten-person panel finds 3 fewer defects than a one-person panel — and the difference comes from the single axis none of the ten looked at.
The fourth row’s configuration is not made up; when a defect is missed in a review, the first decision made is often adding one more reviewer. If the added person is not told which axis to look at, they look at the most familiar one — implementation. The table gives the numeric result of that decision: the panel grew, reading count went up, found dropped from 15 to 12. The drop is not because the new person broke something; it is because the reading they replaced was on a different axis.
The difference is 3/24, that is, 0.125; given the set’s resolution is 1/24 = 0.042, this is three times that resolution and sits comfortably inside the measurement band. The number’s smallness can be misleading: each of the three missed defects is a contract break that breaks code outside the change, and it becomes visible only where review could not find it — once the calling side breaks.
There is a cheap way to open the axis that does not show up in the table: the interface axis can be opened without rereading the entire change. The lines carrying public declarations are a small part of the change, and a reading that looks at them first sees the interface class’s three defects before the others. Opening an axis is not always adding a person to the panel; sometimes it is only changing the reading order. The setup does not measure this distinction — an axis is either open or closed — but what the table says holds for both: there is no number of people that compensates for a closed axis.
The Unwritten Side of the Contract
The interface axis compares the written form of the contract. The signature, the return type, and the error class are text, and text can be read. The contract also has an unwritten side: behaviors the calling side relies on but that are stated nowhere — whether repeating the call is safe, whether the returned list’s order is stable, whether the same input always gets the same response.
When these behaviors break, the interface axis finds no trace, because there is no previous text to compare against. Its counterpart in the setup is direct: such a defect falls not into the interface class but into the sixth class — unwritten requirement — and keeps being missed even with the interface axis open. This is why the bottom table’s last column gives 2 in all five rows.
The distinction comes down to a single question: is what broke written down somewhere? If it is written, it is the interface axis’s job and is found once the axis is opened. If it is not written, it is no axis’s job, and it gets closed outside review, where the contract gets written.
Summary
- A public interface is the set of externally callable names and their behaviors; its contract can break in five separate ways, and three of them run wrong without breaking calling code.
- An interface defect’s trace inside the change is a single line, and its consequence sits outside the change; this is why it requires comparing the signature against its previous form.
- The sets the five axes find are disjoint: the sum of pairwise intersections is 0, everything each axis finds appears only in that axis, and the union of the five is 22.
- The defect lost when an axis is closed equals the size of that class: closing the interface axis drops found from 22 to 19 and raises missed from 2 to 5.
- When a second implementation reading replaces the person on the interface axis, a three-person panel finds 12 instead of 15; a ten-reviewer panel without an interface axis stays under the 22 a single reviewer covering five axes finds, at 19.
- When the unwritten side of the contract breaks, the interface axis has no text to compare; this defect falls into the sixth class and is missed even with the axis open.
Next Step
The largest single share in the table belongs to the implementation axis: 6 defects, 0.250 of the 24. The interface axis’s finding came from comparing the signature; the implementation axis’s finding has no previous form to compare against — what is looked at is the gap between what the code should do and what it does. The next lesson measures this axis: as axes are opened one at a time, what curve does the found count grow along, where does the implementation axis’s own share sit on that curve, and how much does being the single biggest finder make it, on its own, sufficient.
To keep your progress and take notes, Log in
My notes
Log in to take notes.