14 Prompts for Refactoring Legacy Code: Proven Strategies and Examples
Legacy code is a fact of life for most developers. It's the tangled web of outdated functions, undocumented decisions, and quick fixes that somehow still powers business-critical systems. As Michael Feathers wrote in Working Effectively with Legacy Code, “legacy code is simply code without tests.” Without a safety net, even the smallest change can bring down the whole system. That’s why refactoring demands caution, strategy, and deep understanding.
AI assistants have become powerful allies in this process. A well-crafted prompt can turn a language model into a knowledgeable pair programmer: one that analyzes dependencies, suggests elegant abstractions, and writes the tests you’re missing. But vague prompts yield vague results. The key is to provide context, constraints, and a clear goal. Below are 14 battle-tested prompts that I use to modernize legacy systems. Each one includes a ready-to-copy prompt, an explanation of what it does, and a practical example.
Why Prompts Matter for Refactoring
Refactoring is about improving the internal structure of code without changing its external behavior. In legacy systems, you often lack documentation, tests, and time. AI tools can help you:
- Understand unknown code fast.
- Generate test scaffolding.
- Propose incremental refactorings.
- Document decisions.
The prompts below are organized by the most common refactoring scenarios. Copy, adapt, and combine them to suit your codebase.
1. Explain Legacy Code in Simple Terms
What it does: Forces the AI to parse a piece of code and explain its intent, side effects, and assumptions. Great for onboarding or when you inherit an undocumented module.
The prompt:
You are a senior software engineer. Analyze the following legacy code and explain in simple terms:
1. What is the main purpose of this code?
2. What inputs does it expect and what outputs does it produce?
3. What side effects does it have (global variables, file I/O, network calls)?
4. What assumptions does it make about the environment or data?
Keep the explanation concise and use analogies where helpful.
[Paste your code here]
Example: Suppose you have a PHP function that processes orders. The AI will output a clear summary, uncovering that the function also sends emails and updates inventory—something you didn’t know.
2. Identify Dependencies and Side Effects
What it does: Finds hidden coupling points before you refactor. This is the first step in Michael Feathers’ characterization test workflow.
The prompt:
List every external dependency in this code (global variables, static calls, database access, environment variables) and every side effect it performs. For each dependency, classify it as a direct or indirect dependency. Then suggest the minimal set of seams (interfaces) that would allow us to isolate this code for testing.
[Paste your code here]
Example: For a Python module that directly calls requests.get inside a function, the prompt suggests passing a session object as a parameter, making the function testable with mocks.
3. Break a Monolith Function into Small, Single-Responsibility Functions
What it does: Decomposes a long function into logical units, preserving behavior.
The prompt:
Refactor this function to follow the Single Responsibility Principle. Break it into smaller, well-named functions. Preserve the exact behavior, including exception types and return values. Use private/internal functions for helper logic. Add a comment at the top explaining the new structure.
[Paste your code here]
Example: A 200-line C# method called ProcessCustomerData is split into ParseInput, ValidateCustomer, ApplyDiscount, and SaveToDatabase. The AI provides the code for each part.
4. Convert Nested Conditionals into a State Machine or Strategy Pattern
What it does: Reduces cyclomatic complexity and makes state transitions visible.
The prompt:
The following code uses nested if-else statements to control program flow. Refactor it to use a state machine pattern (or strategy pattern, if that fits better). Define the states, transitions, and events explicitly. Include comments to explain each state.
[Paste your code here]
Example: A Go program with if, else if chains to handle request statuses is transformed into a StatusHandler interface and a map of status to handlers, making adding new states trivial.
5. Generate Unit Tests for a Legacy Function
What it does: Creates a test suite that documents current behavior, enabling safe refactoring. This is the foundation of characterization testing.
The prompt:
Write unit tests for the following function using [XUnit/JUnit/PyTest]. Cover the key execution paths, edge cases, and error handling. The tests should assert the current behavior exactly—do not change the function. Include test names that describe the scenario and expected behavior.
[Paste your code here]
Example: For a Java method calculateTotal(items), the AI generates tests for empty list, null argument, negative item price, and rounding edge cases.
6. Extract an Interface to Enable Dependency Injection
What it does: Decouples high-level modules from low-level implementations.
The prompt:
Examine this class and extract an interface for its public methods that would allow callers to depend on abstractions. Identify the methods that are part of the core contract and move them to an interface. Then refactor the class to implement that interface, without changing behavior. Provide the interface code and the updated class.
[Paste your code here]
Example: A TypeScript EmailService class is given an IMessageSender interface, allowing a Notifier class to accept any sender (email, SMS, Slack).
7. Replace Magic Numbers and Strings with Named Constants
What it does: Improves readability and maintainability.
The prompt:
Find all magic numbers, magic strings, and hard-coded thresholds in this code. Replace them with named constants or an enum. Use idiomatic naming (e.g., MAX_RETRY_COUNT, DEFAULT_PAGE_SIZE). Keep the values exactly the same. Provide a before/after diff.
[Paste your code here]
Example: In a JavaScript function, if (errorCode === 503 || errorCode === 504) becomes if ([SERVICE_UNAVAILABLE, GATEWAY_TIMEOUT].includes(errorCode)) with constants defined.
8. Identify Performance Bottlenecks and Suggest Optimizations
What it does: Analyzes complexity and highlights expensive operations.
The prompt:
Analyze this code for performance bottlenecks. Identify time complexity (Big O) of each major operation, suggest more efficient algorithms or data structures, and point out any redundant computations or needless database queries. For each suggestion, explain the expected impact and trade-offs. Do not change the code yet—just provide a report.
[Paste your code here]
Example: A PHP function that loops over a query inside another loop (N+1 problem) is flagged. The prompt suggests using a join or eager loading and shows the complexity form O(N×M) to O(N+M).
9. Migrate Deprecated Syntax to Modern Language Features
What it does: Brings code up to date without breaking functionality.
The prompt:
Rewrite the following legacy code to use modern [PHP 8 / Python 3.12 / ECMAScript 2024] syntax. Replace deprecated functions, unsafe constructs, and verbose patterns with modern equivalents (e.g., arrow functions, null-safe operator, match expressions, type hints). Preserve the exact behavior and add compatibility notes for anything that changed.
[Paste your code here]
Example: A PHP 5 mysql_* code snippet is updated to use PDO with prepared statements and the null-coalescing operator.
10. Generate Documentation Comments (Docblocks)
What it does: Creates accurate API documentation for future maintainers.
The prompt:
Add comprehensive docblocks (JSDoc/PHPDoc) to this code. For each public function, document: a description, parameter types, return type, thrown exceptions, and a usage example. Keep comments concise and meaningful. Do not document the obvious implementation details.
[Paste your code here]
Example: For a messy Python function, the AI generates a Google-style docstring with Args, Returns, and Raises sections.
11. Apply a Design Pattern to Improve Flexibility
What it does: Introduces proven solutions for common design problems.
The prompt:
Suggest and implement the most appropriate design pattern to refactor this code. Consider the Open/Closed Principle: we want to add new features without modifying existing code. Apply the pattern, explain why you chose it, and show the refactored code.
[Paste your code here]
Example: A system that creates different report objects using a switch-case is refactored with the Factory Method pattern, making it easy to add new report types.
12. Improve Error Handling and Add Meaningful Exceptions
What it does: Replaces generic return null or bare catch blocks with structured, actionable errors.
The prompt:
Refactor the error handling in this code. Identify all places where exceptions are swallowed, error codes are returned without context, or input validation is incomplete. Replace them with custom exception types (or Result objects) that include meaningful messages. Preserve the public API if possible; if not, show migration steps.
[Paste your code here]
Example: A Ruby method that returns false on every failure is refactored to raise InvalidOrderError with a reason, while the caller rescues it appropriately.
13. Remove Dead Code and Redundant Logic
What it does: Simplifies the codebase, making it less confusing.
The prompt:
Identify and remove dead code in this file: unused variables, unreachable branches, redundant checks, and commented-out blocks. Explain each removal with a brief justification. Keep the behavior unchanged and note any branch that was previously unreachable.
[Paste your code here]
Example: A JavaScript module has an if (isLoaded === true) { ... } else { return; } where the else branch returns the same result. The AI simplifies it and removes an unused variable.
14. Optimize Legacy SQL Queries and Database Access
What it does: Tackles slow queries and inefficient data access patterns.
The prompt:
Analyze the following SQL queries and database access code. Rewrite them to reduce the number of round trips, use indexes effectively, and avoid expensive operations like SELECT *. Use appropriate JOINs instead of subqueries where possible. Explain how the rewritten query reduces execution time and preserves the result set.
[Paste your code here]
Example: A query that uses WHERE IN (SELECT ...) is rewritten as an inner JOIN, reducing execution time from 2 seconds to 20 ms on a test dataset.
Putting It All Together: A Safe Refactoring Workflow
Combine these prompts in a structured process:
- Understand — Use prompts 1 and 2 to map the code.
- Test — Build a safety net with prompt 5.
- Improve — Apply prompts 3, 4, 7, 9, 11, 12, 13 to restructure.
- Optimize — Profile and optimize (8, 14).
- Document — Add comments and documentation (10).
Real success stories: teams have reduced bug rates significantly after paying down technical debt with AI-assisted refactoring. The key is to commit to small, reversible steps and always rely on tests.
Caution: AI-generated code is not guaranteed to be correct or secure. Always review the output, run your tests, and use AI as a copilot—not a replacement for your judgment.
Final Thoughts
Legacy code can feel like a swamp, but with the right prompts, AI becomes your machete and map. Start by using one prompt on your scariest function, measure the result, and build confidence. The 14 prompts above are a toolbox—adapt them, combine them, and make them your own. Happy refactoring!
Comments