10 Prompts for Refactoring Legacy Code: Strategies and Practical Examples

Introduction

Legacy code is the silent burden of every development team. It’s the code that works—until it doesn’t. It’s the code that no one wants to touch, that lacks tests, and that silently accumulates technical debt. According to a 2023 survey by the Software Sustainability Institute, over 70% of developers report spending at least 30% of their time dealing with legacy code. The challenge isn’t just about understanding what the code does; it’s about safely improving it without breaking the system.

Refactoring legacy code is a high-risk, high-reward activity. Without a clear strategy, you can introduce bugs, lose business logic, or create more technical debt. This is where AI-powered prompts come in. By using Large Language Models (LLMs) with carefully crafted prompts, you can automate parts of the refactoring process—analyzing code, suggesting improvements, writing tests, and even generating migration plans. This article provides 10 ready-to-use prompts for refactoring legacy code, each with a specific use case, an explanation, and a real-world example. These prompts are designed to work with any modern LLM (like GPT-4o, Claude 3.5, or Gemini 2.0) and can be adapted to your programming language and framework.

1. Prompt for Code Understanding and Documentation

Prompt:

I have a legacy codebase written in [language]. Analyze the following function/module and explain what it does in plain English. Identify any side effects, dependencies, and potential risks. Provide a summary that a junior developer could understand. Code: [paste code]

Explanation:
This prompt is ideal for the first step of refactoring: understanding the existing code. Legacy code often lacks comments, has cryptic variable names, and uses outdated patterns. The prompt forces the LLM to produce a human-readable explanation, highlight side effects (like global state changes or database writes), and list dependencies (external APIs, libraries, or other modules). This reduces the time spent on manual code reading.

Example:
A developer working on a 15-year-old PHP e-commerce system uses this prompt for a function called processOrder. The LLM returns: “This function calculates the total price of an order, applies a 10% discount if the customer is a VIP (stored in a session variable), deducts inventory from a global array, and calls sendMail() which is defined in a different file. Side effect: it modifies the global $inventory array. Dependency: requires a $mailer object to be initialized before calling.” This clarity helps the developer decide where to start refactoring.

2. Prompt for Identifying Code Smells

Prompt:

Act as a senior code reviewer. Analyze the following code and list all code smells you find. For each smell, explain why its problematic and suggest a refactoring technique (e.g., Extract Method, Replace Conditional with Polymorphism). Prioritize the smells by risk. Code: [paste code]

Explanation:
This prompt leverages the LLM’s pattern recognition to detect common anti-patterns: long methods, god classes, duplicate code, excessive conditionals, and improper error handling. The output includes a prioritized list, helping the team focus on high-risk issues first.

Example:
A Java developer pastes a 300-line method from a banking application. The LLM identifies: “1. Long method (300 lines) – use Extract Method to break into smaller functions. 2. Duplicate code blocks for validation – use DRY. 3. Nested if-else for account types – use Replace Conditional with Polymorphism. 4. Hardcoded interest rates – move to configuration. 5. No null checks – add guard clauses.” The developer refactors in order, reducing the method to 50 lines.

3. Prompt for Writing Unit Tests for Legacy Code

Prompt:

I need to write unit tests for the following legacy function. The code has no existing tests. Generate test cases using the [testing framework, e.g., pytest, JUnit] covering: normal cases, edge cases (empty input, null values, boundary values), and error conditions. Use mocking for external dependencies. Explain each test case. Code: [paste code]

Explanation:
Legacy code is often untested. This prompt generates a comprehensive test suite, which is essential for safe refactoring. By asking for mocks and edge cases, it ensures the tests are robust enough to catch regressions.

Example:
A Python developer uses this prompt for a function calculate_fee(amount, user_type). The LLM generates 8 test cases: 1. Normal fee for standard user. 2. Discounted fee for premium user. 3. Negative amount (should raise ValueError). 4. Zero amount. 5. Very large amount (overflow test). 6. User type None. 7. Invalid user type string. 8. Mocking the get_discount_db() call to avoid database dependency. The developer runs the tests, finds a bug in negative amount handling, and fixes it before refactoring.

4. Prompt for Extracting Duplicate Code

Prompt:

Analyze the following code snippets and extract the common logic into a reusable function. Suggest a new function name, parameters, and return type. Also suggest where to place it (utility class, helper module, etc.). Snippets: [paste snippets A, B, C]

Explanation:
Duplicate code is a major source of bugs. This prompt automates the detection and extraction of common logic, reducing manual effort and ensuring consistency.

Example:
A C# developer has three methods in different classes that all calculate tax with slightly different rates. The LLM suggests: “Extract a function CalculateTax(decimal amount, decimal taxRate, bool isExempt) and place it in a TaxHelper static class. The snippets have minor differences in rounding—standardize to Math.Round(amount * taxRate, 2).” After extraction, the codebase becomes 30% shorter.

5. Prompt for Migrating from Deprecated Libraries

Prompt:

I am migrating from [old library/API] to [new library/API]. Here is a piece of code using the old library. Rewrite it using the new library, preserving all functionality. Explain the key differences in syntax and behavior. Code: [paste code]

