Skip to main content

Writing Simple Code

· 6 min read

"There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies. The first method is far more difficult." — Tony Hoare

Simple code is not a consolation prize for teams that can't handle complexity. It's the goal.

Software Is an Encoding

· 6 min read

"Programs are meant to be read by humans and only incidentally for computers to execute." — Donald Knuth

Code is documentation. Not a supplement to documentation — the thing itself.

Stop Rewriting, Start Steering

· 6 min read

"Make the change easy, then make the easy change." — Kent Beck

In a previous article, I argued that legacy code is a people problem, not a technology problem. A rewrite is rarely the answer. But if you're not rewriting, what are you doing? You still have a system that's hard to work with. You still have users who need things to get better. So how do you actually change a legacy system?

The Real Problem with Legacy Code

· 5 min read

"Legacy code is code without tests." — Michael Feathers, Working Effectively with Legacy Code

Feathers is right, but he's only describing a symptom. The real problem with legacy code isn't technical. It's a people problem.

The Shape of a Test Suite

· 10 min read

Most test suites are shaped wrong. Not because the individual tests are bad, but because the overall structure creates redundancy, obscures intent, and makes the system harder to change. If your test suite feels like a burden rather than a safety net, the shape is probably the problem.

I've written before about the problems with the test pyramid as a mental model. This article is the constructive follow-up: what should you build instead?

Async/Await is Misleading

· 5 min read

Updated February 21, 2026

Async/await exists to make asynchronous code read like synchronous code. That's the sales pitch, and it's true - as far as it goes. A single async operation does read more cleanly with await than with nested callbacks or chained .then() calls.

But there's a trade-off hiding behind that cleaner syntax, and most developers never think about it explicitly: async/await makes the sequential flow of one operation easier to read while making the concurrent state of your whole program harder to reason about.

That's a real trade-off, and in most programs, it's the wrong one.

Reconciling Functional and Object Oriented Programming

· 6 min read

The Functional and Object Oriented Programming paradigms have different philosophies on how to write software. Like any ideology, they have to be applied in a concrete way to be useful. A programming language implementation has to be made, and the designers have to choose how to embody either or both of those paradigms in the syntax and semantics of the language. Some languages try to stay true to the ideal paradigm. Most mainstream languages, which make up most of the codebases out there, land somewhere in the middle. When you choose a language for a project, you often do not want to choose purely on programming paradigm - the language runtime, ecosystem, deployment options, and performance are critical considerations. Even if you favor one paradigm or the other, you often find yourself in languages that support both, working with people from both camps, and the codebase ends up being somewhere in the middle between the two.