Beer & Servers Don't Mix

The Craftsman’s Dilemma: Why Your Best Engineers Are Your Biggest Overengineering Risk

Or: Why the Engineers Who Care Most About Quality Are Building Solutions Nobody Asked For

The Slack notification came at 2:47 PM on a Thursday. Three days into what should have been a two-day feature, and my senior engineer — one of the best I’ve got — wanted to “sync up about the architecture.” I could already smell the whiteboard markers through my screen. The air in the meeting room was that particular flavour of stale that only comes from engineers who’ve been “designing” for hours. On the wall, a diagram that looked like someone had tried to draw the London Underground from memory, while drunk, during an earthquake.

“So we’ve abstracted the data layer to support multiple backends,” he explained, pointing at boxes within boxes within boxes. “And we’ve added a factory pattern here so we can swap implementations without changing the business logic.”

The feature was a button that sent an email.

This is the craftsman’s dilemma: you need engineers who care deeply about quality — clean code, elegant architecture, robust systems. But craftsman culture without simplicity culture produces gold-plated solutions that take three sprints instead of three days. And the tragedy is, your best engineers are often the worst offenders.

The Parkinson Problem

Cyril Northcote Parkinson, a British naval historian with far too much time to observe bureaucracy, articulated something in 1955 that should be required reading for every engineering leader: “Work expands to fill the time available for its completion.”

He was writing about British civil servants, but he might as well have been describing your last sprint. Give an engineer two weeks to build a feature that should take three days, and they won’t deliver it early. They’ll find ways to make it more “robust.” More “extensible.” More “future-proof.”

The future they’re proofing against, of course, is entirely imaginary. It’s the what-ifs and maybes that live rent-free in every engineer’s head. What if we need to swap databases? What if the business model changes? What if we need to support multiple currencies?

Here’s the uncomfortable truth: you probably won’t. And if you do, the requirements will be so different from what you imagined that your abstraction won’t help anyway.

As Antoine de Saint-Exupéry, an aviator and writer who knew nothing about software but everything about design, put it: “Perfection is achieved not when there is nothing more to add, but when there is nothing left to take away.” The best code isn’t the cleverest — it’s the code where there’s nothing left to remove.

Yet we keep adding.

The Craft Trap

I’ve identified two distinct failure modes in engineers, and they’re both related to misunderstanding what “good” actually means.

The first is over-engineering. We’ve all seen it: the “perfect architecture” that takes six months to build, supports fifteen use cases that nobody asked for, and collapses under its own weight the moment a real customer touches it. Everything must be DRY. Every method needs an interface. Every decision needs an abstraction layer. The code is so decoupled it’s practically divorced.

This usually comes from fear — the fear of being caught out in the future, the fear of looking like a “junior” developer, the fear of someone pointing at your code in a review and saying “but what about…”

Some days I feel like my engineers need a YAGNI tattoo so they stop forgetting this principle. You Ain’t Gonna Need It isn’t just an acronym — it’s a survival strategy.

The second failure mode is the patch-on-top mentality. These engineers understand that over-engineering is bad, but they swing to the opposite extreme. Rather than fixing root causes, they add workarounds. Rather than refactoring, they patch. The code accumulates layer upon layer of special cases, each one a monument to some past crisis that nobody took the time to properly resolve.

Neither of these is craftsmanship. Real craftsmanship is knowing the difference between “good enough” and “cutting corners.” It’s understanding that the goal isn’t beautiful code — it’s readable code. Code that the next person can understand. Code that solves the actual problem without solving twelve theoretical ones.

The Pattern Paradox

Here’s something that keeps me up at night: patterns are meant to speed you up.

That’s the whole point. Someone identified a common problem, documented a proven solution, and gave it a name so you wouldn’t have to reinvent it every time. Factory pattern. Observer pattern. Singleton. These are supposed to be shortcuts, not destinations.

Yet I watch teams implement Redux with TypeScript and never get the advantage out of it. They write massive amounts of boilerplate — reducers, actions, action creators, selectors — without understanding what problem Redux actually solves or when it’s appropriate. They’ve confused “following the pattern” with “solving the problem.”

Even Dan Abramov himself told people in an interview he doesn’t recommend new teams using redux, because he was so chuffed at the reality of how people use it.

The result is ceremony without purpose. Complexity without benefit. A codebase that looks sophisticated but takes three times as long to modify as something simpler would have.

The comedian Jerry Seinfeld, reflecting on his craft, said: “The less steps, the more satisfying. Whether it’s a joke or a meal, the fewer steps you can do it in, the better.” He was talking about comedy and cooking, but swap “joke” for “feature” and you’ve described the difference between engineers who ship and engineers who architect.

The Speed Illusion

I really want some of my engineers to try working on a Rails project for a month. Just so they can see how fast things can actually go.

Rails isn’t magic. It’s just opinionated about the right things. Convention over configuration means you spend less time deciding and more time building. The “Rails way” isn’t always the best way, but having a default way — any way — beats debating abstractions in a meeting room while your deadline approaches like a freight train.

