Software Engineering
Pair-programming, code review, and migrations.
← All modules in this stageModule 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
- A clean pair-programmer workflow that doesn't litter your codebase with "AI smell"
- A migration recipe for the tedious refactors nobody wants to do
- A working sense of when to commit Claude's code and when to rewrite it
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:
- Verbose docstrings that restate the function name.
tryblocks that catchExceptionand re-raise unchanged.- Defensive checks for impossible states (e.g. checking that a freshly-created list is not None).
- Generic names:
helper,util,process_data. - Comments that explain what (which the code already shows), never why.
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:
- You write the function signature and a one-line docstring. That's the spec.
- Claude writes the body. Run it. Read the output.
- 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.
- 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
- Take your last "I'll get to that someday" refactor. Write the migration prompt. Run it. Commit only the parts that pass tests.
- Generate documentation for one undocumented module by asking for "the WHY notes a senior would leave for the next maintainer". Edit it. Commit.
- Write a system prompt for your codebase — naming conventions, error-handling style, test patterns. Reuse it for every code task. The output quality jumps measurably.
- Run the eval harness from Module 20 over a corpus of "good" and "bad" code-review outputs. Calibrate your own prompt for code review.
Going deeper: open the notebooks
notebooks/01_introduction.ipynb— pair-programmer flows, AI smell (~1.5–2h)notebooks/02_intermediate.ipynb— large-scale migrations, refactor pipelines (~2–3h)notebooks/03_advanced.ipynb— secure code review, architecture-level conversations (~1.5–2.5h)
Module checklist
- [ ] You've completed a real task with the spec/implement/test/review loop
- [ ] You've spotted (and fixed) AI smell in your own diff
- [ ] You can list three categories of code where you would not commit Claude's first draft
- [ ] You have a project-specific system prompt you reuse
Next module
Module S6 · Product Management — and the closer for the curriculum.