Refactoring code with AI: small steps and tests
An agent can rewrite a module in a minute and break it just as fast. How to organize refactoring: tests first, one kind of change at a time, a commit per step, rollback.
5 min · · development · агенты
AI for code refactoring works well when you have set the frame: tests exist, the step is small and the result is checked by a command. Without that, an agent confidently rewrites a module, and an hour later it turns out behavior changed in three places nobody thought about. Below is a safe process: what to prepare first, how to word the steps, which rules to give the agent, how to roll back, and where AI really speeds things up and where it does not.
Step 0. Preparation: tests and a clean tree
Refactoring by definition does not change behavior, and "does not change" can only be proven with tests. Before you bring in an agent:
- Commit anything unrelated. The working tree should be clean so the agent's diff is visible on its own:
git statusis empty. - Create a branch:
git switch -c refactor/orders-service. - Run the tests and confirm they pass before you start. Red tests before refactoring mean you cannot tell old breakage from new.
- Cover what you will change. If a module has no tests, the first task is writing "characterization" tests: they pin the current behavior, however odd it is. This task is convenient to hand to an agent: the result is checked by running it. There is a separate article on generating tests: auto-generating unit tests in Claude Code.
- Remember the check command, for example
pytest -qornpm test. Give the same one to the agent.
Step 1. Rules for the agent
Agents read a project rules file (CLAUDE.md for Claude Code, AGENTS.md for Codex and opencode). For refactoring it helps to write a few lines there:
## Refactoring rules
- Do not change behavior. Public signatures and response formats stay the same.
- One kind of change per step: renaming OR extracting a function OR typing.
- After each step run `pytest -q` and show the result.
- Do not touch files outside the named module without separate approval.
- Do not delete tests or weaken checks to make tests pass.
- If a step needs a design decision, stop and ask.The last two points matter most. An agent told "make the tests pass" sometimes takes the shortest path: it edits the test, not the code. That is why the ban on editing tests is worth writing down explicitly.
Step 2. Small diffs
A big task like "refactor the orders service" yields a big diff that nobody can read. Split it into steps that can each be checked in a minute:
| Step | Wording | What to check |
|---|---|---|
| 1 | Add type annotations in orders/service.py without changing logic | tests green, diff only in signatures |
| 2 | Extract the discount calculation from create_order into calc_discount | tests green, logic moved verbatim |
| 3 | Replace duplicated status checks with a call to ensure_status | the list of replaced places matches grep |
| 4 | Rename tmp and data2 meaningfully | only names in the diff |
After each step: read the diff, run the tests, make a commit such as refactor: add types to orders/service. Many small commits let you roll back exactly one step without losing the rest.
An example request to an agent for step 2:
In orders/service.py extract the discount calculation from create_order into a
separate function calc_discount(order). Do not change or optimize the logic. Then
run pytest -q and show the result. If tests fail, do not fix the tests, explain what changed.Step 3. Verification and rollback
After each step:
git diff --stat # how many files were touched
git diff # read it yourself
pytest -q # verify behavior
git commit -am "refactor: extract calc_discount"If something goes wrong:
git restore .discards uncommitted changes in the working tree;git revert <hash>undoes a specific commit without rewriting history;git reset --hard <hash>returns the branch to a known state but deletes everything after it, so use it deliberately.
You can automate verification with a hook: run the tests after every agent edit. How that is set up in Claude Code is described in the article on hooks.
AI for code refactoring: where it speeds things up and where it does not
It speeds up:
- mechanical renames and moving code around the project;
- extracting functions and classes, splitting long functions;
- adding types, docstrings, annotations;
- migrating to a new API call by a pattern (many repetitive replacements);
- writing characterization tests before a change.
It does not help or gets in the way:
- architecture decisions: where to put a responsibility, how to cut modules. The agent will suggest an option, but the choice is yours;
- code without tests and without a way to run it locally: there is nothing to verify with;
- dynamic spots (reflection, metaprogramming, string names), where finding references is unreliable and the agent may miss calls;
- "optimizations" by eye: claims that the new version is faster need measurements.
A practical criterion: if the result of a step can be checked by a command in a minute, the step suits an agent. If checking requires a judgment about design, that is your work, and you give the agent the execution.
What a session costs
Suppose a refactoring takes 40 agent requests: on average 60,000 input tokens (history and files) and 1,200 output tokens per request. Input is 2.4M, output 0.048M. Zukko prices as of October 18, 2026, $ per 1M tokens.
Without cache:
| Model | Calculation | Total |
|---|---|---|
claude-sonnet-5-5 (0.4 / 2) | 2.4 × 0.4 + 0.048 × 2 = 0.96 + 0.096 | $1.06 |
gpt-6.1-sol (0.27 / 1.32) | 2.4 × 0.27 + 0.048 × 1.32 = 0.648 + 0.0634 | $0.71 |
deepseek-v4.1-flash (0.013 / 0.038) | 2.4 × 0.013 + 0.048 × 0.038 = 0.0312 + 0.0018 | $0.03 |
With Claude cache in the Anthropic format (assumption: of the 60,000 tokens in a request, 55,000 are read from cache and 5,000 new ones are written): read 40 × 55,000 = 2.2M × 0.05 = 0.11; write 40 × 5,000 = 0.2M × 0.5 = 0.10; output 0.096. Total about $0.31 instead of $1.06.
This is an illustration: the real cache share depends on the tool and session length. An important caveat: the Claude cache discount exists in the Anthropic format (Claude Code, /v1/messages), while through /v1/chat/completions all input is billed at the input price. The shorter the session and the fewer files the agent keeps in context, the smaller the input at every step. The strength index (claude-sonnet-5-5 has 48, deepseek-v4.1-flash has 39) does not guarantee the result: test two or three models on your own module.
FAQ
Can I trust AI with refactoring without tests?
You should not. Without tests you cannot tell preserved behavior from changed behavior. Characterization tests first, refactoring second.
Which AI for code refactoring should I choose?
We do not claim that one model does it better. Compare by price, context and strength index, then run the same small step through two or three models and compare the diff and the cost.
How do I stop the agent from rewriting too much?
Limit the task to one module and one kind of change, write the rules into CLAUDE.md or AGENTS.md, and read git diff --stat after every step.
What if the agent loops and spends money?
Set a daily limit on the key in the dashboard and watch usage. More in the article how not to lose money when an agent loops.
You can connect an agent to Zukko by following the Claude Code guide, and you create the key in the dashboard.
zukko
Pay 25×
less
for Claude Code & ChatGPT
One fetch_* key and base URL api.zukko.pro — for Cursor, Claude Code, Codex, and any SDK.
Related articles
- Prompt Injection: What It Is and How to Protect a Bot and an AgentHow prompt injection works through user input and third-party documents, and which measures limit the damage: data isolation, tool permissions, key limits.
- Claude Code YOLO: Permission Modes and Working Without PromptsClaude Code has no mode literally called YOLO, but it has several permission modes and flexible rules. How to stop clicking Yes without losing files.
- Claude Code: a checklist of best practices for daily workHabits that make working with Claude Code predictable: a short CLAUDE.md, a plan before edits, small tasks, checking results and keeping spend under control.
- An MCP server in Java: a minimal example with one toolWe write a minimal MCP server in Java with one tool, register it in Claude Code, and learn to find the cause when the agent doesn't see it.