- Learn
- Prompt Engineering
- Prompt Engineering for Refactoring
Learn prompting techniques to effectively guide AI in refactoring code—improving structure, readability, and maintainability.
Prompt Engineering for Refactoring
Refactoring transforms working code into better code. AI can be a powerful refactoring partner when given the right prompts—but without clear direction, it may make changes you don't want.
The Refactoring Mindset
Key principles when refactoring with AI:
- Behavior preservation - Code should work exactly the same after refactoring
- Incremental changes - Small, verifiable steps are safer than big rewrites
- Clear goals - Know what "better" means for your specific situation
- Test coverage - Have tests to verify behavior is preserved
Refactoring Prompt Essentials
Always specify:
- What to refactor (specific code)
- Why (the problem with current code)
- Goal (what better looks like)
- Constraints (what must not change)
Basic Refactoring Template
Refactor this code to [specific goal].
Current code:
```[language]
[code to refactor]
Problems with current code:
- [problem 1]
- [problem 2]
Refactoring goals:
- [goal 1]
- [goal 2]
Constraints:
- Must maintain the same public API
- Must remain backward compatible
- Must not change [specific behavior]
Please:
- Show the refactored code
- Explain each change
- Note any potential risks
## Common Refactoring Scenarios
### Extract Function/Component
Extract reusable parts from this component.
Current code:
const OrderPage = ({ orderId }) => {
const [order, setOrder] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchOrder(orderId)
.then(setOrder)
.finally(() => setLoading(false));
}, [orderId]);
if (loading) return <div className="spinner animate-spin" />;
if (!order) return <div className="error">Order not found</div>;
return (
<div>
<h1>{order.id}</h1>
{/* ... more JSX */}
</div>
);
};
Extract:
- Loading spinner component
- Error display component
- Data fetching logic into a custom hook
Keep the main component's structure recognizable.
### Simplify Conditionals
Simplify these nested conditionals while preserving logic.
Current code:
function getShippingCost(order: Order): number {
if (order.isPremiumMember) {
if (order.total > 100) {
return 0;
} else {
if (order.items.length > 5) {
return 5;
} else {
return 10;
}
}
} else {
if (order.total > 200) {
return 0;
} else {
if (order.total > 100) {
return 10;
} else {
return 20;
}
}
}
}
Goals:
- Reduce nesting
- Make conditions clearer
- Consider using early returns
- Preserve exact same behavior
### Improve Naming
Improve variable and function names in this code.
Current code:
function proc(d) {
const r = [];
for (let i = 0; i < d.length; i++) {
const x = d[i];
if (x.t === 'A') {
const n = x.v * 1.1;
r.push({ ...x, v: n });
} else {
r.push(x);
}
}
return r;
}
Context: This processes a list of transactions, applying a 10% fee to type 'A' transactions.
Requirements:
- Names should be descriptive
- Follow JavaScript naming conventions
- Keep function concise
### Remove Code Duplication
Remove duplication from these similar functions.
Current code:
async function fetchUsers() {
try {
const response = await fetch('/api/users');
if (!response.ok) throw new Error('Failed to fetch users');
const data = await response.json();
return { success: true, data };
} catch (error) {
console.error('Error fetching users:', error);
return { success: false, error: error.message };
}
}
async function fetchOrders() {
try {
const response = await fetch('/api/orders');
if (!response.ok) throw new Error('Failed to fetch orders');
const data = await response.json();
return { success: true, data };
} catch (error) {
console.error('Error fetching orders:', error);
return { success: false, error: error.message };
}
}
async function fetchProducts() {
try {
const response = await fetch('/api/products');
if (!response.ok) throw new Error('Failed to fetch products');
const data = await response.json();
return { success: true, data };
} catch (error) {
console.error('Error fetching products:', error);
return { success: false, error: error.message };
}
}
Create a reusable abstraction that:
- Eliminates the duplication
- Maintains type safety
- Is flexible enough for different endpoints
- Handles the same error cases
### Convert to TypeScript
Convert this JavaScript code to TypeScript with proper types.
Current JavaScript:
function processOrder(order, options = {}) {
const { applyDiscount = false, notifyUser = true } = options;
let total = order.items.reduce((sum, item) => sum + item.price * item.qty, 0);
if (applyDiscount && order.coupon) {
total = total * (1 - order.coupon.percent / 100);
}
return {
orderId: order.id,
total,
itemCount: order.items.length,
discounted: applyDiscount && !!order.coupon
};
}
Requirements:
- Create proper interfaces for Order, Item, Options, etc.
- Use strict types (no 'any')
- Add JSDoc comments for complex types
- Handle nullable fields appropriately
### Modernize Legacy Code
Modernize this legacy JavaScript to use modern ES2022+ features.
Legacy code:
var UserService = {
users: [],
getUser: function(id) {
var user = null;
for (var i = 0; i < this.users.length; i++) {
if (this.users[i].id === id) {
user = this.users[i];
break;
}
}
return user;
},
addUser: function(user) {
var self = this;
return new Promise(function(resolve, reject) {
setTimeout(function() {
self.users.push(user);
resolve(user);
}, 100);
});
},
filterActiveUsers: function() {
return this.users.filter(function(user) {
return user.active === true;
});
}
};
Modernize using:
- const/let instead of var
- Arrow functions
- Array methods (find, filter)
- Async/await
- Object shorthand
- Optional chaining where appropriate
## Refactoring with Constraints
### Preserve API Compatibility
Refactor this module's internals WITHOUT changing its public API.
Current code:
export class UserManager {
private users: Map<string, User> = new Map();
addUser(user: User): void {
// Complex internal logic
}
getUser(id: string): User | undefined {
// Complex internal logic
}
removeUser(id: string): boolean {
// Complex internal logic
}
}
Constraints:
- Method signatures must stay identical
- Return types must stay identical
- Existing consumers must not break
- Tests using public API should still pass
Goals:
- Improve internal efficiency
- Add better error handling internally
- Simplify the implementation
### Maintain Backward Compatibility
Refactor this function to a better design while maintaining backward compatibility.
Current signature:
function formatDate(date: Date, format?: string): string
Desired new design:
function formatDate(options: FormatDateOptions): string
Requirements:
- New code should use the new signature
- Old code should still work (deprecated but functional)
- Add deprecation warning for old usage
- Both should use the same core logic
## Step-by-Step Refactoring
For complex refactors, use a staged approach:
Help me refactor this large function in stages.
Current function (150 lines):
function processCheckout(cart, user, paymentInfo) {
// [large complex function]
}
Stage 1: Identify distinct responsibilities
- List each responsibility this function handles
Stage 2: Plan the extraction
- Which parts should become separate functions?
- What should the interfaces look like?
Stage 3: Execute incrementally
- Refactor one piece at a time
- Verify behavior after each step
Let's start with Stage 1. Analyze the responsibilities in this function.
## Performance-Focused Refactoring
Refactor for better performance.
Current code:
function findCommonElements(arr1: number[], arr2: number[]): number[] {
const result: number[] = [];
for (const item1 of arr1) {
for (const item2 of arr2) {
if (item1 === item2 && !result.includes(item1)) {
result.push(item1);
}
}
}
return result;
}
Performance issue: O(n³) complexity due to nested loops and includes check.
Target: Reduce to O(n) or O(n log n).
Constraints:
- Must return same results (same order not required)
- Must handle duplicates the same way
- No external dependencies
## Testing Considerations
Refactor this code, ensuring it remains testable.
Current code:
async function sendNotification(userId: string, message: string) {
const user = await db.users.findById(userId);
const result = await emailService.send(user.email, message);
await db.notifications.create({ userId, message, sentAt: new Date() });
return result;
}
Problems:
- Hard to unit test (depends on db and emailService)
- No dependency injection
- Side effects mixed with logic
Refactor to:
- Allow dependency injection
- Separate pure logic from side effects
- Make unit testing easy
## When NOT to Refactor with AI
Be cautious about AI refactoring when:
- **No test coverage** - Can't verify behavior preservation
- **Critical production code** - High risk of undetected changes
- **Complex domain logic** - AI may misunderstand business rules
- **Performance-critical paths** - Need measured optimization, not guesses
## Practice Exercise
Create refactoring prompts for these scenarios:
1. **God class** - A 500-line class doing too many things
2. **Callback hell** - Deeply nested callbacks to convert to async/await
3. **Magic numbers** - Code full of unexplained numeric values
4. **Long parameter list** - Function with 10+ parameters
For each, specify:
- The specific problem
- Refactoring goal
- Constraints
- How to verify the refactoring worked
## Summary
- Always specify what to refactor, why, and the goal
- Set clear constraints (API compatibility, behavior preservation)
- Use staged refactoring for complex changes
- Verify with tests after refactoring
- Be explicit about what must NOT change
## Next Steps
Now let's explore context management—how to effectively manage what information you include in prompts to get optimal results.