Lesson 0005 · Core Designer · Module 1
Structural Issues in AI-Generated Code
AI output is legacy code from an author who won't answer questions. This lesson turns your structural eye on the five defects AI code most reliably carries.
Mission tie-in: you'll generate more code with AI, not less. The skill that keeps you in control is the ability to review structure the way you'd review a stranger's pull request — this lesson is that review checklist.
Knowledge: the five recurring defects
AI produces fluent, plausible code — and fluently repeats the same structural defaults:
- God functions and helper sprawl. One function that does everything, or the opposite: twenty micro-helpers with no shape, built to keep each output chunk short.
- Hidden global state. Module-level mutable containers, caches, and singletons that make behavior depend on call order.
- No invariants. Data accepted and mutated freely; invalid states possible by construction.
- Bare exception handling.
except Exceptionwithpass, or errors converted toNone— failure becomes invisible and travels. - Missing seams. Hardcoded I/O, sleep, and environment access inside logic that should be pure and testable.
Treat AI output as legacy code from an unknown author. The discipline that applies is the one Michael Feathers wrote for legacy code: find the seam, lock the behavior with a characterization test, then restructure with confidence.
def process_all(users: list[dict]) -> None:
processed = []
cache = {} # rebuilt every call: dedup never spans batches
for u in users:
if u["email"] in cache:
continue
try:
resp = requests.post("https://api.example.com/notify", json=u)
if resp.status_code != 200:
return # silently stops mid-batch
cache[u["email"]] = True
processed.append(u)
except Exception:
pass # swallows everything
return processed # annotated -> None, yet returns a list on the happy path
Name what's wrong — and this snippet carries nearly every defect on the list: network I/O welded to logic (no seam, so no test runs without a network), a bare except Exception that swallows a typo as readily as a timeout, a mid-loop return that abandons the remaining users and hands back None, a return type that contradicts the annotation, and a cache rebuilt on every call — so the de-duplication it appears to buy never survives a second batch.
| You see this | What it costs | The move |
|---|---|---|
except Exception: pass | The failure is erased at the moment it was most informative. | Catch the specific error; record it or re-raise. |
| _cache = {} at module scope | Behaviour now depends on import order and call order. | Pass the cache in, or scope it to one object. |
| requests.post inside a rule | No seam: the test needs a network to run at all. | Inject a port; the adapter owns requests. |
| return inside a batch loop | One bad item silently abandons every item after it. | Collect a result per item; report the whole batch. |
| six one-line helpers, each used once | Helper sprawl: shape without structure, nothing named. | Inline them; keep only boundaries that mean something. |
Skill: run the AI review checklist
In the snippet, the mid-loop return is dangerous because:
The bare except Exception: pass is bad because:
Best first step when AI code "works" but scares you:
Practice on your own code
Take a function AI generated for you recently. Run the five-point checklist (god function, hidden global state, missing invariants, bare exceptions, missing seams). Fix exactly one defect, with a test.
Reveal: the snippet's refactor direction
Split into two seams: a pure should_notify(user, seen) decision, and an Notifier adapter around the HTTP call. The batch loop raises on failure (fail-fast, see lesson 0014), the cache becomes an explicit parameter owned by the caller, and dead code is deleted. Each defect got a name, a home, and a test.
Your win
You can now review AI output with a five-point checklist instead of vibes — and you've learned the deeper move: treat it as legacy code, lock it with tests, then restructure.
Read and watch deeper
- Working Effectively with Legacy Code, Feathers — ch. 2 "Working with Feedback" and ch. 13–14: the characterization-test discipline applied to unfamiliar code.
- A Philosophy of Software Design, Ousterhout — ch. 16 "Modifying Existing Code": how to improve unfamiliar code without breaking it.
- Clean Code, Martin — ch. 7 "Error Handling": why bare excepts and silent returns are structural defects.
- Watch: ArjanCodes YouTube — search "AI code review" or "AI coding best practices".
- Next: lesson 0010 covers steering AI toward better structure from the start.
- Reference: Patterns — seam / characterization test; glossary — seam, characterization test.
Paste an AI-generated function to your agent-teacher and ask for the five-point review — then argue with its findings.