Claude Prompt to Refactor Legacy Code for Readability and Maintainability
This Claude prompt for refactoring legacy code is built for developers who've inherited a function or module that works but is hard to read, extend, or safely change. It's aimed at the common situation where you can't rewrite a whole system, but you can clean up one file or function at a time without breaking existing behavior.
The prompt asks the model to refactor for readability — clearer naming, smaller functions, removed duplication — while explicitly preserving external behavior, and to explain each change so you understand what moved and why before you merge it. That explanation step is what separates a usable refactor from a black-box rewrite: you need to be able to review the diff against the reasoning, not just trust that it "looks cleaner."
Because the instructions are format-agnostic (role, context, constraints, output format), the same prompt works in Claude, ChatGPT, or Gemini — you paste in the code, describe what it does and any constraints (language version, style guide, things you can't change), and get back refactored code plus a change-by-change explanation you can use in a pull request description.
Prompt template
Role: You are a senior software engineer specializing in code readability and maintainability. Context: Language/framework: [LANGUAGE_AND_VERSION] Style guide or conventions to follow: [STYLE_GUIDE_OR_LINTING_RULES] What this code does: [BRIEF_DESCRIPTION_OF_FUNCTIONALITY] Constraints (things that must not change): [PUBLIC_API_SIGNATURES_OR_OTHER_CONSTRAINTS] Code to refactor: [PASTE_FULL_FUNCTION_OR_FILE_HERE] Instructions: 1. Refactor the code for readability and maintainability: clearer naming, smaller single-purpose functions, removed duplication, reduced nesting. 2. Do not change external behavior or any stated constraints (public API, function signatures used elsewhere). 3. List each change you made, one by one, with a short reason for it. 4. Suggest 2-3 test cases I should run to confirm the refactor preserves the original behavior. 5. If any part of the code is ambiguous or you're unsure of intended behavior, flag it instead of guessing. Output format: 1. Refactored code in a fenced code block 2. A numbered list of changes with a one-line reason for each 3. Suggested test cases
Example input
Language/framework: Python 3.11
Style guide: PEP 8, project uses type hints throughout
What this code does: Calculates a customer's order total including discounts and tax, used at checkout
Constraints: function signature `calculate_total(order, customer)` is called from 4 other places, must not change
Code to refactor:
def calculate_total(order, customer):
t = 0
for i in order['items']:
t = t + i['price'] * i['qty']
if customer['type'] == 'vip':
t = t - (t * 0.1)
elif customer['type'] == 'employee':
t = t - (t * 0.2)
if t > 100 and customer['type'] == 'vip':
t = t - 5
tax = t * 0.08
t = t + tax
return tExample output
```python
DISCOUNT_RATES = {
"vip": 0.10,
"employee": 0.20,
}
VIP_LARGE_ORDER_DISCOUNT = 5.0
VIP_LARGE_ORDER_THRESHOLD = 100.0
TAX_RATE = 0.08
def calculate_total(order: dict, customer: dict) -> float:
subtotal = _calculate_subtotal(order)
discounted = _apply_customer_discount(subtotal, customer)
discounted = _apply_vip_large_order_discount(discounted, customer)
return _apply_tax(discounted)
def _calculate_subtotal(order: dict) -> float:
return sum(item["price"] * item["qty"] for item in order["items"])
def _apply_customer_discount(amount: float, customer: dict) -> float:
rate = DISCOUNT_RATES.get(customer["type"], 0.0)
return amount - (amount * rate)
def _apply_vip_large_order_discount(amount: float, customer: dict) -> float:
if customer["type"] == "vip" and amount > VIP_LARGE_ORDER_THRESHOLD:
return amount - VIP_LARGE_ORDER_DISCOUNT
return amount
def _apply_tax(amount: float) -> float:
return amount + (amount * TAX_RATE)
```
Changes made:
1. Renamed `t` to `subtotal`/`amount`/`discounted` at each stage, since a single letter reused for different meanings made the flow hard to follow.
2. Split the single function into four small helpers (`_calculate_subtotal`, `_apply_customer_discount`, `_apply_vip_large_order_discount`, `_apply_tax`), each handling one step of the calculation.
3. Replaced the if/elif discount logic with a `DISCOUNT_RATES` dictionary lookup, so adding a new customer type doesn't require another elif branch.
4. Extracted the magic numbers (0.1, 0.2, 5, 100, 0.08) into named constants so their purpose is clear and they're defined in one place.
5. Added type hints matching the project's convention, and kept the public function signature `calculate_total(order, customer)` unchanged so the 4 existing call sites are unaffected.
Suggested test cases:
1. A regular (non-VIP, non-employee) customer with a small order, to confirm subtotal and tax are calculated correctly with no discount applied.
2. A VIP customer with an order total just above 100, to confirm both the 10% VIP discount and the additional $5 large-order discount are applied together correctly.
3. An employee customer, to confirm the 20% discount applies and the VIP-only large-order discount does not.When to use it
- You've inherited a function or file with unclear naming, deep nesting, or duplicated logic that's hard to safely modify
- You need to add a feature to old code but the current structure makes it risky to touch
- You're preparing a cleanup pull request and need a clear explanation of each change for reviewers
- You want a second opinion on whether a piece of code can be simplified without changing its behavior
Best practices
- Include the full function or file, not just a snippet, so the model can see all the logic paths and dependencies
- State your language version, style guide, and anything that must not change (public API, function signatures used elsewhere)
- Ask for the refactor and a separate explanation of each change, so you can review reasoning alongside the diff
- Test the refactored code against your existing test suite (or ask the model to suggest test cases) before merging, since a model can't run your code to confirm behavior is unchanged
Common mistakes
- Pasting a code snippet without enough surrounding context, leading to a refactor that breaks an assumption made elsewhere in the codebase
- Not stating constraints like a public API that other code depends on, resulting in a rewrite that changes the function's signature
- Merging the refactored code without running it against real tests, since the model can suggest correct-looking code without verifying it executes identically
- Asking for a refactor with no explanation, then being unable to justify the change in code review
FAQs
What's a good Claude prompt for refactoring legacy code?
A good prompt includes the full function or file, states the language, style guide, and any constraints like public API signatures, and explicitly asks the model to preserve external behavior while explaining each change it makes.
Can Claude or ChatGPT refactor code without breaking it?
Neither model can run your code to confirm behavior is unchanged, so refactored output should always be checked against your existing tests, or new tests should be run against the suggested test cases before merging.
How do I keep an AI-refactored function from changing its public API?
State explicitly in the prompt which function signatures or interfaces must not change, since other code depends on them. Models generally respect stated constraints when they're specified clearly.
Why should I ask for an explanation of each change instead of just the refactored code?
An explanation lets you review the reasoning behind each change alongside the diff, which makes it possible to catch an incorrect assumption before merging rather than trusting the output blindly.
Does this refactoring prompt work for languages other than Python?
Yes — the prompt is written generically with a language placeholder, so it works for JavaScript, Java, Go, or any other language by changing the language, style guide, and constraint details.