# The Faster AI Writes Code, the More Fundamentals Matter

> Regenerate until it works and you get code nobody can maintain. Five ways AI coding goes wrong, and how TDD and deep modules keep your agent in check.

- URL: https://pizzanote.dev/en/ai/software-fundamentals/
- Published: 2026-10-18
- Updated: 2026-10-18

AI has gotten so good at writing code in the past year or two that some people are asking whether all that old software engineering stuff can finally go. The boldest version of this is a movement called "specs to code": you write the spec, AI turns it into code, and when something breaks you don't read the code. You fix the spec and regenerate. AI is the compiler now.

Sounds great. But if you've actually tried it, you know where it ends up: regenerate enough times and you get a pile of code nobody can maintain XD

I used to paste a client's requirements straight into Claude Code, and when the result was wrong, my first move wasn't to read the code. I'd add one more line to the requirements and ask it to redo the whole thing. Three or four rounds later, every feature seemed to be there, but the same logic was written two or three different ways across different files. When I finally needed to change one small thing, even the AI got it wrong XD

Matt Pocock gave a talk at AI Engineer called "[Software Fundamentals Matter More Than Ever](https://www.youtube.com/watch?v=v4F1gFy-hqg)." He sorts the common ways AI coding goes wrong into five failure modes, and every one of them has a fix in a classic book that's been sitting on the shelf for years.

## Code is cheap, bad code isn't

Specs to code rests on one assumption: code is cheap. AI spits out a ton of it in seconds, so if it breaks, throw it away.

But *The Pragmatic Programmer* described software entropy a long time ago: left alone, things only get messier. If every change only cares about the task in front of it and ignores the overall design, the codebase rots, and there's no going back.

John Ousterhout's definition of complexity in *A Philosophy of Software Design* is just as blunt: anything about the structure of a system that makes it hard to understand and modify. A codebase you can't change without creating bugs is a bad codebase.

AI is only amazingly productive in a codebase that's easy to change. That's why software fundamentals matter more now than they ever have.

## Five ways AI goes off the rails

| What AI does | The fundamental you bring |
|---|---|
| Builds something completely different from what you had in mind | Build a shared design concept first, and let AI grill you before any work starts |
| Answers are long-winded and full of words you don't use | Define a ubiquitous language so you both use the same terms |
| Code looks right, then breaks the moment you run it | Use TDD to shrink each step, so feedback comes in small doses |
| Gets lost in the codebase and can't follow the existing logic | Turn piles of shallow modules into deep modules: simple interface, complexity hidden inside |
| Output doubles, and you're completely drained | Design the interfaces yourself, and hand the implementation to AI as a gray box |

If you've used an agent, you've probably hit everything in the left column. Nothing in the right column is new.

## Argue first, build later

When AI builds something you didn't have in mind, it's usually because the two of you never had the same picture. Fred Brooks wrote about the "design concept" in *The Design of Design*: when several people design a system together, they share an idea of it in their heads, and it's hard to get the whole thing into a Markdown file. Designing with AI is no different.

AI's problem is that it's impatient. You're halfway through explaining and it's already writing a plan. Flip it around. Tell it not to touch anything yet, and have it keep questioning you until every branch of the design tree and every decision is settled. Forty to a hundred questions is perfectly normal. Once you actually agree, turn the result into a PRD or an issue.

![Two ways to start: start right away and AI writes a plan immediately and builds on its own guesses, ending up nothing like what you had in mind; argue first and AI asks 40 to 100 questions, then the agreement goes into a PRD or issue before any work starts](/images/posts/software-fundamentals/grill-en.svg)

The other problem is talking past each other. When AI explains what it's doing, it often uses a bunch of words nobody on your project uses, like a senior engineer trying to explain chip architecture to a domain expert who doesn't know any tech. That's exactly the problem DDD set out to solve, and the fix is very DDD too: collect the core terms and definitions of your project into a table in a Markdown file, and have AI read it before every planning session. Once the words line up, its thinking gets a lot shorter, and what it builds sticks much closer to the original plan.

When I moved this blog from Sanity to Markdown, the number of decisions was scary: which domain is canonical, whether URLs carry the language, whether both languages share a slug, how many posts a tag needs before it deserves its own page. If nobody asks about these up front, AI picks something reasonable-looking and keeps going, and by the time you notice, Google has already indexed the URLs.

Terms work the same way. PizzaNote has topics (front-end, AI, life) and categories (like SEO), and categories sit under topics, but the `category` field in a post's frontmatter actually holds a topic. Without a table that spells out baggage like that, AI has to guess all over again every time.

> I recommend Matt Pocock's open-source [skills](https://github.com/mattpocock/skills), which you can install straight into Claude Code. [grill-me](https://github.com/mattpocock/skills/blob/main/skills/productivity/grill-me/SKILL.md) keeps asking until there's nothing left it's quietly assuming. Ubiquitous language scans your codebase and builds the glossary for you. Improve codebase architecture finds the places that could become deep modules.

## Don't outrun your headlights

Even when you give AI TypeScript type checking, a browser to drive, and a ready-made test suite, the code it writes still often doesn't run.

