Playbooks · Coding agents
Refactor a messy function with an agent, keeping behaviour
A solo builder cleans up one tangled function with Claude Code, with passing tests proving the behaviour did not change.
Audience: Solo builder · Stack: A git repo where the function already has tests, or where you can add them first · Tool: Claude Code (terminal CLI)
Before you start
- One function, chosen because it is hard to read, not because it is large. If the function spans several files or several responsibilities, split the task before starting — a refactor of everything is a rewrite.
- Tests that cover the function's current behaviour, passing before you start. If they do not exist, write a few first that pin down what the function does today, including its edge cases. Without them, “keeping behaviour” is a wish, not a check.
- Claude Code installed and logged in, as in the first playbook in this series. Run
claude --versionto confirm the install if you have not used it in this project yet. - A branch for the refactor, and a clean working tree, so the refactor diff is the only diff.
Steps
- Run the function's tests and record that they pass. This is the baseline the whole refactor is judged against.
- Start Claude Code with
claudein the project directory. Type/planto enter plan mode and describe the refactor as a constraint, not a style preference: same inputs, same outputs, same edge cases, clearer structure. Name the specific smells to remove — for example, a nested condition, a duplicated block, or a misleading name — and nothing else. - Read the plan. Reject any part that adds behaviour, changes an output, or “fixes” something while refactoring. A refactor that also fixes a bug is two changes, and the tests cannot tell you which one broke what.
- Let the agent refactor in small steps, running the function's tests after each step. If a step fails a test, stop and revert that step before continuing; do not let the agent adjust the test to fit the new code.
- When the tests pass, read the new function in full, without the diff, as a reader would. If you still cannot follow it, the refactor is not done — say what is still unclear and ask for one more pass on that part only.
- Run the wider suite for the area around the function, not just its own tests. A refactor can break callers even when the function's own tests pass.
- Commit with a message that says refactor only, and note the test command and its result in the commit message or pull request, so a reviewer can repeat the check.
Worked example
A solo builder has a function that prices an order: forty lines, three nested conditions, and a discount block copied twice. The function's tests pass. In plan mode they brief: “Same inputs, same outputs, same edge cases. Remove the duplicated discount block and flatten the nesting. Change no prices, no rounding, and no error messages.” The agent extracts the discount into one helper and flattens one condition; the tests pass after each step. Reading the result fresh, the builder finds one variable name still misleading, asks for that rename alone, re-runs the tests and the surrounding suite, and commits a refactor-only change. The function is shorter, the prices are identical, and the tests are the evidence.
What can go wrong
- A test fails after a refactor step. Fix: revert that step. Do not edit the test to match the new code — the test is the definition of the old behaviour for this task.
- The plan includes a behaviour fix “while we're here”. Fix: remove it from the plan and file it as a separate task. Mixed changes are the way refactors introduce bugs.
- The new code passes tests but reads worse. Fix: say so, concretely, and ask for one more pass on the unclear part. Readability is the point of the task; a passing suite does not certify it.
- Callers break even though the function's tests pass. Fix: run the surrounding suite before committing, and add a test for the caller case that broke, before fixing it.
Limits
- Tests define “same behaviour” only as far as they cover it. Untested edge cases can still change silently, which is why the pin-down-tests-first step matters.
- The agent cannot judge readability for your team. It can produce shorter code that your teammates still find hard to follow; the fresh-read step is yours.
- A refactor is not a performance improvement, a bug fix, or a redesign. If the plan drifts into any of those, it has stopped being a refactor.
Time and cost: NOT_MEASURED. This page gives no time or cost figure because the tool docs fetched for it state none that applies to this task.
Verification
Tier: AUTHOR_DOCS_VERIFIED · verified 2026-10-23. The workflow on this page was not run end to end in a sandbox for this page; every command, flag, and capability claim below was checked against an author doc or author repo fetched on 2026-10-23.
- https://github.com/anthropics/claude-code
- https://code.claude.com/docs/en/overview
- https://code.claude.com/docs/en/quickstart
Cut from this page, with reason:
- No line-count or complexity target: no author doc sets one for this task, so the page judges readability by a fresh read, not a number.
- No time or cost figure: NOT_MEASURED.
- No model named, for the same reason as the other Claude Code playbook.
If a step here does not match what your installed tool shows, trust the tool's own docs for your version and stop before approving a change you cannot read.
Back to: Coding playbooks hub (../../guides/coding/) · Skills index (../../skills/)