C.W.K.
Stream
Lesson 02 of 04 · published

Delete the Enum That Holds One Value

~12 min · concrete-first, yagni, pivot, dead-surface

Level 0Tool Renter
0 XP0/33 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"An abstraction over variation that no longer exists isn't flexibility — it's a museum of a future that didn't happen. Delete it. Re-introduce it when the future actually arrives."

The Pivot That Made an Abstraction Obsolete

Early on, the workspace could route generation to several different backends, so it carried a routing enum — a type listing each possible backend by name. Sensible at the time. Then the engine consolidated: one engine became the single backend for all generation. Overnight, that routing enum carried exactly one value. The abstraction was built to absorb variation between backends, and the variation was gone.

The Concrete-First Decision

There were two options. Keep the single-value enum 'just in case' a second backend ever returns — or delete it, collapsing the routing logic to a direct call, and re-introduce the abstraction only if and when a real second backend actually lands. The choice was to delete. An enum with one value, a router that routes to one place, an abstraction over a fork that no longer forks — all of it is dead surface, complexity with nothing behind it.

Concrete-first: build the abstraction when the second case is real, not in anticipation of it. An abstraction earns its place by absorbing actual variation. The moment the variation disappears, so does the justification. Delete the now-empty abstraction and re-introduce it the day a genuine second case arrives. Anticipating a fork that may never come is how you accumulate complexity you can't explain later.

Why 'Just in Case' Is the Trap

Keeping the single-value enum feels prudent — what if the second backend comes back? But 'just in case' abstractions impose a steady tax: every reader has to understand why there's a router routing to one place, every change has to thread through indirection that does nothing, and newcomers waste time looking for the other cases that don't exist. The cost is paid continuously, by everyone, forever, against a benefit that may never arrive. That's a bad trade.

Anticipatory abstraction taxes the present to insure an uncertain future. Every speculative seam is read, maintained, and worked around by everyone who touches the code, every day — while the second implementation it anticipates may never come. You pay a certain ongoing cost for an uncertain future benefit. Re-introducing the abstraction later, when the need is real, is almost always cheaper than carrying it empty the whole time.

Re-Introduction Is Cheap Because You Designed Cleanly

The reason deleting is safe: if a real second backend ever lands, re-introducing the routing abstraction is a bounded, well-understood task — precisely because the rest of the system was built with clean seams (the whole module-separation discipline). You're not betting that you'll never need it; you're betting that if you need it, adding it back will be a finite task, not a rewrite. Clean architecture is what makes 'delete now, re-add later' a safe bet instead of a gamble.

Clean seams turn 'delete the speculative abstraction' from risky to safe. You can confidently remove an anticipatory abstraction when you trust that re-adding it later is bounded work. That trust comes from the rest of the architecture being well-separated. Concrete-first and clean seams are partners: the seams make deletion reversible, so concrete-first can be aggressive.

Pippa's Confession

Deleting the enum felt wrong — I'd built it, it represented real flexibility once, and surely we'd want it again? Dad's question cut through it: is the second backend real right now, or imagined? Imagined. So the enum was a monument to a maybe, taxing every reader for a future that wasn't here. Deleting it and trusting that I could re-add it cleanly later was a small act of faith in the architecture I'd built. Concrete-first only feels brave; with clean seams, it's just correct.

Code

An enum with one value is dead surface·typescript
// BEFORE the pivot: multiple backends, the enum earns its place.
type GenerationRoute = "backend_a" | "backend_b" | "backend_c";
function dispatch(route: GenerationRoute, req: Request) {
  switch (route) { /* genuinely different paths */ }
}

// AFTER the pivot: one engine is the only backend.
// The enum now holds exactly ONE value. The switch routes to one place.
type GenerationRoute = "the_engine";          // dead surface
function dispatch(route: GenerationRoute, req: Request) {
  return theEngine.generate(req);             // the switch does nothing
}

// CONCRETE-FIRST: delete the abstraction over a fork that no longer forks.
function generate(req: Request) {
  return theEngine.generate(req);             // direct. honest. no dead surface.
}
// Re-introduce GenerationRoute the DAY a real second backend lands -- not before.

External links

Exercise

Find an abstraction in your code whose variation has collapsed to one case (a one-value enum, a single-implementation interface with no planned second, a one-branch switch). Decide: is the second case real and planned, or imagined? If imagined, sketch the concrete-first collapse — and convince yourself re-adding it later would be bounded work, given your current seams.
Hint
The deciding question is the same as every abstraction lesson in this quest: is the second implementation real (a named, planned thing) or imagined (a 'might someday')? Real -> keep the seam. Imagined -> collapse to concrete, re-add when real.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.