# When not to open a seam

The symmetry in STANDARD v1.0 is deliberate. D3 level 0 is *"behaviour selected by `if`/`switch` on a
type field, in more than one place"*. D3 level 3 is *"the seam set is minimal — no speculative
interface with a single implementation and no foreseeable second one"*. The standard then says it
outright: *"level 3 penalises over-abstraction as much as level 0 penalises none"*, and the failure
tag is `over-engineered (premature interface)`.

So this file is not a caveat bolted onto the lesson. It is the other half of it.

## The concrete bad example

Here is the version that looks like the lesson has been learned. Split into one file per public type
and put next to `worked/src/VehicleType.java`, it compiles: `javac 21` with `-Xlint:all` prints
nothing and exits 0.

```java
public interface PricingPolicy {
    long feeMinor(VehicleType type, java.time.Duration stay);
}

public final class StandardPricing implements PricingPolicy {
    @Override public long feeMinor(VehicleType type, java.time.Duration stay) {
        return 2000L * Math.max(1L, (Math.max(0L, stay.toSeconds()) + 3599L) / 3600L);
    }
}

public final class PricingPolicyFactory {
    public static PricingPolicy forLot(String lotName) {
        return new StandardPricing();
    }
}
```

One implementation. No second one named anywhere in the requirements. A factory whose only branch is
the absence of a branch, and a `String` parameter it ignores.

**What a reviewer sees.** Three files and one behaviour. `forLot("terminal-2")` and
`forLot("basement")` return the same object, so the parameter is a promise the code does not keep.
The next person adds a `PricingPolicyRegistry` so lots can be configured, and now there is a
configuration format for a system with one rate.

**What the grader sees.** Behaviour is behind an interface, so D3 is at least level 2 — and level 3
is out of reach, because the seam set is not minimal. `over-engineered` routes to `lessons/B1` at the
`when-not` stage. This is a case where doing more scores less.

## What the seam costs, measured

Three separate prices, and the middle one is the expensive one.

**It costs code before any requirement arrives.** From `node lessons/C2/contrast/measure.mjs`:

```
size of a    2 file(s)  33 normalised lines
size of b    4 file(s)  66 normalised lines
```

Read the shape, not the multiplier: both directories are commented heavily for teaching, so those
counts include javadoc. The seam doubled the file count for behaviour that did not change.

**It costs more than no seam when the change moves the other way.** Also measured, in
`contrast/curveball.md`:

| | Design A, no seam | Design B, seam on `(type, duration)` |
|---|---|---|
| tiered pricing — along the seam | 22 | **2** |
| overnight rate — across it | 14 | **27** |

The 27 is the number that matters here. A seam is a bet on a direction, and a losing bet is not
neutral. Widening `PricingPolicy.feeMinor` to take the entry instant touched the interface, its
implementation and its call site — three files, where the design with no seam at all touched one.

**It costs the ability to change shared code later.** `PricingPolicy.startedHours` is a static
interface method, so a tariff that bills by the minute cannot override it. Every implementation
inherits the rounding whether it suits them or not. That is a fine trade while every tariff bills in
hours, and it is a rewrite the day one does not.

## It also costs clock

An interface, an implementation and a factory is roughly fifteen minutes of a forty-five-minute
round. STANDARD v1.0 caps D2 at 0 when no `main` exists, on the grounds that *"interviewers run it
first"*, and D2 is 25% of the score against D3's 20%. A seam nobody asked for, paid for with the
driver, loses on both dimensions at once.

## The threshold, from both sides

**Open the seam** when you can name the second implementation *and* write the one signature both
would satisfy. Two implementations that exist, or one that exists plus one a requirement sentence
names, is enough. `corpus/parking-lot/reference/src/PricingPolicy.java` passes this test on the
strength of its own javadoc: *"free hours, weekend rates, EV discounts"* are three named tariffs, and
the curveball directory then ships one of them.

**Leave it concrete** when either half is missing. Naming the dimension without the signature means
you do not know the axis yet. The cheap move then is a small concrete class you are willing to
rewrite: `contrast/a/ParkingLot.java` is 22 lines to change, and it is a known 22.

**Never seam a set the compiler already protects.** `Footprint.of(VehicleType)` in `worked/src` is an
exhaustive switch, so a fourth vehicle type is a build failure at the one place a decision is owed.
An interface there would accept the new type in silence and return whatever its default did.

## What this file is not saying

It is not saying prefer the smaller diff.
`corpus/middleware-router/curveballs/02-method-aware-routing/reference-patch/PATCH.md` records a
draft that *"measured smaller"* and was rejected for coupling `Router` to one concrete
`MatchPolicy`. That absorption cost 47 lines, the most expensive in the corpus, and the author chose
the larger number knowingly.

The diff is a check on a claim about a seam. It is not the reason to have one.
