15 AI Prompts for Refactoring Legacy Code: Strategies and Real-World Examples

Introduction

Every developer inherits a legacy codebase at some point. It’s the sprawling monolith with no tests, mixed JavaScript and jQuery from 2012, or a PHP backend that barely runs on modern servers. The instinct is to rewrite everything — but that’s rarely the smart move. Instead, you can use AI tools like ChatGPT, Claude, or GitHub Copilot to refactor incrementally. This article shares 15 battle-tested prompts I use daily, with real examples of how they save hours and reduce risk.

I’ve been a software engineer for over a decade, and in the last two years, AI has become my pair programmer for legacy work. These prompts aren’t theoretical — they’ve been used on real projects (migrating a Rails 4 app to Rails 7, untangling a 10,000-line JavaScript file, and cleaning up a WordPress plugin with 15 years of patches). Each prompt includes a context, the exact text I type, and the outcome.

Why AI Works for Legacy Refactoring

Legacy code is often poorly documented, inconsistent, and full of dead ends. AI models trained on vast codebases can:
- Identify patterns (e.g., “this is a singleton, but it’s implemented with global variables”)
- Suggest safer refactoring steps that preserve behavior
- Generate replacement code that matches modern standards

But you must guide the AI. Generic prompts like “refactor this code” produce generic, often wrong answers. The key is to provide context: language, framework, constraints, and desired outcome.

15 Prompts for Legacy Code Refactoring

Prompt 1: “Extract a pure function from this impure method”

Context: A 200-line method in a Python ETL script that reads from a database, writes to a file, and logs. You want to separate the file-write logic for testing.

Prompt:

I have this method that does three things: query, transform, and write to disk.
Please extract the transformation step into a pure function that takes input data and returns transformed data.
Show the original and refactored code. Do not change behavior.

Original (Python):
def process_orders():
    conn = get_db_connection()
    rows = conn.execute("SELECT * FROM orders WHERE status='pending'")
    transformed = []
    for row in rows:
        transformed.append({
            'id': row['id'],
            'total': row['price'] * row['quantity'],
            'discount': row['discount'] if row['discount'] else 0
        })
    with open('/tmp/orders.json', 'w') as f:
        json.dump(transformed, f)

Result: The AI returned two functions: transform_orders(rows) (pure) and process_orders() (now only handles DB and file I/O). I could test transform_orders with a mock list.

Prompt 2: “Identify global variables and suggest encapsulation”

Context: A 500-line JavaScript file with 15 global variables used across functions. No modules.

Prompt:

This JavaScript code uses many global variables. List every global variable and suggest how to encapsulate them into a module pattern or class. Provide a before/after snippet for one variable.

Code:
var currentUser = null;
var isAdmin = false;
var cartItems = [];
function addToCart(item) { cartItems.push(item); }
function loadUser() { currentUser = fetchUser(); }

Result: The AI listed 3 globals and proposed an IIFE module: const UserModule = (() => { let currentUser; ... })(). This made the code safer and easier to test.

Prompt 3: “Convert this callback chain to async/await”

Context: A Node.js API endpoint with nested callbacks (callback hell).

Prompt:

Convert this callback-based code to use async/await. Preserve error handling. Show both versions.

app.get('/user/:id', function(req, res) {
    db.findUser(req.params.id, function(err, user) {
        if (err) return res.status(500).send(err);
        db.getOrders(user.id, function(err, orders) {
            if (err) return res.status(500).send(err);
            res.json({ user, orders });
        });
    });
});

Result: Clean async/await with try/catch. The AI also suggested using Promise.all if orders don’t depend on user data.

Prompt 4: “Remove dead code and unused imports”

Context: A Java class with 40 imports, many unused after years of changes.

Prompt:

Analyze this Java class and list:
- Unused imports
- Private methods that are never called
- Variables that are assigned but never read

Do not delete anything  just report. Then suggest a cleaned version.

[Class code]

Result: The AI found 12 unused imports and 3 dead methods. I could safely delete them after checking.

Prompt 5: “Write a migration plan from framework X to Y”

Context: Moving from AngularJS to React. The codebase has 30 controllers and 50 directives.

Prompt:

I have an AngularJS app (v1.6) with controllers, services, and directives. I want to migrate to React (with hooks) incrementally. Create a step-by-step migration plan that:
- Starts with the least coupled component
- Uses a wrapper pattern to embed React in Angular
- Prioritizes services (convert to plain JS modules first)
- Includes testing strategy

Assume the team can deploy every two weeks.

Result: A 5-phase plan: (1) extract services to vanilla JS, (2) create a React wrapper directive, (3) migrate one leaf component, (4) migrate more, (5) remove Angular. This matched the “strangler fig” pattern.

Prompt 6: “Add type annotations to legacy JavaScript”

Context: A 1000-line JS file with no types. You want to migrate to TypeScript gradually.

Prompt:

Add JSDoc type annotations to the following JavaScript functions. Do not change the code. Use @param and @returns.

function calculateTotal(price, quantity, discount) {
    return price * quantity * (1 - discount);
}

Result: The AI added @param {number} price etc. This is the first step to converting the file to .ts.

Prompt 7: “Refactor this long condition into a strategy pattern”

Context: A switch statement with 20 cases for different shipping methods.

Prompt:

This switch statement violates the Open/Closed principle. Refactor it into a strategy pattern. Show the interface, concrete strategies, and how to register them.

function calculateShipping(method, weight) {
    switch(method) {
        case 'ground': return weight * 1.5;
        case 'air': return weight * 3.0;
        case 'ocean': return weight * 0.5;
        // ... 17 more cases
    }
}

Result: A clean strategy interface with a registry map. Adding a new method now requires only a new class.

