Stage 05 · SpecialtiesModule 25 of 26~7h

Software Engineering

Pair-programming, code review, and migrations.

← All modules in this stage

Module 12 covered the patterns. This module is about the day-to-day — the workflows working engineers actually use Claude for, the ones that survive sprints and code reviews.

By the end of this module you'll have

Time: about 1.5 hours for the basics, ~7 hours with all three notebooks.

Prerequisites: Modules 12 (code generation), 14 (production patterns), 20 (testing & evaluation).


What "AI smell" is, and how to avoid it

You can usually spot AI-generated code in a PR. Tell-tale signs:

This isn't aesthetics; it's a signal that the engineer didn't engage. Fix it before merging by reading the diff as if you wrote it. If something feels redundant, delete it. The fastest way to fix AI smell is the same as the fastest way to fix junior-engineer smell: a stricter system prompt, plus a stricter you.

A useful system prompt fragment:

- No defensive checks for impossible states.
- No docstrings that just restate the function name.
- Comments only when the WHY is non-obvious. Default: no comments.
- Use the smallest type annotations that pin the contract; no TypeAlias soup.
- Match the existing project style. Read the surrounding files first.

Recipe 1 · Pair-programmer workflow

The shape that works:

  1. You write the function signature and a one-line docstring. That's the spec.
  2. Claude writes the body. Run it. Read the output.
  3. You write the tests. This is the part you keep, even if Claude could do it. Writing the tests is how you understand the code.
  4. Claude reviews the diff with the criteria from Module 12.
# 1. You write:
def normalise_phone(s: str, default_country: str = "GB") -> str:
    """Convert a string to E.164. Raise ValueError if it can't be normalised."""

Then ask Claude:

prompt = (
    "Implement the function below. Follow the rules: stdlib only or `phonenumbers` if needed; "
    "no docstring beyond what's there; raise ValueError with a useful message; no defensive "
    "checks for impossible states.\n\n" + open("phone.py").read()
)

The discipline: you wrote the contract. Claude filled in the implementation. You write the tests. Reviewers read it the way they'd read any PR.


Recipe 2 · The mechanical migration

These are the jobs Claude is unusually good at — repetitive, mechanical, well-defined.

example = """
BEFORE (callsite):
    result = client.execute(query, params=p)

AFTER (callsite):
    result = client.run(query, parameters=p)
"""

prompt = f"""
We renamed `execute(..., params=...)` to `run(..., parameters=...)` on the database client.
Here is one example of the rewrite:
{example}

For the file below, output the diff that performs this migration. Do not modify anything else.
Show the diff in unified format.

FILE:
{open('some_module.py').read()}
"""

Run that across every file (git ls-files | xargs -I{} ...). For more complex migrations, step the model through with a checklist of edge cases.

Where this beats sed/codemod: when the rewrite is almost mechanical but has small contextual decisions ("if there's already a parameters= in the call, don't add a duplicate").


Recipe 3 · Reading unfamiliar code

Onboarding to a new codebase, debugging something you didn't write, or reviewing a 2,000-line PR — Claude can dramatically speed up understanding, even when you wouldn't trust it to write a line.

prompt = (
    "Read the file below and produce: "
    "1) a one-paragraph summary of what it does, "
    "2) a list of the 3–5 most surprising design decisions, "
    "3) the names of the public functions other code probably calls, "
    "4) the riskiest part to change. "
    "If anything is unclear, list it under 'unclear'. Do not invent.\n\n"
    + open("legacy/parser.py").read()
)

This output is for you, not the codebase. Don't paste it into a doc. Use it to navigate — it's the equivalent of asking a senior engineer for a 5-minute walkthrough.


When to commit Claude's code, when to rewrite

Situation Default
Greenfield function with clear spec, you wrote the tests Commit
Migration / mechanical refactor across many files Commit (after running tests)
Performance-critical inner loop Rewrite. Claude defaults to clear; you need fast.
Cryptography, auth, money Rewrite. No "looks roughly right" allowed.
Code that ends up in a security review Rewrite or extensively review.
You couldn't write it yourself Don't commit. You won't be able to maintain it.
It "just works" but you can't quite see why Rewrite. The bug you can't see is the worst kind.

That last one matters. The goal isn't typing speed — it's a maintainable codebase. If Claude solved a problem you don't understand, that's a learning opportunity, not a free win.


A small workflow that compounds

Save this in your shell:

function plz () {
    python -m my_app.claude_code "$@"   # your wrapper around the API
}

git diff --staged | plz "Review this diff for bugs, security issues, and missed edge cases. Skip style."

You're now one keystroke away from a useful pre-commit review. Wire it as a Git hook if you trust your team. Module 14's logging tells you when it's costing more than it's worth.


Try changing one thing


Going deeper: open the notebooks


Module checklist


Next module

Module S6 · Product Management — and the closer for the curriculum.