Skip to content

Refactoring Strategies

Your user service file has grown to 800 lines. It handles authentication, profile management, notification preferences, and account deletion — all in one file. Every developer on the team touches it for different reasons, merge conflicts are constant, and the test file is 1,200 lines of tightly coupled test cases. You know it needs to be split up, but the last time someone tried a big refactor, it broke three other services and took a week to stabilize.

AI-assisted refactoring solves the “too risky to touch” problem by making large refactors incremental, verifiable, and reversible. The key is breaking the refactor into small, safe steps where each step is verified by the existing test suite before moving to the next.

  • An incremental refactoring workflow that keeps the test suite green at every step
  • Copy-paste prompts for the most common refactoring patterns
  • Strategies for refactoring code that other services depend on
  • Techniques for using Cursor’s checkpoints and git to stay safe

Never refactor everything at once. Instead:

  1. Run the full test suite. Confirm it passes. Commit.
  2. Make one structural change (extract a function, move a file, rename a variable).
  3. Run the full test suite. If it passes, commit. If it fails, fix the specific breakage.
  4. Repeat until the refactoring is complete.

Each commit is a checkpoint you can return to. If step 7 of a 10-step refactor goes wrong, you only lose step 7’s work.

Split a large file into focused modules while maintaining backward compatibility:

When you need to change a function signature that is used across the codebase:

The createUser function currently takes 6 positional parameters.
Refactor it to take a single options object.
Before: createUser(email, password, firstName, lastName, role, teamId)
After: createUser({ email, password, firstName, lastName, role, teamId })
Update every caller across the codebase. There are approximately 15 call sites.
Run tests after updating each file to catch any mistakes immediately.

Pattern: Callback to Async/Await Migration

Section titled “Pattern: Callback to Async/Await Migration”
Rename the `getUserData` function to `getUserProfile` everywhere in the codebase.
This includes:
- The function definition in @src/services/user-service.ts
- All imports and call sites
- All test references
- Any string references in route definitions or documentation
Run tests after the rename to verify nothing broke.

For refactors that touch many files, use Plan mode to create a structured plan before executing:

I need to migrate our API from Express middleware pattern to a controller pattern.
Current structure:
- Routes define middleware chains directly
- Business logic is mixed into route handlers
- Validation happens at the route level
Target structure:
- Routes map URLs to controller methods
- Controllers handle request/response
- Services contain business logic
- Validation middleware is applied automatically
Create a detailed plan. For each step, specify:
- Which files to modify
- What the change is
- How to verify it works
Ask me up to 3 clarifying questions before creating the plan.

Review the plan, adjust priorities, then execute step by step with Agent mode.

For refactors where there are multiple valid approaches, use Cursor’s parallel agent feature:

  1. Send the same refactoring prompt to two different models
  2. Each works in its own worktree
  3. Compare the approaches side by side
  4. Apply the one that is cleaner and passes tests

After completing a refactoring, run your full verification suite before submitting:

Agent changes too many files at once. The refactoring step is too large. Break it into smaller pieces. “Move 3 functions to a new file” is better than “reorganize the entire service layer.”

Tests fail after a refactor but the code looks correct. The tests may be testing implementation details rather than behavior. Use Ask mode to analyze whether the test failure is a real regression or a test that needs updating.

Imports break after moving files. Make sure you are creating barrel exports (index.ts files) that re-export from the new locations. This maintains backward compatibility while you update imports incrementally.

Other team members’ PRs conflict with the refactoring. Communicate large refactors to the team before starting. Merge the refactoring quickly (in small PRs if possible) to minimize the conflict window.