Software Fundamentals Matter More Than Ever in the Age of AI Coding
AI coding tools can now generate entire features, refactor files, and even build small apps in minutes. But if you’ve tried to let an AI agent run wild on your codebase, you’ve probably seen how fast things can fall apart.
The core message: software fundamentals matter more than ever. Good design, clear requirements, and testable architecture are what turn AI from a chaos machine into a force multiplier.
Why “Specs-to-Code” Usually Fails
There’s a growing idea that you can write a high-level specification, feed it to an AI, and let it generate and regenerate your entire application. If something’s wrong, you just tweak the spec and recompile with AI—no need to look at the code.
In practice, this often leads to a familiar outcome: every iteration makes the code worse. You get something that runs, then you regenerate and it’s buggier, more tangled, and harder to reason about. Keep going and you end up with a pile of unmaintainable garbage.
Why? Because this approach ignores a basic truth from classic software design:
Bad code is extremely expensive. A codebase that’s hard to understand and change blocks you from taking full advantage of AI. AI thrives in clean, well-structured projects; it amplifies whatever structure you already have—good or bad.
Books like A Philosophy of Software Design and The Pragmatic Programmer have been warning about this for years. They describe how complexity and entropy creep in every time you change code without thinking about the overall design. AI doesn’t magically remove that problem—it accelerates it.
Fixing the First Failure Mode: “The AI Didn’t Do What I Wanted”
One of the most common frustrations with AI coding tools is simple: you ask for something, and what comes back is not what you had in mind.
This isn’t just a prompt problem; it’s a design problem. You and the AI don’t share the same mental model of what you’re building. In classic software terms, you don’t share a design concept—the invisible idea of the system that lives in people’s heads, not in a document.
A practical way to fix this is to deliberately build that shared concept before any code is written.
The “Grill Me” Technique
Instead of asking the AI to plan or code immediately, you can give it an instruction like:
“Interview me relentlessly about every aspect of this plan until we reach a shared understanding. Walk down each branch of the design tree, resolving dependencies between decisions one by one.”
This turns the AI into a design partner that asks dozens of clarifying questions—sometimes 40, 60, or even 100—before it commits to a plan. The result is:
- A much clearer shared understanding of what you’re building
- A conversation you can turn into a product requirements document or issues
- Far less rework when the coding starts
Instead of an AI that eagerly spits out a half-baked plan, you get an adversarial collaborator that forces you to think through trade-offs and edge cases up front.
Creating a Shared Language With AI
Another common failure mode: the AI is verbose, vague, or seems to talk past you. It uses terms inconsistently, or introduces concepts that don’t quite match your domain.
This is essentially a language gap problem, similar to what happens when developers and domain experts don’t share the same vocabulary.
Use a Ubiquitous Language
Domain-Driven Design (DDD) offers a powerful solution: the ubiquitous language. In a traditional project, this means developers and domain experts agree on a shared set of terms and use them consistently in conversations, docs, and code.
With AI, you can do something similar:
- Scan your codebase for important domain terms.
- Collect them into a markdown file with definitions and examples.
- Use this file as a reference for both you and the AI.
When you feed this "ubiquitous language" document into your AI assistant and keep it open while you work, a few things happen:
- The AI plans with less fluff and fewer contradictions.
- Generated code aligns better with your domain model.
- Your own thinking becomes clearer and more consistent.
This simple step dramatically reduces miscommunication and makes AI output feel much closer to what an experienced team member would produce.
Make Feedback Loops the AI’s Speed Limit
Even when the AI understands what you want, the next problem is obvious: the code doesn’t work. Or it kind of works, but it’s brittle and full of hidden bugs.
AI tools tend to “outrun their headlights”: they generate a huge amount of code, then only at the end do they run type checks or tests—if at all. Human developers learn not to do this; we rely on tight feedback loops.
To make AI code more reliable, you need to enforce those same loops:
- Static types (TypeScript, strong typing in backends, etc.)
- Automated tests that are easy to run and interpret
- Runtime feedback (e.g., giving the AI access to a browser to see UI behavior)
The key principle from The Pragmatic Programmer applies directly here: the rate of feedback is your speed limit. If your tests are slow or your architecture makes testing painful, AI will keep generating code faster than you can safely validate it.
Use TDD to Force Small, Safe Steps
Test-Driven Development (TDD) is especially powerful when working with AI because it forces small, deliberate steps:
- Write a failing test first.
- Have the AI make that test pass with minimal code.
- Refactor with the AI to improve design while keeping tests green.
AI is naturally inclined to do too much at once. TDD gives it a structure that keeps changes small, observable, and reversible. Over time, this leads to cleaner design and fewer regressions.
If you’re interested in scaling this mindset beyond coding into content and publishing workflows, you may also find it useful to see how others structure large AI-assisted projects, like in this guide on using AI to write a full non-fiction book in under an hour.
Deep Modules: Architecture AI Can Actually Handle
Even with good tests and types, AI struggles badly in messy architectures. Many AI-generated projects end up as a tangle of tiny, shallow modules: dozens or hundreds of small files, each exposing a few functions with complicated, leaky interfaces.
In this kind of codebase, both you and the AI get lost. It’s hard to see what matters, how pieces fit together, or where to safely make changes.
Deep vs. Shallow Modules
John Ousterhout’s idea of deep modules is a powerful antidote:
- Deep modules: lots of functionality hidden behind a simple, well-designed interface.
- Shallow modules: not much functionality, but a complex or noisy interface.
In a deep-module architecture:
- Each module has a clear purpose and boundary.
- Most of the complexity is hidden inside the module.
- Other parts of the system interact with it through a small, stable surface area.
This is exactly the kind of structure AI can work with effectively. You can:
- Design the interfaces yourself (names, responsibilities, inputs/outputs).
- Let the AI handle most of the internal implementation.
- Write tests at the module boundary to validate behavior.
Over time, you can refactor a shallow, fragmented codebase into one with deeper modules by:
- Identifying clusters of related behavior.
- Wrapping them behind a single, cohesive interface.
- Gradually simplifying the public surface while keeping tests passing.
Protect Your Brain: Design Interfaces, Delegate Implementation
One surprising side effect of AI coding tools is mental fatigue. You might be shipping more code than ever, but your brain feels overloaded trying to keep track of everything the AI is doing.
Deep modules help here too. When your system is organized into clear, well-named modules with simple interfaces, you don’t need to hold every implementation detail in your head.
A practical strategy:
- You focus on system design: modules, boundaries, interfaces, and invariants.
- AI focuses on local implementation details inside those modules.
- Tests verify behavior at the boundaries.
For non-critical parts of your system, you can treat modules as “gray boxes”: you understand what they should do and how to call them, but you don’t obsess over every line inside, as long as tests are solid.
This division of labor turns AI into a highly productive tactical programmer, while you stay in the strategic role—exactly where human judgment and experience matter most.
Invest in Design Every Day
The big takeaway is simple but powerful: code is not cheap. In the AI era, bad code is more expensive than ever because it blocks you from using AI effectively.
Instead of treating design as something you can outsource to an AI agent, treat it as the core of your job. As Kent Beck puts it, you should invest in the design of the system every day:
- Keep refining your module boundaries.
- Keep your ubiquitous language up to date.
- Keep tightening your feedback loops with better tests and types.
AI is an incredible on-the-ground programmer, but it still needs a human architect. Your long-term value isn’t in typing speed or boilerplate—it’s in making good design decisions, structuring systems, and keeping complexity under control.
If you’re exploring how to bring similar structure and leverage to other parts of your workflow, such as marketing or ecommerce content, it’s worth seeing how end-to-end AI pipelines are being built in areas like AI-powered ecommerce advertising as well.
In this new AI age, your fundamentals aren’t obsolete—they’re your superpower.
Comments
No comments yet. Be the first to share your thoughts!