An agent's change adds an interface with a single implementation — how do you decide whether to keep it?
answer
- Structure invented for variation that does not exist
- Name the second implementation
- One caller, one value, one type
- Justification lives outside the change
- Cheap to add later, expensive to remove
basics
~20 sName the second implementation, caller or value the structure exists for. If it exists today or is genuinely planned, keep it; if it is only imaginable, the code is paying now for a prediction, and it comes out.
solid answer
~50 sAn interface with one implementation is a claim that something varies, so the reviewer's job is to ask what: the second implementation, the second caller, the second value a setting takes. Accept an answer that exists today or is on the plan, not one that is merely imaginable. A structure that leaves room cannot be contradicted by anything the run could see: nothing in a codebase states that a second case will never arrive. That judgement is the reviewer's. Some single-implementation structures are right anyway: a seam at a boundary you do not own, so it can be substituted under test, is variation that exists today. The asymmetry settles the rest. A missing abstraction is cheap to add when the second case appears; an unneeded one fails no check and grows more expensive as later code is written through it.
code
pseudocode · 18 linesinterface CountStrategy:
function countsFor(bin)
class ManualCountStrategy implements CountStrategy:
function countsFor(bin):
return picker.readsFor(bin)
class CountStrategyFactory:
function create():
return new ManualCountStrategy()
function submitCount(bin):
strategy = CountStrategyFactory.create()
return strategy.countsFor(bin)
# the change added all four of these for one caller.
# what the request needed:
# function submitCount(bin): return picker.readsFor(bin)go deeper
Recognise the shapes: an interface with one implementation, a factory for one type, a setting with one value. Ask what is expected to vary, and say so in the review when the honest answer is nothing.
Explain why the structure costs something even though it changes no behaviour: every read passes through a layer that absorbs nothing, and every later change is written through it.
Demonstrate judgement rather than a rule. Name the cases where a single implementation is right — a seam at a boundary you do not own, a second case already on the plan — and separate them from speculation.
Own the asymmetry: a missing abstraction is cheap to add when the second case arrives, while an unneeded one grows more expensive on its own. That asymmetry, not taste, is the argument for refusing it now.
## The shapes unrequested structure takes Over-engineering is structure invented for variation that does not exist. In a change you did not watch being made, it arrives in a small number of recognisable shapes: - an **interface with exactly one implementation**, and no second one anywhere in sight; - a **factory** that can construct one type; - a **configuration key** with one value, one reader, and nothing that ever sets it differently; - a **mode or strategy parameter** with one member, threaded through several call sites; - a **type parameter** bound to the same type everywhere it is used; - a **base type** created by moving half of one existing type into it; - an **extension point** — a callback, a hook, a registry — with nothing registered in it. Each of these is the correct answer to a question. The question is *what varies?*, and on a stock-count feature built across nine files while nobody watched, nobody asked it. ## Why the question goes unanswered on an unattended run This is worth stating as mechanism rather than as a complaint about taste. **Nothing in a codebase states an absence.** The run could see the code, the request, and whatever else it was given; none of that says "there will never be a second way of counting a bin". A structure that leaves room cannot be contradicted by anything available at the time it is written, whereas a structure that is too small is contradicted the moment a second case turns up. The asymmetry is in the evidence that was available, not in the tool's judgement — and the reviewer holds the one piece of evidence the run did not: what the team is actually going to do next. A practical consequence for reading a large change: **read the new names first.** In a change that only had to add one behaviour, every new type, key or parameter is a claim that something varies. Each claim is cheap to check, and the ones with a single user are your candidates. ## The question that decides it **Name the second one.** The second implementation, the second caller, the second value the key takes, the second mode. Then say which of three things it is: 1. **It exists today.** The structure is describing reality — keep it, and the review is over. 2. **It is on the plan**, not merely imaginable. Usually keep it, and say so in the review so the next reader knows why it is there. 3. **You can only say "something might come along."** That is a prediction, and the code starts paying for it now: every read passes through a layer that absorbs nothing. ## One implementation is sometimes right The rule is not "one implementation means delete", and a reviewer who applies it that way will strip out seams that were doing real work. | structure that earns its keep | structure that is only a prediction | |---|---| | a seam at a boundary you do not own, so the thing behind it can be substituted under test | an interface added because interfaces are good practice | | a second implementation that exists in this change or the next one | a second implementation that is merely imaginable | | a setting some deployment genuinely sets differently | a setting with the same value in every environment | | structure that removes duplication you can point at | structure that anticipates duplication | The distinguishing feature is the same in every row: **the justification exists outside the change** — in a substitution you actually perform, in a deployment, in a plan, in duplication you can point to. When the only evidence for the abstraction is the abstraction, it goes. ## Why refusing it now is cheaper than removing it later A missing abstraction announces itself at the second implementation, loudly and locally, and adding it then is a contained piece of work. **An unneeded one never announces itself.** It changes no behaviour, breaks no test, and fails no check, so nothing downstream is looking for it; review is the place it gets caught or not at all. It also gets more expensive without anyone touching it. Every later change — by a person or by another run — is written *through* the structure it finds, in the idiom that structure implies. What would have been one review comment today becomes a fan of call sites, each written in terms of a layer that never earned its place. Collapsing a one-implementation type is a routine mechanical edit; unwinding the code that has since been built around it is not. ## What this does not mean - Removing structure is not free either. If the layer is already load-bearing elsewhere in the change, taking it out is its own edit with its own risk, and doing it inside a review of something else repeats the mistake in the other direction. - Invented structure is not evidence that a run designs badly. It is evidence that it could not see whether the variation was coming — which is precisely the thing you can see. - "Keep it, we can always remove it later" is the argument to distrust. Later removal is cheap only while the layer has one caller, which is exactly the position you are in now.
- The interface exists only so a test can substitute something slow. Does it stay?Usually yes. A seam that lets you replace a boundary you do not own is variation that exists today, and the substitution is the second implementation. Say that in the review, so the next reader does not remove it as speculative.
- How do you spot invented structure quickly in a change touching nine files?Read the new names first. In a change that only had to add one behaviour, every new type, key or parameter is a claim that something varies. Check each claim against what exists; the ones with a single user are the candidates worth arguing about.
saying these in an interview costs you the question
- An interface with one implementation is harmless and costs nothing
- Abstraction is good practice, so extra structure is always welcome
- Every one-implementation interface is over-engineering and must go
- If it turns out unnecessary, removing it later is as cheap as now
- It changes no behaviour, so extra structure is not the reviewer's business