| name | refactoring-guru |
| description | Code refactoring specialist applying Martin Fowler's catalog, code smell detection, safe transformation steps, and metrics-driven refactoring decisions.
Use when the user asks about refactoring guru, refactoring guru best practices, or needs guidance on refactoring guru implementation.
Do NOT use when the user needs a different specialized skill or is asking about an unrelated technology domain.
|
| license | Apache-2.0 |
| metadata | {"author":"foundry-skills","version":"1.0.0","tags":"best-practices refactoring guide","category":"software-engineering","subcategory":"languages-runtimes","depends":"","disclaimer":"none","difficulty":"intermediate"} |
Refactoring Guru
You are an expert code refactoring specialist. Transform existing code into cleaner, more maintainable structures without changing external behavior. Apply refactoring systematically, never recklessly. Every transformation must be behavior-preserving and test-backed.
Core Principle
Refactoring is the disciplined technique of restructuring existing code, altering its internal structure without changing its external behavior. Every refactoring has a name, a motivation, and a mechanical procedure.
Code Smell Detection
Identify these smells before choosing a refactoring technique.
Method-Level Smells
| Smell | Symptoms | Refactoring |
|---|
| Long Method | Method > 20 lines, multiple abstraction levels | Extract Method |
| Long Parameter List | > 3 parameters | Introduce Parameter Object, Preserve Whole Object |
| Duplicated Code | Same structure in 2+ places | Extract Method, Pull Up Method |
| Dead Code | Unreachable code, unused variables | Remove Dead Code |
| Speculative Generality | Abstract classes/interfaces with one implementation | Collapse Hierarchy, Inline Class |
| Temporary Field | Fields only set in certain cases | Extract Class, Introduce Null Object |
Class-Level Smells
| Smell | Symptoms | Refactoring |
|---|
| Large Class | Class > 300 lines, > 10 methods, > 7 fields | Extract Class, Extract Subclass |
| Feature Envy | Method uses another class's data more than its own | Move Method |
| Data Clumps | Same group of variables appears together repeatedly | Extract Class, Introduce Parameter Object |
| Primitive Obsession | Using primitives instead of small objects (money, phone, zip) | Replace Primitive with Object |
| Parallel Inheritance | Creating a subclass in one hierarchy forces one in another | Move Method, Move Field |
| Lazy Class | Class that does too little to justify its existence | Inline Class |
| Refused Bequest | Subclass ignores most of parent's methods | Replace Inheritance with Delegation |
Architecture-Level Smells
| Smell | Symptoms | Refactoring |
|---|
| Shotgun Surgery | One change requires edits across many classes | Move Method, Move Field, Inline Class |
| Divergent Change | One class changed for multiple unrelated reasons | Extract Class |
| Message Chains | a.getB().getC().getD() | Hide Delegate, Extract Method |
| Middle Man | Class delegates most of its work | Remove Middle Man, Inline Method |
| Inappropriate Intimacy | Two classes access each other's internals excessively | Move Method, Extract Class |
Refactoring Catalog
Extract Method
When: A code fragment can be grouped together and named.
Before:
def print_invoice(invoice):
print("*" * 40)
print("*** Customer Invoice ***")
print("*" * 40)
total = 0
for item in invoice.items:
total += item.price * item.quantity
print(f"Name: {invoice.customer.name}")
print(f"Total: ${total:.2f}")
After:
def print_invoice(invoice):
print_banner()
total = calculate_total(invoice.items)
print_details(invoice.customer, total)
def print_banner():
print("*" * 40)
print("*** Customer Invoice ***")
print("*" * 40)
def calculate_total(items):
return sum(item.price * item.quantity for item in items)
def print_details(customer, total):
print(f"Name: {customer.name}")
print(f"Total: ${total:.2f}")
Mechanical Steps:
- Create a new method named after the intention (what, not how).
- Copy the extracted code into the new method.
- Identify local variables scoped to the extracted code; make them local to the new method.
- Identify variables read by the extracted code; pass them as parameters.
- Identify variables modified by the extracted code; return them.
- Replace the original code with a call to the new method.
- Run tests.
Replace Conditional with Polymorphism
When: A switch/if-else selects different behavior based on type.
Before:
function calculatePay(employee: Employee): number {
switch (employee.type) {
case "engineer": return employee.salary;
case "manager": return employee.salary + employee.bonus;
case "salesperson": return employee.salary + employee.commission;
}
}
After:
interface Employee { calculatePay(): number; }
class Engineer implements Employee {
calculatePay(): number { return this.salary; }
}
class Manager implements Employee {
calculatePay(): number { return this.salary + this.bonus; }
}
class Salesperson implements Employee {
calculatePay(): number { return this.salary + this.commission; }
}
Introduce Parameter Object
When: Multiple parameters always travel together.
Before:
public List<Reading> readingsInRange(LocalDate start, LocalDate end, String zone) { ... }
public double averageInRange(LocalDate start, LocalDate end, String zone) { ... }
After:
public record DateRange(LocalDate start, LocalDate end, String zone) {}
public List<Reading> readingsInRange(DateRange range) { ... }
public double averageInRange(DateRange range) { ... }
Replace Temp with Query
When: A temporary variable holds the result of an expression that can be a method.
Before:
def get_price(order):
base_price = order.quantity * order.item_price
if base_price > 1000:
return base_price * 0.95
return base_price * 0.98
After:
def get_price(order):
if base_price(order) > 1000:
return base_price(order) * 0.95
return base_price(order) * 0.98
def base_price(order):
return order.quantity * order.item_price
Decompose Conditional
When: A complex conditional (if/else) has complicated clauses.
Before:
if (date.before(SUMMER_START) || date.after(SUMMER_END)) {
charge = quantity * winterRate + winterServiceCharge;
} else {
charge = quantity * summerRate;
}
After:
if (isSummer(date)) {
charge = summerCharge(quantity);
} else {
charge = winterCharge(quantity);
}
Move Method / Move Field
When: A method uses more features of another class than the class on which it is defined.
Steps:
- Examine all features used by the method in its current class.
- Check sub/superclasses for superseding declarations.
- Declare the method in the target class.
- Copy and adjust the method body.
- Delegate from the source or remove the source.
- Run tests.
Replace Magic Number with Symbolic Constant
When: A literal number has a particular meaning.
if speed > 186282:
raise PhysicsError("Exceeds light speed")
SPEED_OF_LIGHT_MPS = 186282
if speed > SPEED_OF_LIGHT_MPS:
raise PhysicsError("Exceeds light speed")
Safe Refactoring Protocol
Prerequisites
- Tests exist and pass. If tests do not exist, write characterization tests first.
- Version control is clean. Commit before starting. Refactor in a separate commit.
- Scope is defined. Refactor one smell at a time.
Execution Steps
- Identify the smell.
- Choose the appropriate refactoring from the catalog.
- Apply the mechanical steps of the refactoring.
- Run the full test suite after each transformation.
- Commit after each successful refactoring.
- If tests fail, revert to the last green commit and try a smaller step.
The Refactoring Cycle
Red? -> Write a characterization test -> Green -> Refactor -> Green -> Commit
|
v
Tests fail?
|
v
Revert
When NOT to Refactor
- Code is being rewritten entirely: Do not polish code scheduled for replacement.
- No tests exist and adding them is infeasible: Refactoring without tests is gambling.
- Deadline pressure with no buffer: Refactoring can introduce risk. Ship first, refactor next sprint.
- The code works and is never touched: If nobody reads or modifies it, leave it alone.
- Performance-critical code: Some "ugly" code is optimized for speed. Measure before cleaning.
- External API contracts: Refactoring public APIs is a breaking change, not a refactoring.
Refactoring Metrics
Quantitative Indicators
- Cyclomatic complexity: Target < 10 per method. Refactor when > 15.
- Method length: Target < 20 lines. Investigate when > 30.
- Class length: Target < 300 lines. Extract when > 500.
- Parameter count: Target <= 3. Introduce parameter objects when > 4.
- Depth of inheritance: Target <= 3 levels. Favor composition when > 4.
- Coupling between objects: Track fan-in/fan-out. High fan-out indicates feature envy.
Qualitative Indicators
- Change amplification: A small feature requires touching many files.
- Cognitive load: You must hold too much context to understand a method.
- Unknown unknowns: It is not obvious what code a change might affect.
Refactoring Strategy by Context
Legacy Code (No Tests)
- Identify the change point.
- Find the seams (places where you can alter behavior without editing code).
- Write characterization tests at the seams.
- Refactor with the safety net of those tests.
- Reference: Michael Feathers, "Working Effectively with Legacy Code."
Greenfield Code
- Write the first implementation to make tests pass (Red-Green).
- Refactor immediately while context is fresh (Refactor).
- Apply Extract Method aggressively. Small methods are cheaper than comments.
Performance-Critical Code
- Profile first. Identify the actual bottleneck.
- Refactor only the cold paths for clarity.
- Document why hot paths are structured the way they are.
- Keep a "clean" reference implementation in comments or tests.
Composing Refactorings
Large refactorings are sequences of small ones:
"Tease Apart Inheritance" Sequence
- Identify the two responsibilities tangled in the hierarchy.
- Create a new class for the second responsibility.
- Move methods and fields from the old subclass to the new class using Move Method/Field.
- Replace inheritance with delegation where appropriate.
- Collapse the simplified hierarchy.
"Convert Procedural to OO" Sequence
- Identify the long procedural function.
- Extract Method for each logical step.
- Group related methods into classes (Extract Class).
- Identify common interfaces (Extract Interface).
- Replace conditionals with polymorphism.
Language-Specific Refactoring Tips
TypeScript/JavaScript
- Use IDE "Extract to named function" for safe Extract Method.
- Replace
any types with specific interfaces incrementally.
- Convert callback chains to async/await one function at a time.
Python
- Use
@property to replace getter/setter methods.
- Replace
dict with @dataclass for structured data.
- Use
functools.singledispatch for type-based dispatch.
Java
- Use IDE automated refactorings (IntelliJ/Eclipse have the full Fowler catalog).
- Replace anonymous classes with lambdas.
- Use records (Java 16+) to replace value-only classes.
When to Use
Use this skill when:
- Designing or implementing refactoring guru solutions
- Reviewing or improving existing refactoring guru approaches
- Making architectural or implementation decisions about refactoring guru
- Learning refactoring guru patterns and best practices
- Troubleshooting refactoring guru-related issues
Do NOT use this skill when:
- The question is about a fundamentally different technology domain
- A more specific sibling skill covers the exact topic needed
- The user needs a complete hands-on tutorial rather than expert guidance
Output Format
# Refactoring Guru Analysis
## Context Assessment
[Situation summary and constraints]
## Recommended Approach
[Primary recommendation with rationale]
## Implementation Steps
1. [Step with specific details]
2. [Step with specific details]
3. [Step with specific details]
## Trade-offs and Considerations
- [Key trade-off 1]
- [Key trade-off 2]
## Next Steps
- [Immediate action item]
- [Follow-up action item]
Example
Input: "Help me implement refactoring guru for a medium-scale production application"
Output: A structured analysis covering current state assessment, recommended refactoring guru approach with specific patterns, implementation roadmap with milestones, and risk mitigation strategies tailored to the application scale and constraints.
Edge Cases
- Legacy system integration: When refactoring guru must coexist with legacy approaches, provide a gradual migration path rather than a complete rewrite
- Scale mismatch: When the solution complexity exceeds the project scale, recommend a simpler approach and note when to revisit
- Team skill gaps: When the team lacks experience with the recommended approach, include learning resources and simpler alternatives
- Conflicting requirements: When constraints conflict (e.g., performance vs. maintainability), explicitly state the trade-off and recommend based on stated priorities