AI Can Generate Code, But It Cannot Replace Engineering Discipline
contents
AI made the discipline matter more, not less
The common story is that AI coding tools let you skip the hard parts of engineering. My experience building real projects with them says the opposite. AI removes the slow part, typing, and leaves every hard part standing: deciding what to build, keeping it coherent, knowing when it’s wrong. If anything, those got harder, because now bad code arrives faster than you can vet it.
I build with AI-generated code daily on my own projects, and the lesson is consistent. The model can produce a working-looking module in seconds. Whether that module belongs in your codebase is a question it can’t answer for you. That’s still engineering, and it’s still on you.
What this solves
- Corrects the idea that AI replaces engineering judgment.
- Explains why speed makes structure and tests more important.
- Gives practical rules for not creating an unmaintainable mess.
AI can create code faster than you can understand it
The core danger is a speed mismatch. The model writes faster than you can read, and far faster than you can truly understand. So you get a pile of plausible code, and the bottleneck quietly moves from “can I produce this?” to “do I actually know what this does?” If you skip that second step, you’re accumulating code you can’t maintain.
I’ve watched myself accept a slick fifty-line function because it looked right and the tests were green, then lose an hour later when it broke in a way I couldn’t reason about, because I never really read it. That hour is the bill for borrowed understanding. The code was generated. The comprehension wasn’t, and comprehension is the thing you actually need when something fails at the worst possible time.
Here’s a real example from ResearchBridge, the capstone I’ve built. I asked the model to build a tool-calling agent with three tools: retrieve school requirements, compare the student’s profile against those requirements, and submit a readiness report. The generated code looked clean. Each tool worked in isolation. Tests passed. But there was a hidden ordering dependency: the submit_readiness_report tool needed data from the other two tools, which were stored in variables that started as None. If the LLM called the tools out of order, the code crashed with a TypeError. The system prompt tried to enforce ordering, but there was no code-level guard. The code was technically correct. The architecture was fragile. I only caught it by thinking through failure modes, not by running tests. That’s the discipline: the model wrote the code, but I had to verify it handled the cases I cared about.
So the first rule is uncomfortable but simple. If you don’t understand the code, it isn’t done, no matter how green the tests are. Generated and understood are two different states, and only one of them is safe to ship.
Start with structure, not code
The biggest leverage isn’t in the generation. It’s in what you decide before you generate anything. When I let the model dive straight into implementation, I get something that works in isolation and fights the rest of the system. When I define structure first, where things live, what the boundaries are, what each piece is responsible for, the generated code has somewhere sensible to land.
So I split the work into two modes. First a planning conversation: no code, just deciding the shape. What are the modules? Where’s the boundary between a route handler and the service logic behind it? What’s the data flow? Only once that’s settled do I move to an implementation session where the model fills in the structure I already chose. Plan, then build. Not build, then discover I had no plan.
This matters more with AI, not less, because the model will happily generate infinite code with no architecture at all. It has no opinion about whether your project stays coherent. You have to supply that opinion up front, or the codebase becomes a heap of locally-correct, globally-incoherent parts.
Break bundled tasks into small problems
AI degrades when you hand it a big, bundled task. Ask it to “build the auth system with sessions, rate limiting, and password reset” and you get a sprawling response that’s hard to review and harder to trust. Each piece might be fine. The bundle is a mess, because you can’t reason about all of it at once, and neither, reliably, can the model.
So I break things down before handing them over. One problem per session, scoped small enough that I can fully review the output. Build the route handler. Review it. Then the service function behind it. Review that. The smaller the unit, the more completely I understand what came back, and understanding is the whole point.
The same logic applies to chats. I keep a session focused on one problem and start a new chat when I move to a new one. A long thread that’s drifted across five unrelated tasks fills the context with stale, half-relevant detail, and the model’s output gets worse as the noise climbs. A clean, focused context produces sharper code. Context is a resource, and treating it like an infinite dumping ground is how the quality quietly rots.
Tests, commits, and modular files are the safety rails
Here’s the part that AI makes non-negotiable. When code arrives this fast, you need rails that catch mistakes just as fast, because AI accelerates both your progress and your errors. Tests, commits, and modular files aren’t process overhead anymore. They’re what keeps a fast-moving project from becoming unrecoverable.
Tests are the check on code you didn’t write by hand: they tell you the generated function actually does what you assumed, instead of merely looking like it does. Commits are the undo button, and I commit far more often than I used to, because AI can introduce a subtle breakage in seconds, and a tight commit history means I can find and drop exactly the bad change instead of unwinding an afternoon. Modular files keep the blast radius small, so a bad generation damages one well-bounded place instead of leaking through everything.
None of this is new advice. Structure, tests, version control, that’s just engineering. The point is that AI didn’t make these optional by handling the hard part for you. It made them load-bearing by removing the natural slowness that used to hide your lack of them. The typing got automated. The discipline didn’t, and the discipline was always the job.