Refactoring with Claude Code
Claude Code's biggest refactoring advantage is its ability to read the full codebase before making any change. A rename that touches 23 files, an extraction that creates 3 new modules, a restructure that shuffles 15 imports — Claude handles these atomically, with context that no IDE refactoring tool has. This page covers the prompts and patterns that make large refactors safe.
The Golden Rule: Read Before Refactor
Always ask Claude to map the code before touching it. This is the single step most developers skip, and it's why refactors go wrong.
Read all files in src/services/ and tell me: what does each file do, what are its dependencies, and what calls into it? I want to understand the shape before we change anything.
Given what you just read: I want to extract the email validation logic into src/utils/validation.ts. What files would you change, in what order, and what could go wrong? Describe the plan before making any changes.
Execute the plan you described. After each file change, run the relevant tests. If any test fails, stop and tell me before continuing.
The "stop and tell me" instruction is load-bearing. Without it, Claude will try to fix a broken test instead of pausing — which can cause cascading changes that are hard to review.
Rename Across the Entire Codebase
Rename a function
Rename the function processPayment to chargeCard across all files. Find every caller using grep, update the definition in src/services/payment.ts, update every caller, update the test file, and run npm test to confirm nothing broke.
Rename a class or type
Rename the UserService class to AccountService. Update: the class definition, all import statements, all instantiations, any type annotations that reference it, and the test file. Then run npx tsc --noEmit to confirm the TypeScript build is clean.
tsc --noEmit catches missed callers that tests might not cover. Running it as a final step before committing is the fastest way to confirm a rename is complete.Extract Functions and Modules
Extract a function
Extract the email validation logic from UserController.handleRegistration() into a standalone validateEmail(email: string): boolean function in src/utils/validation.ts. Update the controller to import and call it. Run npm test after the extraction.
Extract a module from a large file
src/utils.ts has grown to 800 lines. Extract the date formatting functions (formatDate, formatRelativeTime, parseISO) into a new file src/utils/dates.ts. Re-export them from src/utils.ts so existing callers don't break. Once all callers are updated to import from the new path, remove the re-exports.
The re-export step is the key to zero-downtime module extractions. Move the code, re-export from the old location, migrate callers incrementally, then remove the shim. Claude can do all of this in one session if you describe the steps explicitly.
Restructure a Directory
Restructure src/utils/ from a flat directory into three subdirectories: src/utils/validation/, src/utils/formatting/, and src/utils/api/. Move each file to the appropriate subdirectory. For each move: (1) create the new file, (2) update all imports in the codebase, (3) run tests. Don't remove the old files until all callers are updated. After all moves, delete the old files and verify npx tsc --noEmit passes.
Refactor Types
Add a property to a shared interface
Add a required createdAt: Date property to the User interface in src/types.ts. Find every place that constructs a User object and add the property. Run npx tsc --noEmit to find anything missed, then fix each remaining type error. Don't use as any or ! to silence errors — fix the underlying issue.
Change a type shape
Change the userId field on the Order type from string to number. Trace all the downstream effects: database queries, API response serializers, and test factories. Run npx tsc --noEmit to find type errors and fix them. Don't touch test assertions — if a test breaks, the implementation is wrong, not the test.
Refactor for Performance
Remove N+1 queries
GET /api/orders is slow because it loads user data for each order in a separate query (N+1). Refactor src/routes/orders.ts to batch-load users with a single query. Use the existing UserRepository.findByIds(ids: string[]) method. Run the existing tests after — they should all still pass.
Refactoring Anti-Patterns to Avoid
| Anti-pattern | Why it breaks | What to do instead |
|---|---|---|
| Refactor without reading first | Claude misses callers it didn't read | Always Phase 1: read and map |
| Giant single-step refactor | Hard to review, one failure cascades | Break into: rename → extract → restructure |
| Skip test verification | Regressions are invisible until prod | Run tests after each stage |
Use as any to silence type errors | Type errors are signals, not noise | Fix the underlying issue |
| Delete old files before callers updated | Breaks build mid-refactor | Re-export from old location until done |
| Refactor and add features simultaneously | PRs are impossible to review | Separate PRs: refactor first, feature second |
Safe Refactor Checklist
- Commit current state — creates a rollback point before any changes
- Ask Claude to read first — map the code before touching it
- Ask for the plan — what files, in what order, what risks?
- Run tests before starting — confirm you have a green baseline
- Refactor in stages — run tests after each stage
- Verify the build —
tsc --noEmitor equivalent after all changes - Review with
/review— catch anything Claude missed in the implementation
FAQ
How do I refactor a large codebase with Claude Code?
Use a two-phase approach: (1) Ask Claude to read and map the code first — this gives Claude the context it needs. (2) Then ask for the refactor with explicit verification steps: "rename X to Y across all files and run the tests after." Claude will make the changes atomically and verify with your test suite.
Can Claude Code rename a function across all files?
Yes. Claude Code uses ripgrep to find all usages before renaming, making cross-file renames reliable. Explicitly ask Claude to verify the build or run tests — otherwise it renames without checking for missed callers.
How do I extract a function or module with Claude Code?
Give Claude the target and the destination: "Extract the email validation logic from UserController into a standalone validateEmail function in src/utils/validation.ts. Update the controller to import and call it. Run the tests after." The "run the tests after" instruction is critical — extraction often breaks callers in non-obvious ways.
What is the safest way to do a large refactor with Claude Code?
Commit first (rollback point), ask Claude to explain its plan before making changes, do the refactor in stages with tests after each, and never do a giant refactor in one step. Each stage should leave the tests green. Use /review after each stage to catch regressions before they compound.
How do I restructure a module with Claude Code without breaking everything?
Use the strangler fig approach: create the new structure alongside the old, re-export from old locations to avoid breaking callers, migrate callers incrementally, then remove the old re-exports last. Ask Claude to follow this pattern explicitly in your prompt.