Engineering approach

How I think about building software

The principles I come back to when designing and reviewing systems. They are opinions about practice, not claims about any particular project.

Denne side findes kun på engelsk.

Simplicity

The best code I write each year is the code I delete.

I don't trust clever. It is easy to mistake complexity for sophistication — the intricate abstraction, the pattern applied three layers deep, the framework that could theoretically do anything. None of it survived contact with a real deadline and a real team. The systems that lasted were the boring ones I could explain to a new engineer in five minutes.

Simplicity isn't the absence of effort. It's the result of it. It takes real work to find the version of a solution that has nothing left to remove, and almost no one rewards you for it because the result looks obvious in hindsight. That's exactly why it's valuable — obvious code is code that the next person can change without fear.

When I'm reviewing my own work, the question isn't 'is this impressive?' It's 'what could I take out and lose nothing?' Usually the answer is more than I'd like to admit.

Complexity is easy. Anyone can add. The discipline is in what you refuse to build.

Maintainability

I write for the engineer who inherits this at 2am — and it's often me.

Code is read far more than it's written. That sounds like a platitude until you've spent a week excavating a system someone built in a hurry three years ago, trying to reconstruct intent from a diff. The lesson is always the same: the moment of writing is the cheapest possible time to make something clear.

I care more about naming than most people think is reasonable. A well-named function is documentation that can't go stale. I write commit messages that explain why, not what — the diff already shows what changed; the future reader needs the reason. And I'm suspicious of comments that explain confusing code, because the better move is almost always to make the code less confusing.

Maintainability is a gift you give to people you'll never meet, including a future version of yourself who has completely forgotten how any of this works.

Every system becomes legacy. The only question is how cruel you were to the person who inherits it.

Performance

Performance is a design decision, not a phase you get to later.

There's a comforting myth that you should build it first and optimize it afterward. In my experience, the performance problems that actually matter aren't the ones a profiler finds in a hot loop — they're the ones baked into the architecture. The N+1 query that's really a data-access pattern. The synchronous call that should have been a message. You don't profile your way out of those; you'd have to rebuild.

So I think about performance at the point where it's cheap to influence: when I'm choosing how data flows, where boundaries sit, what's synchronous and what isn't. In distributed systems, the difference between a design that scales and one that buckles is rarely faster code — it is deciding early that services talk asynchronously, so a slow downstream can't drag everything down with it.

I don't micro-optimize prematurely. But I refuse to make architectural choices that I know foreclose performance, just because the slowness won't show up until later.

You can optimize a slow function. You cannot optimize your way out of a slow design.

Architecture

Architecture is the set of decisions that are expensive to undo. I try to make fewer of them.

The most useful thing I've learned about architecture is to defer it. Not avoid it — defer it. The decisions that are genuinely architectural are the ones that are hard to reverse, and the worst time to make a hard-to-reverse decision is at the start, when you know the least about the problem you're actually solving.

Teams commit to microservices on day one because it feels like the serious choice, then spend a year paying the distributed-systems tax for a product that two people could have maintained as a monolith. Microservices are a powerful tool when team topology and domain boundaries genuinely justify the overhead — and an expensive form of theatre when they don't. Knowing the difference is the actual skill.

Good architecture, to me, is mostly about keeping options open. Drawing boundaries where the seams really are, so that when I learn something new — and I always do — the cost of changing my mind is a refactor, not a rewrite.

Make the reversible decisions fast and the irreversible ones slowly. Most teams do the opposite.

Testing

I don't test for coverage. I test so I can change things without holding my breath.

A test suite is a confidence machine. Its job isn't to hit a coverage number — it's to let me refactor aggressively, knowing that if I break something important, something will tell me before a customer does. A codebase I'm afraid to change is a codebase that's already dying, no matter how clean it looks.

Which is why I test behaviour, not implementation. Tests that are coupled to internal structure shatter every time you refactor, so they actively punish the cleanup that code needs to stay healthy. I want tests that care what the system does and stay quiet about how it does it. Where a defect is a financial or contractual problem rather than a cosmetic one — pricing, billing, anything that moves money — the core rules deserve exhaustive tests, not for the metric but because they have to be trusted absolutely.

And I test the seams hardest. Unit tests prove the pieces work alone; the expensive bugs live where systems meet, where one component's assumptions quietly diverge from another's.

Tests aren't there to prove the code works. They're there so you can change it tomorrow.

Automation

Shipping should be boring. The day a deploy is exciting, something has gone wrong.

I automate the path to production until it's a non-event. Manual deployment is a slow-motion incident — a source of human error and a tax on iteration, usually paid at the worst possible moment by the most tired person on the team. The more the pipeline does, the more attention is freed for the work that actually requires a human.

My rule is that the pipeline is the only path to production. The instant manual deploys are possible, they happen — and they happen during exactly the kind of emergency where you least want someone improvising with production access. Remove the option and you remove the temptation.

The same instinct applies beyond deploys. If I've done something fiddly and error-prone twice by hand, I'll spend the time to make sure I never have to do it carefully a third time. Not because automation is elegant, but because my attention is the scarcest resource I have and I'd rather not spend it on things a machine does better.

Automate the boring path so completely that going to production stops being a decision.

Business Understanding

The hardest problems in software are rarely about software.

You cannot architect a system whose purpose you don't understand. Most of what looks like a technical problem is a symptom of a business reality nobody encoded properly — a pricing rule that lives in someone's head, an approval flow that exists only as an email thread, a constraint everyone knows and no one wrote down. Solve the symptom and it comes back. Solve the constraint and the symptom evaporates.

In commerce, the breakthrough is rarely a framework or a clever query. It is realizing that the domain model is the product — get pricing and approvals right and the interface nearly designs itself; get them wrong and no amount of polish will save it. The engineering that matters is understanding how the business actually sells, then encoding that faithfully.

This is why I work embedded in teams rather than at arm's length. I can't model a business I'm only allowed to see through a ticket queue. The good decisions come from being in the room where the real constraints get discussed.

Code is how you encode a decision. If you don't understand the decision, the code is just confident guessing.

Continuous Learning

I default to boring technology — which is precisely why I have to keep learning.

There's no contradiction here, though it sounds like one. I reach for proven, well-understood tools in production because reliability is the whole point. But the only way to know which tools deserve that trust — and when a genuinely better approach has arrived — is to keep learning constantly, on my own time, away from anything production depends on.

Reading about how other systems fail, the documentation of tools I may never use, the occasional paper — most of it is never applied directly. But it builds the intuition that tells you, in the moment that matters, that the problem in front of you is one someone has already solved well.

Use boring tools in production. Stay restless everywhere else. The two depend on each other.