Product

The cost of making every feature a special case

Each exception is reasonable on the day it is added. The tenth one is why the product takes a quarter to change.

A customer needs the export to include one extra column. It ships as a flag. Another needs the approval step skipped for internal accounts. Another needs a different tax rule for one region. Each request is small, each is legitimate, and each is implemented by the fastest available route.

Eighteen months later, changing the export means understanding four flags, and nobody can say with confidence which combinations are reachable.

The real cost is epistemic

The expensive part of a special case is not the branch. It is that it removes a rule someone could previously rely on. "Exports always include these columns" was knowledge. After the flag, it is a guess that has to be re-verified every time.

The question that catches most of them

Before adding an exception, ask what would have to be true about the model for this not to be an exception. Usually the answer reveals a concept the domain has and the schema does not — "account type", "jurisdiction", "contract terms" — and naming that concept absorbs the current request and the next four.

// The special case, on the day it is reasonable:
if (account.id === 'acme') columns.push('internal_ref');

// The concept the special case was pointing at:
columns.push(...account.exportProfile.columns);
ts

The second version is barely more work on the day. It is dramatically less work on every subsequent day, and — more importantly — it restores a rule: exports include the columns the account’s export profile specifies. That sentence is true again, and can be relied on.

When the exception is correct

Sometimes there genuinely is no concept, and the honest thing is a documented exception with an expiry conversation attached. That is a fine outcome. It is a different outcome from an undocumented flag, which is what happens when nobody asks.

Working on something this applies to?