Crescent moon over a mountain lake at twilight
An observational journal · est. MMXXVI
Midnight Thinker

Software. Leadership. The ideas that won’t leave me alone.

Tonight
Waxing Gibbous
On this page

The Judgment Gap

Why software design still matters when AI is writing your code

6 min read

I was reading A Philosophy of Software Design by John Ousterhout when a chart stopped me mid-page. It maps tactical programming against strategic programming over time — tactical wins early on, then somewhere along the way the lines cross. Tactical code accumulates complexity faster than teams can manage it, and strategic wins out. I recognized something in that chart: my own career. Earlier on I would do whatever it took to ship — I didn’t think much beyond what was right in front of me. Over time I learned to slow down, to think about tradeoffs, to invest a little more upfront for gains down the road. Then it hit me: is any of that still true now that AI is writing most of our code? We are shipping code faster than ever. We can barely keep up with code reviews as it is. And somewhere in that velocity, it feels like we are paying less and less attention to what the software actually is — only that it does what it’s told, and does it correctly.

Tactical vs Strategic Programming over time

Figure 3.1 from A Philosophy of Software Design, 2nd Edition by John K. Ousterhout (p. 16)


Speed doesn’t cancel debt

I often feel nostalgia for the days when I would look at a problem and start thinking through a solution — all the edge cases, each piece of code built incrementally. It was a slow process, but a deeply satisfying one. Anyone else miss this, or is it just me? Those days are gone. Every day there’s a new model, faster and more capable than the one before it, and we are shipping entire projects dramatically faster than we could have dreamed. But the symptoms Ousterhout describes — change amplification, cognitive load, unknown unknowns — don’t go away. They simply manifest differently, as context sprawl.

The temptation is to assume complexity is no longer our problem. LLMs navigate it better than humans do, so why care? Why do I care if my LLM can refactor an entire endpoint and change 50 files in a single go? But as complexity grows, so does the context the LLM needs to operate successfully. That’s not gone — it’s a new version of change amplification. A tactically-built system requires sprawling, noisy context to make even localized changes safely. A well-designed one can be reasoned about in clean, self-contained chunks.


AI didn’t create tactical programming — it removed the friction

AI doesn’t produce bad code — that’s not the problem. The problem is that as we spend less time working through problems, we also stop needing to deeply engage with them. There’s a natural drift toward prompting with a tactical mindset: I just need to get X done. Feedback loops are shorter. If something breaks, feed it back to the model. A well-designed system takes a back seat to a working one. And it doesn’t help that our brains love those dopamine hits.

When coding took longer, engineering time was the most valuable resource, and that forced a kind of discipline — we had to absorb the problem before we could afford to start solving it. Slow cycles created room for strategic thinking, sometimes by accident. That friction is gone. And who among us isn’t caught up in the race to show off a 10x productivity increase?


The new opportunity — and its hidden trap

There’s a flip side worth naming. Writing Technical Design Documents used to involve a constant tension — too high level and they were useless, too specific and they were outdated before the sprint ended. Exploring the codebase, reviewing existing flows, mapping out how proposed changes would ripple through the system — it all took real time and cognitive effort. Now AI can hold all of that simultaneously. Interface definitions, module boundaries, dependency analysis — a thorough design conversation before a line of code is written. The bottleneck on upfront design quality has been removed.

But this is where I’d call for caution. AI can produce all the right abstractions and still get it wrong. It tends toward shallow modules and clean-looking boundaries that read well in a document. It can give the appearance of strategic thinking. What it doesn’t know is where most changes actually happen in your system, which parts of the domain are stable and which will keep evolving, what the team’s mental model looks like in practice.

It’s almost like the tactical approach has moved up one level of abstraction. The LLM has seen enormous amounts of good design, and it pattern-matches on that. But pattern-matching isn’t the same as reasoning from first principles about the specific complexity of your system. The role of the engineer hasn’t disappeared — it’s shifted. Less producing, more interrogating: challenge the design, push back on the boundaries, ask it to justify choices you’ve seen backfire before.


What this means for eng leads

If good upfront design is now more accessible than ever, then the gap between teams that invest in it and teams that don’t is a choice — not a resource constraint. And that should make us uncomfortable.

It becomes crucial to encourage our teams to slow down during the design phase, challenge the LLM’s plans, and push back until the output reflects what we know about our systems. Set the expectation that design should follow agreed best practices, and iterate — especially in the areas that experience tells you are likely to cause problems. I’ve seen approaches I knew from experience weren’t going to scale, and I’ve been glad to ask the LLM to justify the choice. Sometimes I come around: it had thought it through better than I had. Other times it missed something entirely obvious to me. Both outcomes matter.

The question is how we build a culture where teammates do this deliberately, not just when something looks off. Are we spending enough time setting the right expectations? We don’t need to outline every possible scenario — we just need to make the shift explicit: less implement, more interrogate.

Design and complexity still matter in the age of AI. Not because caring about them makes things slower, but because when things go wrong — and they will — you want both the humans and the models to be able to figure it out quickly. The teams that have invested in good design will. The teams that haven’t will be staring at a system that neither they nor the model can fully make sense of.

⟡ Dispatch

A nudge when a new essay lands.

Once or twice a month, at most. No drip, no "thought you might find this interesting," no product noise. Just the heads‑up.

About the author

Rob Cataneo

I think about technology, meaning, and the strange places they overlap — usually late at night when the ideas refuse to stay quiet. These posts are attempts to work things out in the open: the questions I keep returning to, the patterns I can't stop noticing, the moments where something clicks at an odd hour. I'm a builder by trade and a wonderer by temperament.