Software Design Reference Core Designer · Seeing Structural Problems

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:

  1. 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.
  2. Hidden global state. Module-level mutable containers, caches, and singletons that make behavior depend on call order.
  3. No invariants. Data accepted and mutated freely; invalid states possible by construction.
  4. Bare exception handling. except Exception with pass, or errors converted to None — failure becomes invisible and travels.
  5. 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.

The AI didn't invent these defects — it sampled them from the world's code, where they are common. That is exactly why your eye, not the generator's fluency, is the quality gate.
Field notes · the five defects, as they actually appear
You see thisWhat it costsThe move
except Exception: passThe failure is erased at the moment it was most informative.Catch the specific error; record it or re-raise.
_cache = {} at module scopeBehaviour now depends on import order and call order.Pass the cache in, or scope it to one object.
requests.post inside a ruleNo seam: the test needs a network to run at all.Inject a port; the adapter owns requests.
return inside a batch loopOne bad item silently abandons every item after it.Collect a result per item; report the whole batch.
six one-line helpers, each used onceHelper 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

Paste an AI-generated function to your agent-teacher and ask for the five-point review — then argue with its findings.