Now, we can’t use Rails for most of our production scale here at Agoda. That’s not the point. The point is that fast actually exists. Shipping features in hours instead of days isn’t a myth from the startup era. It’s what happens when you stop treating every feature as an opportunity to build infrastructure.

The problem is, craftsmen pad their estimates because the code needs to be written “well.” But “well” has become synonymous with “complex.” With “abstracted.” With “theoretically extensible in ways we’ll never actually extend it.”

Meanwhile, deadlines are entropy accelerators. When you push a team to meet a deadline, they make compromises. That’s not a character flaw — it’s rational behaviour under constraint. But when the estimate itself has been inflated by unnecessary complexity, the deadline pressure creates a perverse cycle. Engineers feel rushed, so they add workarounds. The workarounds create technical debt. The debt slows everything down. The next estimate gets padded further.

The economist John Maynard Keynes noted that “the difficulty lies not so much in developing new ideas as in escaping from old ones.” Your engineers need to escape from the idea that more abstraction equals better code.

What Actually Matters

Let me share what we’ve learned at Agoda about distinguishing true craftsmanship from its expensive imitation.

Before you build: Don’t plan for problems you don’t have yet. The things you’re planning for may not happen. Requirements change. Business priorities shift. That elegant abstraction you spent a week building might never get used. Ron Jeffries, co-founder of Extreme Programming, put it simply: “Always implement things when you actually need them, never when you just foresee that you will need them.”

While you build: Solve the customer’s problem with the least complexity possible. Every line of code is a liability. Every abstraction is a tax on future readers. When you feel the urge to add something “just in case,” resist. The code doesn’t need to be beautiful — it needs to be readable. Readable enough for the next person who works on it.

When you hit a wall: This is where real craftsmanship shows up. You’ve found a problem with your code structure. Maybe a new feature doesn’t fit cleanly. This is not the time to patch on top. This is the time to step back and ask: how can I make this code simpler before I proceed? Following simplicity means you’ll hit problems sooner — that’s the whole point. The key is recognizing that when you hit these problems, it’s time to refactor, not time to patch.

Martin Fowler, through Kent Beck’s influence, articulated something every engineer should understand: “For each desired change, make the change easy (warning: this may be hard), then make the easy change.” If it’s hard to implement something, first refactor the code to make it easy to implement. Then make the easy change. The refactoring becomes a gift to your future self and everyone else who will touch this code.

The Simplicity Principle

Here’s what I’ve learned, sometimes painfully: when you ask engineers how to do something faster, you’re implicitly authorising a set of trade-offs that will come back to haunt you. Faster usually means skip the tests, copy-paste rather than abstract, add a feature flag we’ll “definitely” clean up later, hardcode this value because it’s “just for now.”

But when you ask how to make something simpler, you often get the speed for free — just without the accompanying technical debt.

Simplicity forces engineers to ask different questions: Do we actually need this feature? Can we solve the customer’s problem with fewer moving parts? What can we remove rather than add?

Kent Beck’s four rules of simple design have survived for decades because they work. The code passes the tests — it works, this is table stakes. It reveals intention — someone reading your code can understand what you were trying to do. It has no duplication — everything is said once and only once. And it has the fewest elements — no unnecessary classes, methods, or abstractions.

Notice the order. The code has to work first. Then it has to be understandable. Then you eliminate duplication. And only after all that do you minimise the overall structure.

Most craftsmen I meet have inverted this hierarchy. They start by minimising elements (premature abstraction), then worry about duplication (copy-paste paranoia), occasionally think about clarity, and sometimes check if it works.

The Bottom Line

Simplicity isn’t the absence of effort — it’s the presence of clarity. It’s not about doing less work; it’s about doing the right work. Every line of code you don’t write is a line you don’t have to maintain, test, document, or debug at 3 AM when it inevitably breaks.

As a product engineer, you take responsibility for the feature, not the code. You own the code because the code creates and supports the feature. The code is the means, not the end. The moment you forget this — the moment the architecture becomes more interesting than the problem it solves — you’ve stopped being a craftsman and become an artist. And artists, as much as I love them, have no place in sprint planning.

The legendary basketball coach John Wooden said, “If you don’t have time to do it right, when will you have time to do it over?” In software, we somehow convinced ourselves that doing it wrong now and fixing it later is a viable strategy. It isn’t. But neither is doing it “right” in ways that nobody asked for.

The craftsman’s dilemma isn’t a choice between quality and speed. It’s understanding that true quality is simplicity. It’s building what’s needed, nothing more. It’s resisting the urge to future-proof against futures that will never arrive.

So the next time your best engineer wants to “sync up about the architecture” for a two-day feature, ask them one question: “How can we do this in a more simple way?”

You might be surprised how often the answer is “by doing less.”

Now, if you’ll excuse me, I need to go review a pull request. Someone’s added a factory pattern to a button that sends an email. It’s beautifully abstracted. And I’m going to ask them to delete it.