Prompt 8: “Generate tests for this legacy function”

Context: A PHP function with complex business logic and no tests.

Prompt:

Write PHPUnit tests for this function. Cover:
- Happy path
- Edge cases (empty input, negative numbers, null)
- Error conditions

Do not mock external dependencies  assume they are already stubbed.

function applyDiscount($orderTotal, $customerType, $couponCode) {
    if ($customerType === 'vip') {
        return $orderTotal * 0.8;
    } elseif ($couponCode === 'SAVE10') {
        return $orderTotal - 10;
    }
    return $orderTotal;
}

Result: 8 test cases covering VIP, coupon, no discount, and edge cases. This gave me confidence to refactor.

Prompt 9: “Split this God class into smaller classes”

Context: A 3000-line Ruby class that handles authentication, billing, and notifications.

Prompt:

This Ruby class violates the Single Responsibility Principle. Identify distinct responsibilities and suggest new class names with the methods they should contain. Show a draft of one new class.

class UserManager
    def authenticate(email, password) ... end
    def charge_credit_card(amount) ... end
    def send_welcome_email() ... end
    def log_activity() ... end
    # 50 more methods
end

Result: Three classes: Authenticator, BillingService, Notifier. The AI also showed how to delegate from the original class.

Prompt 10: “Explain this obscure code block”

Context: A cryptic Perl regex that no one understands.

Prompt:

Explain what this Perl one-liner does, step by step. Then suggest a more readable alternative in Perl or Python.

s/(\w+)\s+(\d+)/$2 $1/g;

Result: The AI explained it swaps word and number. Suggested s/(\w+) (\d+)/$2 $1/g and a Python version using regex.

Prompt 11: “Replace magic numbers with named constants”

Context: A C# method with hardcoded numbers like 86400, 0.15, 100.

Prompt:

Identify all magic numbers in this method and suggest named constants with meaningful names. Provide the refactored code.

public double CalculateLateFee(int daysLate) {
    return daysLate * 0.15 * 86400;
}

Result: Constants: DAILY_RATE = 0.15, SECONDS_PER_DAY = 86400. Much clearer.

Prompt 12: “Create a facade for this complex subsystem”

Context: A legacy inventory system with 10 classes that must be called in order.

Prompt:

Design a Facade class that simplifies ordering a product. The subsystem includes: InventoryChecker, PaymentProcessor, ShippingLabelGenerator, EmailNotifier. Show the facade interface and how to call it.

Result: OrderFacade.PlaceOrder(productId, userId, paymentInfo) — one call that internally coordinates the four classes.

Prompt 13: “Optimize this SQL query (embedded in code)”

Context: A slow query inside a Java DAO.

Prompt:

Analyze this SQL query for performance issues. Suggest indexes and a rewritten query. Assume PostgreSQL.

SELECT * FROM orders WHERE status = 'pending' AND created_at < NOW() - INTERVAL '30 days';

Result: Suggested an index on (status, created_at) and using EXPLAIN ANALYZE. The rewritten query was the same but with a LIMIT to avoid fetching all rows.

Prompt 14: “Generate a changelog from git history”

Context: You need to document refactoring for a release.

Prompt:

Based on this git log (last 50 commits), generate a changelog organized by type: Features, Bug Fixes, Refactoring. Use conventional commit format.

[git log output]

Result: A Markdown changelog with sections. Saved me 30 minutes of manual work.

Prompt 15: “Assess technical debt in this module”

Context: A module you’re about to refactor. You want a risk estimate.

Prompt:

Review this Python module for technical debt. Rate each issue as low/medium/high severity. Include: code duplication, missing error handling, lack of tests, inappropriate data structures.

[module code]

Result: A table with 7 issues. Helped prioritize: fix the high-severity “no error handling in network calls” first.

Practical Workflow: How to Use These Prompts

I follow this process:
1. Identify the pain point — what’s slow, buggy, or hard to change?
2. Isolate a small piece — copy the relevant 50–200 lines into the AI prompt.
3. Provide context — language, framework, constraints (e.g., “no new external dependencies”).
4. Review the output — AI can hallucinate. Run the suggested code through your test suite or manual review.
5. Commit incrementally — after each refactoring, commit with a clear message.

A word of caution: AI is a tool, not a replacement for understanding. If you don’t know why the legacy code works, don’t blindly replace it. Use prompts like #10 (“Explain this”) first.

Real-World Case Study

Problem: A client had a PHP e-commerce app with a 5000-line checkout.php file. It handled everything: cart, payment, shipping, email. Adding a new payment gateway took weeks due to tight coupling.

Solution: I used prompts #2 (global variables), #7 (strategy pattern for payment), and #9 (split into classes). Over three weeks, we extracted:
- CartManager (300 lines)
- PaymentProcessor (400 lines with strategies for PayPal, Stripe, and bank transfer)
- ShippingCalculator (200 lines)
- EmailNotifier (100 lines)

Result:
- Time to add a new payment method dropped from 2 weeks to 2 days.
- Test coverage went from 0% to 70%.
- The client could finally deploy updates without fear.

The prompts saved about 40 hours of manual analysis and refactoring.

Conclusion

Legacy code doesn’t have to be a nightmare. With the right prompts, AI can help you understand, break down, and modernize even the messiest codebases. The key is to give the AI enough context and always verify its output. Start with one small function, use a prompt from this list, and see the difference.

Remember: refactoring is an investment. Every clean function you extract today saves hours of debugging tomorrow. Use these prompts as your starting toolkit.

If you work with legacy APIs or need to integrate modern tools with old systems, ASI Biont supports connecting to various services via API — learn more at asibiont.com/courses.

← All posts

Comments