Refactoring with AI: Patterns That Work
Learn how to safely refactor code with AI assistance. From extract function to complete rewrites, master AI-powered code improvement.
Refactoring with AI: Patterns That Work
Clean up code without breaking it. AI makes refactoring 10x safer.
Code rots. What worked six months ago is now a tangled mess. AI makes refactoring faster and safer—it can see patterns you miss and help you restructure without breaking things.
What you’ll learn:
- The golden rule (tests first!)
- 8 refactoring patterns with prompts
- When to refactor vs. when to ship
- Safety strategies for large changes
| Pattern | Use When | Prompt Start |
|---|---|---|
| Extract Function | Function > 30 lines | ”Extract the [logic] into…” |
| Extract Component | Component > 200 lines | ”Break this into smaller…” |
| Extract Module | File has mixed concerns | ”Separate these into…” |
| Rename | Names are unclear | ”Suggest better names…” |
| Simplify | Too many branches | ”Simplify using early returns…” |
| Remove Duplication | Copy-paste code | ”Extract shared utility…” |
The Golden Rule
Never refactor without tests.
Before any refactoring:
I'm going to refactor [COMPONENT/FUNCTION/MODULE].
Before I change anything:
1. What tests exist for this code?
2. What additional tests should I add to ensure
the refactor doesn't break anything?
3. What's the current public API I need to preserve?
If no tests exist, write them first. Then refactor.
Core Refactoring Patterns
1. Extract Function
When: A function does too much.
This function is 80 lines. Extract smaller functions with
descriptive names so the main function reads like documentation.
[paste function]
Rules:
- Each extracted function should do ONE thing
- Names should describe WHAT, not HOW
- Keep extracted functions in same file unless reusable
- Preserve all existing behavior
2. Extract Component
When: A React component is too large.
This component has grown to 300 lines. Break it into smaller,
focused components.
[paste component]
Guidelines:
- Identify logical UI sections
- Each component should have one responsibility
- Props should be minimal and well-typed
- Keep state at the appropriate level
- Don't over-extract (some code is fine together)
3. Extract Module
When: A file has unrelated concerns.
This file contains utilities for:
- Date formatting
- String manipulation
- API helpers
- Validation
Split into separate modules:
- utils/date.ts
- utils/string.ts
- utils/api.ts
- utils/validation.ts
Create an index.ts that re-exports for backward compatibility.
4. Simplify Conditionals
When: Nested if/else is unreadable.
Simplify this conditional logic:
[paste code with nested conditionals]
Options:
- Early returns
- Guard clauses
- Switch/case
- Object lookup
- Polymorphism (if complex enough)
Choose the clearest approach for this situation.
5. Remove Duplication
When: Same code appears multiple places.
These three functions have duplicated logic:
[paste functions]
Extract the shared logic into a reusable helper.
Keep the unique parts in each function.
Make sure existing callers don't need to change.
6. Improve Naming
When: Code is hard to understand.
Improve the naming in this code:
[paste code]
Current names are vague or misleading.
New names should:
- Describe intent, not implementation
- Be consistent with the codebase
- Be searchable (not too short)
- Follow conventions: [your conventions]
Safe Refactoring Workflow
Step 1: Understand Current State
Analyze this code:
[paste code]
Tell me:
1. What does it do?
2. What are the inputs and outputs?
3. What edge cases are handled?
4. What's the current public API?
5. What's coupled to this code?
Step 2: Write Characterization Tests
Write tests that capture the CURRENT behavior of this code,
even if that behavior seems wrong.
[paste code]
These tests should pass with the existing implementation.
We'll use them to verify the refactor doesn't change behavior.
Step 3: Plan the Refactor
I want to refactor this code to [GOAL].
Current code:
[paste code]
Before coding, outline:
1. What changes will you make?
2. What order will you make them?
3. What could go wrong?
4. How will we verify success?
Step 4: Refactor in Small Steps
Let's refactor step by step.
Step 1: [specific small change]
Show me just this step. I'll test before moving to step 2.
After each step: run tests, verify behavior unchanged.
Step 5: Clean Up
The refactor is working. Final cleanup:
1. Remove any dead code
2. Improve any remaining names
3. Add/update documentation
4. Verify types are correct
Refactoring Prompts by Goal
”Make it Readable"
Refactor for readability:
[paste code]
Goals:
- A new developer should understand it in 2 minutes
- Functions should be <20 lines
- Names should be self-documenting
- Magic numbers should be named constants
Keep behavior identical.
"Make it Testable"
Refactor for testability:
[paste code]
Current problems:
- [Hard to mock dependencies]
- [Hidden side effects]
- [Global state]
Make it easy to test by:
- Injecting dependencies
- Separating pure logic from side effects
- Making inputs/outputs explicit
"Make it Faster"
This code is slow. Profile shows [bottleneck].
[paste code]
Optimize for performance while keeping:
- Same API
- Same behavior
- Readable code (no premature optimization)
Show before/after and explain the improvement.
"Make it Type-Safe"
Add proper TypeScript types to this JavaScript:
[paste code]
Requirements:
- No 'any' types
- Proper generics where applicable
- Strict null checks
- JSDoc preserved or converted to types
"Make it Maintainable”
This code works but is hard to maintain.
[paste code]
Refactor to:
- Separate concerns
- Reduce coupling
- Make changes easier
- Make the code self-documenting
Explain your changes.
Dangerous Refactors (Handle with Care)
Complete Rewrites
Warning: Most rewrites fail. Prefer incremental refactoring.
If you must rewrite:
I need to rewrite this module. Before we start:
Current code:
[paste code]
1. Document EVERYTHING it does (including edge cases)
2. List all callers and how they use it
3. Define the exact API the new version must match
4. What tests must pass?
Only after we've captured all this, show me the new version.
Changing Public APIs
I want to change this API:
Current:
function oldApi(a, b, c) { ... }
New:
function newApi({ a, b, c, d }) { ... }
Help me:
1. Create the new function
2. Make old function call new function (backward compat)
3. Add deprecation warning to old function
4. Update all callers gradually
Database Schema Changes
I need to change the database schema:
Current schema:
[paste schema]
Desired schema:
[describe changes]
Create a migration that:
- Transforms data correctly
- Is reversible
- Handles edge cases
- Has zero downtime (if needed)
Refactoring Anti-Patterns
❌ Big Bang Refactors
Don’t change everything at once. Change one thing, test, repeat.
❌ Refactoring Without Tests
You will break something. Tests catch it before users do.
❌ Refactoring While Adding Features
One thing at a time. Refactor OR add features, not both.
❌ Over-Abstracting
Should I create an abstraction for [PATTERN]?
Answer: Only if you have 3+ concrete use cases. Otherwise, keep it simple.
Key Takeaways
- Tests first — Never refactor without safety net
- Small steps — One change at a time, test between
- Preserve behavior — Refactoring changes structure, not function
- Document intent — Tell AI what you want, not how to do it
- Know when to stop — Good enough is better than perfect
🎯 Refactor Something Today
- Find your longest function or messiest file
- Write a test for it (or ask AI to generate one)
- Pick ONE pattern from the table above
- Apply it with AI assistance
- Run tests to verify nothing broke
Small refactors compound. Do one today, another tomorrow.
Quick Refactoring Prompts
# Extract function
"Extract the [logic] from this function into a separate, well-named function."
# Rename for clarity
"Suggest better names for variables and functions in this code to improve readability."
# Reduce complexity
"This function has too many branches. Simplify it using early returns or guard clauses."
# Remove duplication
"Find duplicate code in these files and extract to a shared utility."
Related Resources
Guides:
- Testing AI Code — Write tests before refactoring
- Debugging with AI — Fix issues during refactoring
- Shipping Fast — Know when to ship vs. refactor
Cheatsheets:
- AI Debugging Checklist — When refactoring breaks things
- Git for Vibecoders — Commit between changes
Blog:
- 7 Mistakes to Avoid — Including over-refactoring
Next: Shipping Fast — Get your work into users’ hands