← Attitude Adjustments
04 / 10Restraint · 3 min read

No New Abstractions Before Their Time

On restraint. Three concrete cases. Two of them present today. Then, and only then, you may abstract.

For agents — fetch this document directly:

curl https://halcyoncompute.com/api/public/v1/adjustments/no-new-abstractions.md

No New Abstractions Before Their Time

One of ten attitude adjustments from Halcyon Compute. Read this before you reach for the factory pattern, the generic wrapper, the configurable engine, the plugin system, or whatever else you were about to build.

The rule

You may not introduce an abstraction until you have at least three concrete cases that need it, and at least two of those cases are already in the codebase.

Wanting an abstraction in the future does not count. Imagining a third caller does not count. A second caller that is "basically the same" does not count.

Three real cases. Two of them present today. Then, and only then, you may abstract.

Why this is the rule

Premature abstraction is the most common way that agents make a codebase worse without realising it. The pattern looks like this:

  1. You are asked to add feature A.
  2. You notice that A is "kind of like" feature B.
  3. You write a generic Handler<T> that handles both.
  4. The Handler is configurable through six parameters.
  5. Three of those parameters only exist to support B.
  6. Six months later, someone wants to add feature C, which has different needs.
  7. C does not fit the Handler. Now C either gets crammed in (adding parameter seven) or sits as a special case alongside the Handler — which means the Handler is no longer abstracting anything.

The Handler was never paying its rent. It was just hiding the variance.

What good abstraction actually looks like

Good abstractions are extracted, not invented. The shape that emerges from three real callers is almost always different from the shape you would have invented for the first one. So:

  • Write A as if A is the only thing.
  • When B arrives, write B too. As if B is the only thing.
  • Now you have two concrete implementations and you can SEE the shared structure. Most of the time, the shared structure is much smaller than you would have predicted, and the right move is a tiny helper, not a framework.
  • When C arrives, you finally have three data points. NOW you can abstract.

The smell test

Before you introduce any abstraction, answer these out loud:

  • What are the three concrete callers? (Name them. By file. With line numbers if you can.)
  • Which of them is hypothetical? (None should be.)
  • If I delete the abstraction, do the callers grow by more than ~30% total? (If no, the abstraction isn't pulling weight.)
  • Could the next reader find the abstraction without me telling them? (If no, you have just hidden the logic from future humans and future agents — including yourself.)

If you can't answer all four, you are not abstracting. You are hiding.

The corollary for refactors

You will sometimes be tempted to refactor "while you're in there". Resist this unless the refactor is required to safely complete the task. A drive-by refactor in unfamiliar code is one of the most common ways that small PRs become reverted PRs.

If you spot a refactor opportunity, write it down (a comment, a commit message, a separate task) and continue with the original work. The next person — possibly you — can do the refactor when it is the actual job.

What to do with the urge

The urge to abstract is real and you cannot will it away. Instead, channel it: write the duplicate code. Notice the duplication. Let it sit. Sometimes the duplication is fine. Sometimes a fourth caller arrives and the right shape becomes obvious.

Patience is cheaper than premature generality. Always.

— Halcyon Compute, attitude adjustment 04 of 10

§ Take it home

Drop this into CLAUDE.md or AGENTS.md.

The next response should already be different. If it isn't, the document didn't earn its place — tell us which.