For a long time, I'd start every project by thinking about scale before
writing a single feature. Caching layers, abstracted data fetching,
"flexible" component APIs designed for use cases I didn't have yet. It felt
productive — like engineering discipline. It wasn't.
On MaintInsight, I spent the first week designing a generic
"referential system" abstraction meant to handle sites, plants, workshops,
and equipment interchangeably, before I'd shipped a single working CRUD
screen. Three weeks in, the actual requirements shifted — inspections
needed a different hierarchy than I'd assumed — and half that abstraction
got deleted.
The pattern repeated on smaller projects too: naming a function
fetchDataGeneric because "it might need to fetch other things later,"
when in practice it only ever fetched one thing, for the entire lifetime
of the project.
Reading through other people's early-stage codebases — including some
genuinely successful ones — I noticed something: the first version was
almost always embarrassingly simple. Direct database queries. Inline
logic. No premature interfaces. The abstraction came later, once the
second or third use case actually showed up and made the right shape
obvious.
I'd been optimizing for a future that hadn't asked for it yet, and paying
the cost — slower shipping, more code to maintain, more surface area for
bugs — in the present.
- Ship the concrete case first. One hardcoded path, no config options,
no generic naming.
- Duplicate before abstracting. If I write the same logic a third
time, then I extract it — and by then the right abstraction is obvious
from the actual repetition, not guessed in advance.
- Name things for what they do today, not what they might do someday.
None of this is a new idea — it's the "rule of three," it's YAGNI, it's
been said a hundred times. But there's a difference between knowing a
principle and actually feeling the cost of ignoring it on a real project.
MaintInsight is where it clicked for me.
This isn't an argument for writing careless code. Type safety, clear
naming, and basic separation of concerns aren't "premature" — they pay off
immediately, not just at scale. The line I try to draw now: optimize for
correctness and clarity today, optimize for scale and flexibility only
once a second real use case demands it.