Explanation:
Migration is a common legacy code task. This prompt handles the mechanical translation, but more importantly, it explains behavioral differences (e.g., error handling, performance) that could cause subtle bugs.

Example:
A developer migrates from requests to httpx in Python. The LLM rewrites the synchronous call to an async call, adds timeout parameters, and explains: “The old code ignored SSL errors; the new code uses verify=False but you should add proper certificate handling. Also, httpx raises HTTPStatusError instead of requests.exceptions.HTTPError.” The migration is completed 10x faster.

6. Prompt for Simplifying Conditional Logic

Prompt:

The following code has deeply nested conditionals. Refactor it using early returns, guard clauses, or the Strategy pattern. Provide the refactored code and explain why its safer and more readable. Code: [paste code]

Explanation:
Deeply nested conditionals (arrow code) are a hallmark of legacy systems. This prompt applies structured refactoring techniques, reducing cognitive load and error potential.

Example:
A JavaScript developer pastes a function with 6 levels of if-else for user authorization. The LLM refactors it using guard clauses: “if (!user) return error; if (!user.isActive) return error; if (user.role !== 'admin') return error; return grantAccess();” The result is 40 lines instead of 120, and the logic is linear.

7. Prompt for Adding Error Handling and Logging

Prompt:

The following legacy code lacks error handling and logging. Add try-catch blocks, meaningful error messages, and logging statements using [logging framework]. Use appropriate log levels (INFO, WARN, ERROR). Preserve the original logic. Code: [paste code]

Explanation:
Legacy code often swallows exceptions or crashes without explanation. This prompt systematically adds robust error handling, which is critical for production systems.

Example:
A Ruby on Rails developer uses this prompt for a method that calls an external payment API. The LLM adds: “begin; response = payment_gateway.charge(amount); logger.info('Payment success', {amount: amount, transaction_id: response.id}); rescue Timeout::Error => e; logger.error('Payment timeout', error: e.message); retry up to 3 times; rescue StandardError => e; logger.fatal('Payment failed', error: e.message); raise; end.” The method is now production-ready.

8. Prompt for Generating Migration Plan

Prompt:

I need to refactor the following module into a microservice. Generate a step-by-step migration plan that includes: 1. Identify bounded context. 2. Extract database tables. 3. Create API contracts. 4. Implement service. 5. Set up CI/CD. 6. Run parallel runs. 7. Cut over. For each step, estimate effort (small/medium/large) and risks. Current module: [description or code]

Explanation:
Large-scale refactoring requires a plan. This prompt produces a structured, actionable plan with risk assessments, helping teams avoid scope creep.

Example:
A team wants to extract the user authentication module from a monolith. The LLM produces: “Step 1: Bounded context = authentication (small effort). Step 2: Extract tables users, sessions, roles (medium, risk of data inconsistency). Step 3: Create REST API with OAuth2 (medium). Step 4: Implement in Go (large). Step 5: CI/CD with Docker (small). Step 6: Run both old and new systems for 2 weeks (medium). Step 7: Cut over after monitoring (risky).” The team follows the plan and completes the migration in 6 weeks.

9. Prompt for Converting from Procedural to Object-Oriented Code

Prompt:

The following code is written in a procedural style. Refactor it into object-oriented code using [language] with classes, encapsulation, and inheritance where appropriate. Explain the design decisions. Code: [paste code]

Explanation:
Many legacy systems are written in procedural style (e.g., PHP without classes, C with global functions). This prompt transforms the code into a more maintainable OOP structure.

Example:
A PHP developer has a set of functions for managing inventory. The LLM creates a InventoryManager class with private properties for stock, public methods for addStock, removeStock, getStock, and a protected method for logging. It explains: “Encapsulation prevents direct modification of stock. Inheritance allows creating a PerishableInventory subclass that adds expiry logic.” The code becomes easier to test and extend.

10. Prompt for Performance Optimization

Prompt:

Analyze the following legacy code for performance bottlenecks. Suggest optimizations: caching, database query reduction, algorithm changes, or concurrency improvements. Provide before/after code snippets. Code: [paste code]

Explanation:
Legacy code often performs poorly due to outdated practices. This prompt identifies and fixes bottlenecks, often with minimal changes.

Example:
A Python developer pastes a loop that makes an API call for each item in a list. The LLM suggests: “Use asyncio.gather to make concurrent calls, or batch requests if the API supports it. Also cache responses with functools.lru_cache. Original: 10 seconds for 100 items. Optimized: 1.2 seconds.” The developer implements the changes, and the page load time drops significantly.

Conclusion

Refactoring legacy code is not a one-time task—it’s a continuous discipline. The prompts provided here are not magic bullets; they are tools that augment your expertise. By using them, you can reduce the time spent on manual analysis, increase test coverage, and make safer changes. The key is to always verify the LLM’s output, especially for business-critical logic. Start with the understanding prompt, then move to testing, and finally to structural changes. With these 10 prompts, you can turn a daunting legacy codebase into a manageable, modern system. Remember, the goal is not to rewrite everything, but to improve incrementally—and AI can help you do that faster.

ASI Biont supports connecting to version control systems like GitHub and GitLab via API to automate prompt-driven refactoring workflows—learn more at asibiont.com/courses.

← All posts

Comments