Sasindu Bandara
← Articles4 min read

Almost every architectural decision looks fine in week one. The ones that matter only reveal themselves around month fourteen, when the original authors have moved on and the requirements have changed twice.

Published
Words
888
EngineeringArchitecture

I joined my current company as an intern and stayed. That is not a career-advice story — it is the reason I think about codebases the way I do. I have watched the same systems from four different levels of responsibility, and I have been the person who had to maintain code I wrote eighteen months earlier without remembering why.

That experience produces one strong opinion: almost every architectural decision looks fine in week one. The decisions that actually matter only reveal their cost in year two.

What week one optimises for

In week one you are optimising for velocity, and reasonably so. The requirements are fresh, the team is small, everyone has the whole system in their head, and the fastest path to a working thing is usually the right path.

Week one rewards: clever abstractions that save typing, shared utilities that handle six cases, config that infers sensible defaults, a schema that maps neatly onto today's requirement.

None of those are wrong. They are just being evaluated against the wrong horizon.

What year two actually tests

By month fourteen, four things have reliably happened:

  1. The requirements changed — not once, twice, and in a direction nobody predicted.
  2. The original authors are gone, or on something else, or genuinely do not remember.
  3. The system has real data in it, which means the easy fixes are now migrations.
  4. Someone new has to make a change under time pressure, without the context.

Every one of those is a test of legibility, not of cleverness. And clever code fails legibility tests.

The specific things that go wrong

The abstraction that handled six cases. It now handles eleven, four of which do not fit, and it has three boolean flags whose interactions nobody can enumerate. Every change to it risks the other ten callers, so people stop changing it and start working around it. The workarounds are now the real system.

The week-one alternative — writing the thing out twice — looked worse and aged better. Duplication is cheap to fix when you finally understand the pattern. A wrong abstraction is expensive to fix forever.

The inferred configuration. Sensible defaults are wonderful until production behaves differently from staging and nobody can tell you why, because the value is not written down anywhere; it is derived. Explicit config is more typing in week one and is the difference between a ten-minute and a two-day investigation in year two.

The schema that matched the requirement. Requirements are a snapshot of what someone wanted on a Tuesday. Schemas outlive them by years. A schema modelled on the domain — the things that are actually true about the business — survives requirement changes. A schema modelled on the current screen layout does not.

The silent failure. Somewhere there is a catch that logs and continues. In week one it prevented a crash. In year two it has been quietly swallowing a class of error for eleven months and the data is subtly wrong in a way that will take a week to reconstruct.

What I do differently now

Write the boring version. If I cannot explain an abstraction to a new joiner in two sentences, I do not write it yet. I write the repetitive version and wait until the pattern is obvious from three or four real examples rather than two hypothetical ones.

Make state explicit and inspectable. Anything that can be a value in a table rather than a value in someone's head, should be. Anything derived should say where it came from.

Fail loudly by default. Silent recovery is a decision that should be made deliberately, per-case, with a comment explaining why. The default should be that a broken thing announces itself immediately, while the cause is still one change away.

Optimise the diff, not the file. The question is not "is this file elegant?" It is "when someone changes this in a year, will the diff be small and obviously correct?" Those are different properties, and the second one is the one that gets paid.

Write down the why, not the what. The code says what it does. A comment repeating that is noise. The thing that is genuinely lost is why — why this ordering, why this timeout, why we do not use the obvious approach. That is the comment worth writing, and it is the one almost nobody writes.

The trade being made

None of this is free. Every item on that list costs velocity in week one. Explicit config is more typing. Duplicated code is more lines. Loud failures mean more incidents surfaced early rather than discovered late.

That is the trade, and it is worth naming honestly rather than pretending the "good" approach is also the fast one. You are spending week-one speed to buy year-two speed.

Whether that trade is correct depends entirely on how long the system will live. A prototype that gets thrown away in six weeks should be written the fast way — applying year-two discipline to throwaway code is its own kind of waste.

But most enterprise systems are not thrown away. They are still running, largely unloved, years after everyone assumed they would be replaced. That is the real design horizon, and it is the one most of us plan for least.

Written by Sasindu Bandara, Software Engineer at Innovative-e, Inc. in Colombo, Sri Lanka.