Legacy Code to Clean Architecture: AI Prompt Playbook for Painless Refactoring
You know the feeling: you open a file that's been in production for eight years, and the first method you see is 400 lines long, mixing database queries, UI rendering, and business logic. The comments say // do not touch and // TODO: fix this. Your manager asks you to add a new feature, but you're afraid to change anything because the tests are either missing or broken. This is legacy code, and it's everywhere.
Refactoring it manually is risky and time-consuming. But here's the good news: modern AI assistants, trained on millions of public repositories, can act as your safety net and pair programmer. They can analyze dependencies, suggest incremental changes, generate tests, and even translate old code to modern frameworks. But only if you ask them the right way.
This playbook is a collection of battle-tested prompts for refactoring legacy code with AI. Each prompt is designed to minimize risk, preserve behavior, and help you move from "spaghetti" to clean architecture step by step. I've used these prompts on real projects—from Java monoliths to PHP 5 codebases—and they've saved me weeks of manual work. Let's dive in.
1. The Big Picture: Map the Beast Before You Touch It
Before you change anything, you need a bird's-eye view. The first prompt asks the AI to analyze the codebase structure, identify dependencies, and highlight risk hotspots. This is your reconnaissance mission.
Prompt:
Act as a senior software architect. Analyze the codebase at [path/to/codebase] and produce a structural overview.
Focus on:
- Top-level modules/packages and their responsibilities
- Circular dependencies between modules
- Classes with more than 500 lines (god objects)
- Methods with more than 50 lines and high cyclomatic complexity (use a threshold of 10)
- Database access spread across many layers
- Global state or singletons that make testing hard
Output a Markdown report with: a module dependency diagram (text-based), a list of risk hotspots with file paths, and a suggested order of refactoring steps (from lowest to highest risk).
Example usage: I ran this on a legacy Java Spring project. The AI correctly identified OrderService as a god object (1200 lines) and found a circular dependency between billing and inventory packages. The suggested order started by breaking OrderService into smaller services—exactly what we needed.
2. Characterize the Legacy: What Tech Debt Are We Dealing With?
Not all legacy code is the same. It could be old syntax, missing types, or tangled business logic. This prompt helps you categorize the debt so you can prioritize.
Prompt:
Analyze the file [path/to/file] and classify the type of technical debt present.
Categories to consider:
- **Code smells**: duplication, long parameter lists, feature envy, etc. (list each with line numbers)
- **Architectural debt**: violations of layering, lack of interfaces, tight coupling
- **Technical debt**: outdated dependencies, deprecated APIs, missing tests
- **Documentation debt**: missing comments, misleading names
For each issue, propose a specific refactoring technique (e.g., Extract Method, Introduce Interface, Replace Conditional with Polymorphism) and estimate the risk level (low/medium/high) of applying it.
Example usage: On a legacy PHP script, the AI flagged 15 duplicated SQL queries, a global $db variable, and three deprecated mysql_* functions. It suggested introducing a repository pattern and replacing mysql_* with PDO. The risk levels guided our order: we started with the low-risk PDO migration.
3. The Seam Finder: Isolate Dependencies for Safe Testing
Michael Feathers, in his book Working Effectively with Legacy Code, calls seams "places where you can alter behavior without changing it." This prompt helps you find seams in your code.
Prompt:
Act as a refactoring expert. In the file [path/to/file], identify all seams that would allow us to test a specific method [method_name] in isolation.
Look for:
- Direct calls to static methods, singletons, or global variables
- Database connections (e.g., PDO, JDBC) and network calls (HTTP, sockets)
- File system operations
- Hardcoded dependencies (e.g., `new Database()` inside the method)
For each seam, suggest how to break the dependency using standard techniques like:
- Extract Interface
- Introduce Parameter (pass a mockable object)
- Wrap in a class that can be overridden
Show a code snippet for the most critical seam.
Example usage: I used this on a C# method that called DateTime.Now and File.ReadAllText. The AI suggested wrapping both in an IClock and IFileReader interface. After that, I could unit-test the method without touching the file system.
4. Golden Master: Generate Characterization Tests to Lock Behavior
Before you refactor, you need a safety net. Characterization tests capture the current behavior, so you can detect unintended changes. This prompt asks the AI to generate them.
Prompt:
Analyze the class [class_name] in [path/to/file]. Generate characterization tests (also known as golden master tests) that capture the current behavior without assuming correctness.
For each public method:
- Use realistic input values (include edge cases like null, empty, negative numbers)
- Call the method and assert that the output matches what the code actually produces
- Mock all external dependencies (database, network, file system) to make tests deterministic
Provide the test code in [testing_framework] (e.g., JUnit, pytest, PHPUnit). Also mention any private methods that should be tested indirectly.
Example usage: For a legacy Python function that parsed a CSV, the AI generated 20 test cases, including malformed rows and missing columns. When I later refactored the function, these tests caught a subtle off-by-one error that would have broken production.
5. Extract Method: Break Down God Functions
Long methods are the #1 sign of legacy code. The Extract Method refactoring is simple but effective. This prompt automates the tedious part.
Prompt:
In the file [path/to/file], the method [method_name] is too long (lines [start]-[end]).
Refactor it by extracting logical blocks into separate private methods.
For each extraction:
- Give the new method a descriptive name based on its responsibility
- Copy the relevant code, replacing local variables with parameters
- Ensure the original method calls the new method at the right place
- Preserve all side effects (e.g., updates to class fields, I/O operations)
Show the refactored code before and after, and list the extracted methods with their responsibilities.
Example usage: I applied this to a 200-line validate_order method. The AI extracted validateCustomer, validateItems, and validatePayment—each about 30 lines. The original method became a readable sequence of calls. The behavior stayed identical because the AI carefully preserved the order of side effects.
6. Replace Magic Numbers and Strings with Constants
Legacy code is full of magic numbers like if (status == 3) or if (color == 'blue'). This prompt cleans that up, making the code self-documenting.
Prompt:
In the file [path/to/file], find all magic numbers and strings that are used in comparisons, array indices, or calculations.
Replace them with named constants or enum values.
- Use a clear, context-specific name (e.g., `STATUS_ACTIVE` instead of `3`)
- If the value is part of an external API, keep the literal but add a comment explaining it
- Group related constants into a single class or enum
Show a diff of the changes. Ensure the refactoring does not change any runtime behavior.
Example usage: In a legacy PHP payment script, the AI replaced if ($status == 2) with if ($status === PaymentStatus::COMPLETED). It also created an enum class. The code became much more readable, and a bug where == was used instead of === was spotted.
7. Introduce Design Patterns (e.g., Strategy, Factory) to Untangle Conditionals
Deeply nested conditionals are a hallmark of legacy code. Design patterns like Strategy or Factory can replace them with polymorphic dispatch. This prompt guides the AI to do it.
Prompt:
In the file [path/to/file], the method [method_name] contains a complex if-else or switch statement that handles multiple cases.
Refactor it using the Strategy or Factory pattern, whichever is more appropriate.
- Identify the varying behavior and encapsulate each case in a separate class
- Define an interface or abstract base class for the strategy
- Modify the original method to delegate to a strategy instance (obtained from a simple factory)
- Preserve the exact same logic for unknown cases (e.g., default)
Show the full refactored code for the strategy classes and the client code.
Example usage: I had a calculateShipping function with 12 if statements for different carriers. The AI created a ShippingStrategy interface and classes like DHLStrategy, FedExStrategy, and a factory that maps carrier names to strategies. The main method became a single line: return strategyFactory.get(carrier).calculate(order). Much cleaner.
8. Dependency Direction: Invert Dependencies to Follow DIP
Dependency Inversion Principle (DIP) is key to clean architecture. This prompt helps you invert dependencies so high-level modules don't depend on low-level details.
Prompt:
Analyze the module [module_name] and its dependencies in [path/to/codebase].
Identify places where the high-level module directly depends on a low-level implementation (e.g., `new ConcreteRepository()` or static call to a utility).
Refactor to invert the dependency:
- Introduce an interface for the low-level service
- Change the high-level class to depend on the interface (via constructor injection or a service locator)
- Update the composition root to provide the concrete implementation
Show the before/after code for the high-level class and the new interface. List any other files that need changes.
Example usage: In a Java app, the ReportGenerator directly called new PdfExporter(). The AI introduced a ReportExporter interface and made ReportGenerator accept it in its constructor. Now we can easily mock the exporter in tests and swap to a CSV exporter if needed.
9. Modernize the Stack: Translate Legacy Syntax to Modern Equivalents
Sometimes you need to move from PHP 5 to PHP 8, or Java 8 to Java 17. This prompt focuses on syntax and API modernization without changing behavior.
Prompt:
You are an expert in [old_version] and [new_version]. Refactor the file [path/to/file] to use modern syntax and APIs from [new_version].
Specific tasks:
- Replace deprecated functions/classes with their modern counterparts (e.g., `mysql_*` → PDO, `create_function` → closures)
- Use type hints, return types, and strict typing if available
- Replace anonymous classes with named classes if it improves readability
- Use modern collection methods (e.g., `array_map`, `array_filter`, streams)
- Ensure the code still runs identically; do NOT refactor logic or change behavior
Provide a list of changes with before/after snippets.
Example usage: I migrated a PHP 5.6 codebase to PHP 8.0. The AI automatically replaced mysql_real_escape_string with prepared statements, added declare(strict_types=1), and converted array_* loops to array_filter/array_map. The diff was surprisingly small and safe.
10. Split a Monolith into Modules (by Bounded Context)
If you have a monolith, you might want to split it into modules (or even microservices later). This prompt helps you identify boundaries.
Prompt:
Analyze the monolith codebase at [path/to/codebase] and propose a modular structure based on Domain-Driven Design (DDD) principles.
- Identify bounded contexts (e.g., billing, inventory, user management) by looking at domain language and data ownership
- Group classes and files into modules that have minimal interdependence
- For each module, define its public API (classes/interfaces that are exposed) and its internal implementation
- Highlight any cross-module dependencies that need to be broken (e.g., direct DB table access from another module)
Output a text-based diagram of the module dependency graph and a step-by-step plan to physically split the codebase (e.g., move files to new packages).
Example usage: For a Rails app, the AI identified three bounded contexts: billing, inventory, and users. It suggested extracting them into Rails engines. The plan included moving models, controllers, and views into separate directories. We followed it and reduced the main app's size by 40%.
11. Automate the Grind: Generate Boilerplate for the New Structure
Once you have a plan, you need to create new files, interfaces, and tests. This prompt generates boilerplate that matches your architectural decisions.
Prompt:
Based on the following refactoring plan, generate the boilerplate code for the new structure:
- Plan: [paste plan]
- Language/Framework: [e.g., Java/Spring, TypeScript/Node]
For each new file, provide:
- The full code with proper imports, class/interface declarations, and placeholder methods
- Javadoc/TSDoc comments explaining the responsibility
- Unit test skeletons (using [testing_framework]) with placeholder assertions
Ensure the code is consistent with the plan and follows [style guide, e.g., Google Java Style].
Example usage: After planning a microservice split, I used this prompt to generate the UserService interface, its implementation, and a test class. It saved me at least a day of typing boilerplate.
12. Regression Risk Analysis: What Could Break?
Before merging, you need to assess the risk of your changes. This prompt asks the AI to analyze the impact.
Prompt:
You are about to refactor [file/module] in the codebase. The following changes are planned: [list of changes].
Analyze the potential regression risks:
- List all callers of the modified methods/classes (search the codebase for usages)
- Identify any behavior changes that might not be covered by existing tests
- Suggest additional test cases to cover these risks
- Recommend a rollback strategy (e.g., feature flags)
Output a risk matrix with columns: Risk, Impact, Likelihood, Mitigation.
Example usage: Before refactoring a payment processing module, I pasted the change list. The AI found that a third-party integration test was still using the old API, and it recommended adding a test for a specific edge case (refund of partially paid order). We added that test, and it caught a bug during the refactor.
13. Keep the Momentum: Write a Refactoring Log
Finally, document your journey. This prompt generates a concise log that you can share with your team.
Prompt:
Summarize the refactoring session described below into a concise log for a Changelog or commit message.
- Session details: [paste description]
Include:
- What was refactored (files, methods)
- Why (motivation)
- How (techniques used)
- What tests were added or updated
- Any known issues or follow-up tasks
Keep it under 200 words and use bullet points.
Example usage: After a day of refactoring, I generated a log that my team could read in two minutes. It made the PR review much smoother.
Wrapping Up
Legacy code refactoring is a marathon, not a sprint. AI won't do the work for you, but with these prompts, you can turn it into a powerful ally that accelerates analysis, automates repetitive tasks, and reduces risk. The key is to always validate the AI's output with tests and code review. Start with a small, non-critical module, and gradually build confidence.
Remember: the goal is not to rewrite everything from scratch, but to make the codebase a little better every day. Use these prompts as your toolbox, and you'll be surprised how quickly the "swamp" becomes a structured garden.
Have you tried refactoring with AI? Share your own prompts in the comments below!
Comments