Refactoring legacy Python code can feel like exploring an ancient, tangled forest. You know there's a beautiful landscape hidden beneath the overgrowth, but every step risks breaking something. According to a 2018 survey by Stripe, developers spend about 17 hours per week on maintenance tasks, not just adding new features. That's time that could be spent innovating. What if you had a smart assistant to help you navigate this forest? Large language models (LLMs) can act as that guide, offering suggestions and even generating code. But as with any tool, the quality of the output depends on the quality of the input. The secret lies in crafting precise, context-rich prompts.
This isn't just about asking AI to 'clean up my code.' It's about giving it specific, actionable instructions that target common legacy issues. Each prompt below is designed to address a particular refactoring challenge, from identifying code smells to breaking down monolithic functions and improving performance. I've tested these with real-world code samples, and the results are impressive. By the end of this guide, you'll have a toolkit of prompts to make your refactoring process faster, more efficient, and less error-prone.
1. Identify Code Duplication
Task: Before you can fix duplication, you need to find it. This prompt helps you locate copy-pasted code blocks across your project.
Prompt:
You are a senior Python developer. Analyze the code in the file `{file_path}` and identify any duplicated code blocks (exact or near-exact matches). For each duplication, provide:
- The line numbers of the duplicated blocks.
- A similarity percentage.
- A suggestion on how to refactor it (e.g., extract a function, use a loop, create a base class).
Focus on blocks longer than 5 lines. Ignore trivial duplicates like import statements or simple assignments.
Example Result:
Found 3 duplicated blocks:
1. Lines 45-58 and 112-125 (similarity: 92%)
- Both blocks validate user input: check for empty fields, validate email format, and return error messages.
- Refactoring suggestion: Extract a function `validate_user_data(data)` and call it from both places.
2. Lines 200-210 and 350-360 (similarity: 100%)
- Both blocks calculate the total price from a list of items, applying a discount if the customer is a premium member.
- Refactoring suggestion: Create a helper function `calculate_total(items, is_premium)`.
3. ...
2. Break Down a Monolithic Function
Task: Long functions are a hallmark of legacy code. This prompt helps you decompose a complex function into smaller, more manageable pieces.
Prompt:
You are an expert in Python refactoring. The function `{function_name}` in `{file_path}` is over 100 lines and performs multiple responsibilities. Break it down into smaller, focused functions. For each new function, provide:
- A descriptive name that follows PEP 8 conventions.
- The code for the function.
- A brief comment explaining its purpose.
Ensure that the refactored code preserves the original behavior exactly. Use type hints where appropriate.
Example Result:
Given a function process_order(order) that does validation, inventory check, payment, and email notification, the prompt might produce:
def process_order(order):
if not validate_order(order):
raise ValueError("Invalid order")
if not check_inventory(order):
raise InventoryShortage()
charge_payment(order)
send_confirmation_email(order)
def validate_order(order):
# ... checks required fields
return True
def check_inventory(order):
# ... checks stock levels
return True
def charge_payment(order):
# ... processes payment
pass
def send_confirmation_email(order):
# ... sends email
pass
3. Improve Readability with Descriptive Variable Names
Task: Vague variable names like x, temp, or data make code hard to understand. This prompt helps you rename variables for clarity.
Prompt:
You are a Python code quality expert. In the file `{file_path}`, find variables with unclear or non-descriptive names (e.g., single letters, abbreviations, or generic terms like 'data', 'temp', 'item'). Suggest better names based on their usage context. Provide:
- The original variable name and line number.
- The suggested new name.
- A reason for the change.
Do not rename variables in loops if the loop variable is conventionally short (e.g., i, j, k) unless it improves readability.
Example Result:
Line 12: 'x' → 'order_total' (the variable stores the total cost of an order)
Line 34: 'data' → 'user_profile' (the variable contains user profile information)
Line 56: 'lst' → 'active_items' (the list contains items that are currently in stock)
4. Detect and Fix Anti-Patterns
Task: Legacy code often contains known anti-patterns like using except: pass, global variables, or mutable default arguments. This prompt helps you identify them and suggests fixes.
Prompt:
You are a seasoned Python reviewer. Analyze `{file_path}` for common anti-patterns and code smells. For each issue found, provide:
- The line number and a snippet of the problematic code.
- The type of anti-pattern (e.g., 'mutable default argument', 'broad except', 'global statement').
- A recommended fix with a code example.
Focus on issues that can lead to bugs or maintenance problems.
Example Result:
Line 78: def process(data=[]): # mutable default argument
-> Fix: def process(data=None):
if data is None:
data = []
Line 102: except: pass # broad except
-> Fix: except Exception as e:
log_error(e)
# re-raise or handle appropriately
5. Optimize Performance with Profiling Suggestions
Task: Performance issues often lurk in loops, database queries, or I/O operations. This prompt asks the AI to suggest optimizations based on code inspection.
Prompt:
You are a Python performance engineer. The file `{file_path}` contains code that seems slow. Identify potential performance bottlenecks, such as:
- Unnecessary loops or nested loops that can be optimized.
- Redundant function calls or repeated calculations.
- Inefficient data structure usage (e.g., using a list for membership tests).
For each bottleneck, provide:
- The line number and code snippet.
- Explanation of why it's slow.
- A concrete optimization suggestion with code.
Assume the code is run with a typical dataset size (e.g., 10,000 elements).
Example Result:
Line 45-50:
for item in items:
if item.id in user_ids: # user_ids is a list
process(item)
This is O(n*m) because membership test in a list is O(n).
Optimization: Convert user_ids to a set once: user_ids_set = set(user_ids).
Then the check becomes O(1), making the whole loop O(n).
Optimized code:
user_ids_set = set(user_ids)
for item in items:
if item.id in user_ids_set:
process(item)
6. Add Type Hints and Docstrings
Task: Legacy code often lacks type hints and documentation. This prompt helps you add them systematically.
Prompt:
You are a Python documentation specialist. For the file `{file_path}`, add type hints to all function signatures and docstrings to all public functions and classes. Follow the Google docstring style. For each function, include:
- A one-line summary.
- Args section with types and descriptions.
- Returns section with type and description.
- Raises section if applicable.
Ensure that the code remains functionally unchanged.
Example Result:
def calculate_discount(price: float, customer: Customer) -> float:
"""Calculate the discount amount for a customer.
Args:
price: The original price.
customer: The customer object.
Returns:
The discount amount as a float.
Raises:
ValueError: If price is negative.
"""
if price < 0:
raise ValueError("Price cannot be negative")
# ...
7. Refactor for Testability
Task: Legacy code is often tightly coupled, making it hard to test. This prompt helps you identify and refactor sections that hinder unit testing.
Prompt:
You are a Python test-driven development advocate. Analyze `{file_path}` and identify parts of the code that are difficult to test, such as:
- Functions that depend on global state or external services.
- Hard-coded dependencies.
- Complex logic embedded in I/O operations.
For each issue, suggest a refactoring that improves testability, such as:
- Extract pure functions.
- Use dependency injection.
- Introduce interfaces or abstract base classes.
Provide code snippets for the refactored version.
Example Result:
Original:
def send_report():
data = fetch_from_db()
report = generate_report(data)
send_email(report) # sends via SMTP
Refactored:
def send_report(email_sender: Callable): # dependency injection
data = fetch_from_db()
report = generate_report(data)
email_sender(report)
Now you can pass a mock email sender in tests.
8. Modernize Old Python Syntax
Task: Legacy code may use outdated patterns like % formatting, print statements, or xrange. This prompt helps you update syntax to modern Python (3.9+).
Prompt:
You are a Python modernization expert. The file `{file_path}` contains Python 2 or early Python 3 code. Update it to modern Python (3.10+), including:
- Replace `%` string formatting with f-strings.
- Replace `xrange` with `range`.
- Replace `print` statements with `print()` functions.
- Use `pathlib` instead of `os.path` for file operations.
- Use `asyncio` instead of old threading if appropriate.
Provide a summary of changes made and the updated code.
Example Result:
# Before
print "Hello, %s!" % name
# After
print(f"Hello, {name}!")
9. Suggest Design Patterns for Common Problems
Task: Sometimes legacy code reinvents the wheel. This prompt helps you recognize situations where a design pattern would simplify the code.
Prompt:
You are a software architect. In `{file_path}`, identify places where a well-known design pattern (e.g., Strategy, Factory, Observer, Singleton) could be applied to make the code more maintainable and flexible. For each suggestion, provide:
- The lines of code that could be refactored.
- The pattern you recommend.
- A brief explanation of why it fits.
- A skeleton code example in Python.
Example Result:
Lines 100-200 contain a series of if-elif statements to handle different payment methods. This can be refactored using the Strategy pattern:
class PaymentStrategy:
def pay(self, amount):
pass
class CreditCardPayment(PaymentStrategy):
def pay(self, amount):
# credit card logic
pass
# ... other strategies
class PaymentProcessor:
def __init__(self, strategy: PaymentStrategy):
self.strategy = strategy
def process(self, amount):
self.strategy.pay(amount)
10. Generate a Refactoring Plan for the Whole Project
Task: Sometimes you need a bird's-eye view of what to refactor first. This prompt helps you prioritize based on impact and risk.
Prompt:
You are a lead Python developer. Analyze the project in the directory `{project_path}` (list files if needed). Produce a refactoring plan with prioritized tasks. For each task:
- Describe the issue (e.g., code duplication in module X).
- Suggest the refactoring approach.
- Estimate the effort (low, medium, high).
- Note the risk (low, medium, high).
- Suggest an order based on risk and effort (e.g., do high-risk low-effort tasks first).
Focus on the most impactful changes that can be done incrementally.
Example Result:
Refactoring Plan:
1. High Priority, Low Effort: Replace deprecated `pkg_resources` with `importlib.metadata` in `setup.py`.
2. High Priority, Medium Effort: Remove circular imports in `models.py` by moving shared constants to a separate module.
3. Medium Priority, Low Effort: Add type hints to public APIs in `api.py`.
...
These prompts are starting points, not silver bullets. The key is to iterate: use the AI's suggestions, review them, and refine your prompts based on the results. You'll often need to provide more context, such as the purpose of a function or the expected behavior, to get the most useful output.
Remember, AI is a tool to augment your skills, not replace them. The best refactoring decisions still require human judgment. But by using these prompts, you can save hours of manual analysis and focus your energy on the creative aspects of software development.
So next time you're facing a legacy codebase, don't despair. Fire up your favorite LLM, craft a precise prompt, and watch the transformation unfold. Happy refactoring!
Comments