*The Pragmatic Programmer* has a great image for this: outrunning your headlights, driving faster than your lights can show you the road. How often you get feedback is the speed limit for your development. AI's habit is to dump a huge chunk of code in one go and think about tests afterwards. It would be weird if that didn't crash.

TDD is how you give AI brakes. Write one small test, make it pass, refactor and think about the design for a second, and only take one small step at a time.

![Two rhythms for writing code: dump hundreds of lines and run tests at the end, leaving a pile of bugs; or repeat test, pass, refactor in small steps so you know right away which step broke](/images/posts/software-fundamentals/headlights-en.svg)

I've crashed past my headlights myself. During the PizzaNote redesign, AI switched the headings to a full Chinese webfont. It looked great and seemed fine on desktop, but mobile LCP shot up to 12 seconds. The fix was to build a subset with only the characters the posts actually use, and I added a `check-content` step to prebuild that warns when a title is too long or a description is the wrong length. Now when AI finishes a post, one build tells me whether anything is over the limit, instead of finding out after it goes live.

## You own the interface, AI writes the inside

AI is great at producing shallow modules: lots of little pieces that each do very little but have complicated interfaces, scattered all over the place. When AI explores a codebase like that on its own, it gets lost too, and can't tell what depends on what.

Ousterhout's answer is the deep module: a big chunk of complex functionality hidden behind a very simple interface. AI doesn't need to understand every line inside a deep module. It just talks to it through a clean interface and writes tests at the boundary. Find the code that belongs together and pull it into deep modules, and your system becomes easy to TDD and much harder for AI to get lost in. A codebase that's easy to test is always easy to maintain.

![Shallow versus deep modules: shallow modules have wide interfaces, do very little, and tangle their dependencies so AI gets lost; a deep module hides lots of implementation behind a narrow interface, where you design the interface and tests and AI writes the inside](/images/posts/software-fundamentals/modules-en.svg)

PizzaNote has one of these. Every page sets up its SEO with a single call to `pageMetadata`: pass in the title, description, path and share image, and that's it. Canonical, hreflang, x-default, Open Graph and Twitter cards are all hidden inside. What I care about is whether the interface is easy to use and the tags it outputs are right. How it's put together inside, AI can write. When we add a new page, AI just calls it the same way instead of rewriting SEO for every page.

The fifth problem gets solved here too: output doubles, and you're drained. You're tired because you're trying to keep every implementation detail in your head and stay in sync with AI, which is basically micromanaging it. The whole point of modules is to reduce cognitive load, so treat deep modules as gray boxes. You design the interface and the test boundary, and AI handles the implementation inside. The exception is core logic that really can't go wrong, like payments.

Same goes for writing a PRD: focus on how modules change and what the interfaces look like, not on implementation details. That's Kent Beck's advice, too: invest in the design of the system a little every day.

## If you're just getting started

If you don't have a CS background or you've just started coding, deep modules and DDD might feel a bit far off. For day-to-day AI use, I think three things are enough.

First, treat AI like a really smart intern on their first day. Talking to AI is a lot like talking to a person, so when you hand something off, ask yourself: are my instructions clear enough? Do they know what "done" looks like for me? Usually you can't just toss over a vague goal. Tell them how to do it, give them a step-by-step plan, and before they start, let them throw every question they have at you, just like grill-me.

Second, know the domain yourself, and don't just pass messages along. At a real software company, nobody lets customers give orders to the engineers directly, and no engineer reads one-star reviews on Google Play and somehow divines the bug. There's always a PM or senior colleague who knows the domain and turns the problem into a clear requirement. When you work with AI, that person is you. Domain knowledge is what turns you from someone typing commands into someone collaborating with AI, and from a tester filing bugs into someone designing the system.

Third, build a feedback loop. There will always be bugs. Debugging follows a pretty fixed routine: gather the error information, find the cause, fix it the right way, test again. Let AI read the terminal or wherever the errors show up, then have it write the right tests to verify the fix, and you'll spend a lot less time debugging.

## AI on the front line, you on the map

The fatal flaw of specs to code is that it makes us stop investing in system design.

Matt's metaphor is that AI is the sergeant on the ground. You can hand it the big changes and the tedious implementation work. But someone above it has to look at the whole map and decide where to go, and that someone is you. Doing that job well comes down to the fundamentals software engineering has spent more than twenty years refining.

You can still let AI spit out a few hundred lines at a time and enjoy the vibe coding rush. But next time, before you hit "regenerate," take *The Pragmatic Programmer* off the shelf and flip through it, OK?

## References

- Matt Pocock, "[Software Fundamentals Matter More Than Ever](https://www.youtube.com/watch?v=v4F1gFy-hqg)," AI Engineer
- Matt Pocock, [mattpocock/skills](https://github.com/mattpocock/skills) (GitHub)
- David Thomas and Andrew Hunt, *The Pragmatic Programmer*
- John Ousterhout, *A Philosophy of Software Design*
- Frederick P. Brooks, *The Design of Design*
- Eric Evans, *Domain-Driven Design*
