Introduction
Legacy code isn't just old code — it's code that works but resists change. Michael Feathers defines it as "code without tests" in Working Effectively with Legacy Code, and that lack of safety makes every refactor feel like walking a tightrope. Fortunately, large language models (LLMs) can now act as a thinking partner that helps you map, untangle, and transform legacy systems step by step.
This article collects 9 prompts organized into three skill levels: basic documentation and cleanup, advanced method-level refactorings, and expert architectural migrations. Each prompt includes the exact task, the prompt text you can copy, and a realistic result example. Use them as templates, adapt them to your codebase, and always validate the AI's suggestions with tests and code review.
Why Use Prompts for Refactoring?
Refactoring is a discipline of small, behavior-preserving transformations (Fowler, Refactoring). LLMs accelerate this by summarizing hidden logic, suggesting decomposition plans, and even generating skeleton tests. They don't replace your judgment — they give you faster access to patterns proven in practice.
Basic Prompts: Understanding and Documenting Legacy Code
1. Code Intent Discovery
Task: Get a clear explanation of a cryptic function before touching it.
Prompt:
Analyze the following [language] function. Explain its purpose, inputs, outputs, and each side effect. Identify any unused variables or unreachable branches. Suggest a more descriptive name based on its actual behavior.
[Paste the function]
Example result: For a function named proc1 that updates invoice.total and recalculates tax, the AI returns:
This function validates order items, calculates the subtotal, applies a 5% tax, and updates the global
invoiceobject. It also increments an internal countercnt. The variabletempis written but never read. Recommended name:calculateInvoiceTotal.
2. Dead Code Hunter
Task: Locate unused elements in a large source file without manual grep-in-every-file.
Prompt:
Act as a static analysis tool. In the following source file, list every unused variable, unreachable branch, and function with no callers. For each, give the line number and a one-line reason. Suggest a safe removal order.
[Paste file]
Example result: The AI produces a table:
| Line | Element | Reason |
|---|---|---|
| 14 | $oldPrice |
Assigned but never read |
| 45 | if (false) |
Constant condition, dead block |
| 210 | updateCache() |
No references outside this file |
It recommends removing $oldPrice first because it's local, then the branch, then verifying external references for the function.
3. Comment-to-Documentation Converter
Task: Turn scattered comments and tribal knowledge into maintainable docs.
Prompt:
Convert these code comments and README notes into a concise module documentation page. Include a short description, parameters, return values, exceptions, and an example call. Do not modify or suggest changes to the code itself.
[Paste comments + code]
Example result: The output is a Markdown doc:
## `billing.ApiClient.createInvoice`
- **Parameters**: `customerId:string`, `items:Array<Item>`
- **Returns**: `Promise<Invoice>`
- **Throws**: `ConnectionError` when network fails
- **Example**: `createInvoice('c123', [item])`
The description highlights that items must be non-empty, which the original comment mentioned only inline.
Advanced Prompts: Method-Level Refactoring
4. Extract Method Sequence
Task: Break a 200-line function into focused, testable methods.
Prompt:
This function is too long. Propose a sequence of Extract Method refactorings. For each step, show:
1. The code before (with the lines to extract)
2. The code after (with the new method)
3. The chosen method name and why it matches the extracted logic
Keep behavior identical. Avoid using class fields if locals suffice.
[Paste the long function]
Example result: The AI suggests three extractions:
1. validateInput(input) — extracts lines 5–18.
2. applyDiscounts(order) — extracts lines 22–35.
3. persistResult(order) — extracts lines 40–55.
It shows a diff for each and notes that the extracted methods can be tested independently.
5. Global State to Dependency Injection
Task: Eliminate global variables and make the code testable.
Prompt:
The class below depends on several global variables (e.g., `$db`, `$config`). Plan a refactor to use dependency injection via constructor or method parameters. Show before/after signatures and the exact call-site changes in the rest of the module.
[Paste class and surrounding code]
Example result: Before:
function saveOrder($order) {
$conn = $GLOBALS['db']; // global
$conn->query(...);
}
After:
function saveOrder(Order $order, PDO $conn) { ... }
The AI lists all callers inside orders.php, marking which files need an updated invocation.
6. Adding Seams for Unit Tests
Task: Make a class testable without changing its public behavior.
Prompt:
The following class calls a concrete external service inside a method, making it hard to test. Identify the dependency, introduce an interface (or wrapper), and show a test that mocks that interface. Keep the original method's external behavior intact.
[Paste class]
Example result: The AI introduces:
public interface PaymentGateway {
boolean charge(Customer c, Money amount);
}
then modifies OrderService to accept a PaymentGateway in its constructor. A JUnit 5 test uses Mockito:
PaymentGateway mock = mock(PaymentGateway.class);
OrderService svc = new OrderService(mock);
assertTrue(svc.checkout(order));
Expert Prompts: Architectural Transformation
7. Strangler Fig Migration Roadmap
Task: Plan an incremental migration from monolith to microservices.
Prompt:
We want to extract the payment processing capability out of this monolithic module into a new service using the strangler fig pattern. Create a step-by-step roadmap:
- Identify the exact interfaces that need to be exposed.
- Define a temporary facade that routes calls to the new service while retaining the old implementation as fallback.
- List the feature flags needed.
- Specify how to monitor and migrate consumers.
[Paste architecture overview or code]
Example result: The AI outputs a table with phases:
| Phase | Action | Risks |
|---|---|---|
| 1 | Add /v2/payments endpoint in the new service |
None |
| 2 | Introduce PaymentFacade switching on a flag |
Could double-run if config wrong |
| 3 | Migrate consumers to HTTP client | Timeouts |
| 4 | Remove old code | Data loss if fallback still needed |
8. Toward Hexagonal Architecture
Task: Refactor a procedural class into a domain-centric design.
Prompt:
Refactor this legacy class to separate domain logic, application ports, and adapters. Show:
- The main domain entity and service
- The port interfaces (e.g., repositories, gateways)
- One example adapter for an existing framework (e.g., JPA, HTTP client)
Preserve all current business rules.
[Paste class]
Example result: The AI creates a structure like:
domain/
Order.java
OrderRepository.java (port)
application/
OrderService.java
infrastructure/
JpaOrderRepository.java (adapter)
It shows Order with business methods and OrderService orchestrating via the port.
9. Characterization Tests for Untested Code
Task: Build a safety net before refactoring legacy behavior.
Prompt:
Write characterization tests for this legacy function using [pytest/JUnit]. For a representative set of inputs (including edge cases), record the exact current output and any state changes. Do NOT try to fix the behavior; only capture it.
[Paste function]
Example result: For a messy calculateShipping function, the AI generates:
def test_calculate_shipping():
assert calculate(1, 'US') == 5.0
assert calculate(0, 'US') == 0.0 # bug: should be error
assert calculate(1, 'CA') == 7.5
It notes the zero-input anomaly as "current behavior to preserve" so the refactor doesn't silently change it.
Conclusion
AI prompts are not a replacement for refactoring discipline — they are accelerators. Start with basic prompts to understand what you're dealing with, progress to advanced prompts for safe method-level changes, and finally use expert prompts to reshape entire modules. Combine every suggestion with a characterization test suite and pair programming review.
Pick one legacy function, copy the appropriate prompt from this list, and run your first refactoring session today. The result will be code that your future self — and your team — will thank you for.
Comments