first commit
This commit is contained in:
@@ -0,0 +1,674 @@
|
||||
---
|
||||
name: refactor
|
||||
description: "Expert code refactoring based on Martin Fowler's catalog — improve maintainability without changing behavior. Covers code smells, composing methods, moving features, organizing data, simplifying conditionals, method calls, and generalization. Triggers on: refactor, 重构, clean up, improve code, code smell, extract method, rename, simplify."
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
# Refactor — Expert Code Restructuring
|
||||
|
||||
Surgical code refactoring based on Martin Fowler's <Refactoring> (2nd Edition) catalog. Improve structure, readability, and maintainability without changing external behavior. Gradual evolution, not revolution.
|
||||
|
||||
---
|
||||
|
||||
## When to Use
|
||||
|
||||
This skill activates when:
|
||||
- Code is hard to understand or maintain
|
||||
- Functions/classes have grown too large
|
||||
- Code smells are detected
|
||||
- Adding features is difficult due to poor structure
|
||||
- User explicitly requests refactoring, cleanup, or improvement
|
||||
- User says: refactor, 重构, clean up, improve code, code smell, extract method, rename, simplify
|
||||
|
||||
---
|
||||
|
||||
## The Golden Rules
|
||||
|
||||
These five rules are non-negotiable. Violating any of them turns refactoring into reckless editing.
|
||||
|
||||
### 1. Behavior is Preserved
|
||||
|
||||
Only *how* the code works changes, never *what* it does. If tests existed before, they must pass after. If the refactoring introduces a behavioral change, it's not refactoring — it's rewriting.
|
||||
|
||||
### 2. Small Steps
|
||||
|
||||
Each change should be the smallest possible transformation that compiles and passes tests. If a step breaks, you know exactly which change caused it. Refactoring is a series of tiny, safe transformations, not one big rewrite.
|
||||
|
||||
### 3. Version Control is Your Friend
|
||||
|
||||
Commit before starting. Commit after each successful step. This gives you infinite undo. Branch from a clean state so you can abandon the refactoring without consequences.
|
||||
|
||||
### 4. Tests are Essential
|
||||
|
||||
"Without tests, you're not refactoring — you're just editing." If tests don't exist for the target code, write characterization tests first. These tests capture the current behavior so you can detect regressions.
|
||||
|
||||
### 5. One Thing at a Time
|
||||
|
||||
Never mix refactoring with feature changes. Never refactor two unrelated things simultaneously. Each commit should contain exactly one refactoring operation.
|
||||
|
||||
---
|
||||
|
||||
## When NOT to Refactor
|
||||
|
||||
| Scenario | Action |
|
||||
|----------|--------|
|
||||
| Code works and won't change again | Leave it alone |
|
||||
| Critical production path with no tests | Write characterization tests first |
|
||||
| Under tight deadline pressure | Document the smell, refactor later |
|
||||
| No clear purpose or benefit | Don't refactor for refactoring's sake |
|
||||
| Code is fundamentally wrong | This is a rewrite, not a refactoring |
|
||||
|
||||
---
|
||||
|
||||
## Code Smells Catalog
|
||||
|
||||
Based on Fowler's taxonomy. Before refactoring, identify which smell is present.
|
||||
|
||||
### Bloaters
|
||||
|
||||
| Smell | Description | Primary Refactoring |
|
||||
|-------|-------------|-------------------|
|
||||
| **Long Method** | Method > 10-15 lines, doing multiple things | Extract Method, Replace Temp with Query |
|
||||
| **Large Class** | Class with too many fields/methods (God Object) | Extract Class, Extract Subclass |
|
||||
| **Primitive Obsession** | Using primitives instead of small objects | Replace Data Value with Object, Replace Type Code with Class |
|
||||
| **Long Parameter List** | Method with > 3-4 parameters | Introduce Parameter Object, Preserve Whole Object |
|
||||
| **Data Clumps** | Same group of data appearing together | Extract Class, Introduce Parameter Object |
|
||||
|
||||
### Object-Orientation Abusers
|
||||
|
||||
| Smell | Description | Primary Refactoring |
|
||||
|-------|-------------|-------------------|
|
||||
| **Switch Statements** | Repeated switch/if-else on type codes | Replace Conditional with Polymorphism, Replace Type Code with Subclasses |
|
||||
| **Temporary Field** | Field only set in certain circumstances | Extract Class, Introduce Null Object |
|
||||
| **Refused Bequest** | Subclass doesn't use inherited members | Replace Inheritance with Delegation, Push Down Method/Field |
|
||||
| **Alternative Classes with Different Interfaces** | Classes doing similar things with different names | Rename Method, Move Method, Extract Superclass |
|
||||
|
||||
### Change Preventers
|
||||
|
||||
| Smell | Description | Primary Refactoring |
|
||||
|-------|-------------|-------------------|
|
||||
| **Divergent Change** | One class changed for different reasons | Extract Class |
|
||||
| **Shotgun Surgery** | One change requires many small changes across classes | Move Method, Move Field, Inline Class |
|
||||
| **Parallel Inheritance Hierarchies** | Adding a subclass to one hierarchy forces adding to another | Move Method, Move Field |
|
||||
|
||||
### Dispensables
|
||||
|
||||
| Smell | Description | Primary Refactoring |
|
||||
|-------|-------------|-------------------|
|
||||
| **Comments** | Comments explaining what code does (not why) | Extract Method, Rename Variable, Introduce Assertion |
|
||||
| **Duplicate Code** | Same code structure in multiple places | Extract Method, Pull Up Method, Form Template Method |
|
||||
| **Lazy Class** | Class doing too little to justify existence | Inline Class, Collapse Hierarchy |
|
||||
| **Data Class** | Class with only fields and getters/setters | Move Method, Encapsulate Field, Encapsulate Collection |
|
||||
| **Dead Code** | Unused code, imports, commented-out blocks | Delete it (git history has it) |
|
||||
| **Speculative Generality** | Code built for "someday" that never came | Inline Class, Collapse Hierarchy, Remove Parameter |
|
||||
|
||||
### Couplers
|
||||
|
||||
| Smell | Description | Primary Refactoring |
|
||||
|-------|-------------|-------------------|
|
||||
| **Feature Envy** | Method uses another class's data more than its own | Move Method, Extract Method + Move Method |
|
||||
| **Inappropriate Intimacy** | Classes know too much about each other's internals | Move Method, Move Field, Replace Delegation with Hidden Delegate |
|
||||
| **Message Chains** | `a.getB().getC().getD().doSomething()` | Hide Delegate, Extract Method |
|
||||
| **Middle Man** | Class delegates everything to another class | Remove Middle Man, Inline Method |
|
||||
| **Incomplete Library Class** | Library missing methods you need | Introduce Foreign Method, Introduce Local Extension |
|
||||
|
||||
---
|
||||
|
||||
## Refactoring Techniques Catalog
|
||||
|
||||
Organized by category, from Fowler's catalog. Each technique includes its mechanical steps.
|
||||
|
||||
### Composing Methods
|
||||
|
||||
#### Extract Method
|
||||
Turn a code fragment into a method whose name explains its purpose.
|
||||
|
||||
**Mechanics:**
|
||||
1. Create a new method named after what the fragment does (not how)
|
||||
2. Copy the extracted code into the new method
|
||||
3. Identify local variables: read-only become parameters, modified become return values
|
||||
4. Pass parameters and handle return values
|
||||
5. Replace the original fragment with a call to the new method
|
||||
6. Test
|
||||
|
||||
**Before:**
|
||||
```java
|
||||
void printOwing() {
|
||||
printBanner();
|
||||
// Print details
|
||||
System.out.println("name: " + _name);
|
||||
System.out.println("amount: " + getOutstanding());
|
||||
}
|
||||
```
|
||||
|
||||
**After:**
|
||||
```java
|
||||
void printOwing() {
|
||||
printBanner();
|
||||
printDetails(getOutstanding());
|
||||
}
|
||||
|
||||
void printDetails(double outstanding) {
|
||||
System.out.println("name: " + _name);
|
||||
System.out.println("amount: " + outstanding);
|
||||
}
|
||||
```
|
||||
|
||||
#### Inline Method
|
||||
Replace a method call with its body when the method body is as clear as the name.
|
||||
|
||||
**Mechanics:**
|
||||
1. Check the method is not polymorphic (no subclasses override it)
|
||||
2. Find all callers
|
||||
3. Replace each call with the method body
|
||||
4. Delete the method definition
|
||||
5. Test
|
||||
|
||||
#### Extract Variable
|
||||
Put the result of an expression (or part of it) in a self-explanatory variable.
|
||||
|
||||
**Before:**
|
||||
```java
|
||||
if (platform.toUpperCase().indexOf("MAC") > -1 &&
|
||||
browser.toUpperCase().indexOf("IE") > -1 &&
|
||||
wasInitialized() && resize > 0) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**After:**
|
||||
```java
|
||||
final boolean isMacOs = platform.toUpperCase().indexOf("MAC") > -1;
|
||||
final boolean isIEBrowser = browser.toUpperCase().indexOf("IE") > -1;
|
||||
final boolean wasResized = resize > 0;
|
||||
if (isMacOs && isIEBrowser && wasInitialized() && wasResized) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### Inline Temp
|
||||
Replace a temp variable with its expression when the temp is only used once and the expression is clear.
|
||||
|
||||
#### Replace Temp with Query
|
||||
Extract the expression into a method. Temps that are computed once and reused are replaced with method calls.
|
||||
|
||||
#### Split Temporary Variable
|
||||
A temp assigned more than once (not loop/collecting) should be split into separate variables, one per responsibility.
|
||||
|
||||
#### Remove Assignments to Parameters
|
||||
Don't assign to parameters. Use a local variable instead.
|
||||
|
||||
#### Replace Method with Method Object
|
||||
When a long method uses many local variables that make Extract Method hard, turn the method into its own class, with locals as fields.
|
||||
|
||||
#### Substitute Algorithm
|
||||
Replace an algorithm with a clearer one.
|
||||
|
||||
---
|
||||
|
||||
### Moving Features Between Objects
|
||||
|
||||
#### Move Method
|
||||
Move a method to the class where it's used most.
|
||||
|
||||
**Mechanics:**
|
||||
1. Check all features used by the method on its current class
|
||||
2. Check for polymorphism (subclass/superclass methods)
|
||||
3. Create the method on the target class, adapting as needed
|
||||
4. Reference the target object from the source
|
||||
5. Turn the source method into a delegating method, or remove it
|
||||
6. Test
|
||||
|
||||
#### Move Field
|
||||
Move a field to the class where it's used most.
|
||||
|
||||
#### Extract Class
|
||||
When a class does the work of two, split it. Create a new class and move relevant fields and methods.
|
||||
|
||||
#### Inline Class
|
||||
When a class does almost nothing, absorb it into the class that uses it most.
|
||||
|
||||
#### Hide Delegate
|
||||
Create methods on the server to hide the delegate chain. `manager = person.getDepartment().getManager()` → `manager = person.getManager()`.
|
||||
|
||||
#### Remove Middle Man
|
||||
When a class is doing too much delegation, call the delegate directly.
|
||||
|
||||
#### Introduce Foreign Method
|
||||
When a server class needs an additional method but you can't modify it, create a method on the client with the server instance as the first argument.
|
||||
|
||||
#### Introduce Local Extension
|
||||
When you need multiple foreign methods, create an extension class (subclass or wrapper).
|
||||
|
||||
---
|
||||
|
||||
### Organizing Data
|
||||
|
||||
#### Self Encapsulate Field
|
||||
Access fields through getters and setters, even within the owning class.
|
||||
|
||||
#### Replace Data Value with Object
|
||||
When a data item needs additional data or behavior, turn it into an object.
|
||||
|
||||
**Before:**
|
||||
```java
|
||||
class Order {
|
||||
private String customer; // Just a string
|
||||
}
|
||||
```
|
||||
|
||||
**After:**
|
||||
```java
|
||||
class Order {
|
||||
private Customer customer; // Rich object with name, address, credit rating
|
||||
}
|
||||
```
|
||||
|
||||
#### Change Value to Reference
|
||||
When you need to share one instance of an object across multiple places.
|
||||
|
||||
#### Change Reference to Value
|
||||
When a reference object is small, immutable, and you want value semantics.
|
||||
|
||||
#### Replace Array with Object
|
||||
When an array holds heterogeneous data (`String[] row = new String[3]` — name, score, wins), replace with an object.
|
||||
|
||||
#### Duplicate Observed Data
|
||||
Domain data lives in a GUI control but domain logic needs it. Copy the data into a domain object and set up an observer to keep the two in sync (Observer pattern). Separates presentation from domain so each can evolve independently.
|
||||
|
||||
#### Change Unidirectional Association to Bidirectional
|
||||
Two classes need each other's features but only one holds a reference. Add a back-pointer and make the modifiers on both ends keep the link consistent. Add the reference only when genuinely needed — bidirectional links raise coupling and risk inconsistency.
|
||||
|
||||
#### Change Bidirectional Association to Unidirectional
|
||||
A two-way link exists but one side no longer uses the other. Drop the unneeded direction. Reduces coupling, simplifies lifecycle management, and avoids "zombie" objects kept alive only by a stale back-pointer.
|
||||
|
||||
#### Replace Magic Number with Symbolic Constant
|
||||
Replace literal numbers/strings with named constants.
|
||||
|
||||
#### Encapsulate Field
|
||||
Make public fields private and provide accessors.
|
||||
|
||||
#### Encapsulate Collection
|
||||
Never return the raw collection. Return a read-only view and provide add/remove methods.
|
||||
|
||||
#### Replace Type Code with Class
|
||||
Replace a numeric/string type code with a class that has meaningful behavior.
|
||||
|
||||
#### Replace Type Code with Subclasses
|
||||
When type code affects behavior, use polymorphism instead of conditionals.
|
||||
|
||||
#### Replace Type Code with State/Strategy
|
||||
Similar to subclasses but uses composition when the type can change at runtime.
|
||||
|
||||
#### Replace Subclass with Fields
|
||||
When subclasses vary only in constant data, replace them with fields on a single class.
|
||||
|
||||
---
|
||||
|
||||
### Simplifying Conditional Expressions
|
||||
|
||||
#### Decompose Conditional
|
||||
Extract the condition, then-part, and else-part into separate methods.
|
||||
|
||||
**Before:**
|
||||
```java
|
||||
if (date.before(SUMMER_START) || date.after(SUMMER_END)) {
|
||||
charge = quantity * _winterRate + _winterServiceCharge;
|
||||
} else {
|
||||
charge = quantity * _summerRate;
|
||||
}
|
||||
```
|
||||
|
||||
**After:**
|
||||
```java
|
||||
if (isSummer(date)) {
|
||||
charge = summerCharge(quantity);
|
||||
} else {
|
||||
charge = winterCharge(quantity);
|
||||
}
|
||||
```
|
||||
|
||||
#### Consolidate Conditional Expression
|
||||
Combine multiple conditionals that have the same result.
|
||||
|
||||
#### Consolidate Duplicate Conditional Fragments
|
||||
Move code that appears in every branch outside the conditional.
|
||||
|
||||
#### Remove Control Flag
|
||||
Replace control flags with break, continue, or return.
|
||||
|
||||
#### Replace Nested Conditional with Guard Clauses
|
||||
Use early returns for special cases instead of deep nesting.
|
||||
|
||||
**Before (arrow code):**
|
||||
```java
|
||||
double getPayAmount() {
|
||||
double result;
|
||||
if (_isDead) {
|
||||
result = deadAmount();
|
||||
} else {
|
||||
if (_isSeparated) {
|
||||
result = separatedAmount();
|
||||
} else {
|
||||
if (_isRetired) {
|
||||
result = retiredAmount();
|
||||
} else {
|
||||
result = normalPayAmount();
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
**After:**
|
||||
```java
|
||||
double getPayAmount() {
|
||||
if (_isDead) return deadAmount();
|
||||
if (_isSeparated) return separatedAmount();
|
||||
if (_isRetired) return retiredAmount();
|
||||
return normalPayAmount();
|
||||
}
|
||||
```
|
||||
|
||||
#### Replace Conditional with Polymorphism
|
||||
When a conditional chooses different behavior based on the type of an object, use subclasses.
|
||||
|
||||
#### Introduce Null Object
|
||||
Replace null checks with a null object that provides default behavior.
|
||||
|
||||
#### Introduce Assertion
|
||||
State assumptions explicitly with assertions.
|
||||
|
||||
---
|
||||
|
||||
### Making Method Calls Simpler
|
||||
|
||||
#### Rename Method
|
||||
The name should say what the method does. If you can't think of a good name, the method may have multiple responsibilities.
|
||||
|
||||
#### Add Parameter / Remove Parameter
|
||||
Add parameters when a method needs more info. Remove parameters when the method can get the info another way.
|
||||
|
||||
#### Separate Query from Modifier
|
||||
A method should either return a value OR change state, never both.
|
||||
|
||||
#### Parameterize Method
|
||||
Several methods doing similar things with different values → one method with a parameter.
|
||||
|
||||
#### Replace Parameter with Explicit Methods
|
||||
The inverse: when a parameter essentially selects different behavior, create separate methods.
|
||||
|
||||
#### Preserve Whole Object
|
||||
Pass the whole object instead of pulling individual fields from it.
|
||||
|
||||
#### Replace Parameter with Method
|
||||
*(refactoring.guru: Replace Parameter with Method Call)* When a parameter can be computed from data the object already has, remove the parameter and let the method call the query itself.
|
||||
|
||||
#### Introduce Parameter Object
|
||||
Group parameters that naturally go together into an object.
|
||||
|
||||
#### Remove Setting Method
|
||||
Make a field immutable by removing its setter and setting it in the constructor.
|
||||
|
||||
#### Hide Method
|
||||
Make methods private when they're not used outside the class.
|
||||
|
||||
#### Replace Constructor with Factory Method
|
||||
When you need more flexibility than a simple constructor call.
|
||||
|
||||
#### Replace Error Code with Exception
|
||||
Throw an exception instead of returning an error code.
|
||||
|
||||
#### Replace Exception with Test
|
||||
Check the condition first instead of catching an exception.
|
||||
|
||||
---
|
||||
|
||||
### Dealing with Generalization
|
||||
|
||||
#### Pull Up Field/Method/Constructor Body
|
||||
Move identical fields/methods/constructor code from subclasses to superclass.
|
||||
|
||||
#### Push Down Method/Field
|
||||
Move behavior from superclass to only the subclasses that use it.
|
||||
|
||||
#### Extract Subclass
|
||||
Create a subclass for a subset of features used in some instances.
|
||||
|
||||
#### Extract Superclass
|
||||
Create a superclass for shared features of similar classes.
|
||||
|
||||
#### Extract Interface
|
||||
Create an interface from a subset of a class's public methods.
|
||||
|
||||
#### Collapse Hierarchy
|
||||
Merge a superclass and subclass when they're not different enough.
|
||||
|
||||
#### Form Template Method
|
||||
Generalize an algorithm in the superclass, letting subclasses fill in the specifics.
|
||||
|
||||
#### Replace Inheritance with Delegation
|
||||
When a subclass only uses part of the superclass, use composition instead.
|
||||
|
||||
#### Replace Delegation with Inheritance
|
||||
When a delegating class needs access to all of the delegate's behavior.
|
||||
|
||||
---
|
||||
|
||||
## The Refactoring Process
|
||||
|
||||
### Phase 1: Prepare
|
||||
|
||||
1. **Write characterization tests** if they don't exist. These capture current behavior — they don't need to be elegant, just comprehensive enough to catch regressions.
|
||||
2. **Commit** current state. Start from a clean working tree.
|
||||
3. **Create a branch** for the refactoring. Keep it separate from feature work.
|
||||
|
||||
### Phase 2: Identify
|
||||
|
||||
1. **Smell the code.** Use the smell catalog above to classify what's wrong.
|
||||
2. **Understand the code.** Read it thoroughly. You must understand what it does before changing it.
|
||||
3. **Choose the right refactoring.** Pick from the technique catalog. Know what the result looks like before you start.
|
||||
|
||||
### Phase 3: Refactor (Small Steps)
|
||||
|
||||
For each step:
|
||||
1. **Make one small change.** One refactoring technique at a time.
|
||||
2. **Compile.** The code should compile after every change.
|
||||
3. **Run tests.** All tests must pass. If they don't, you've changed behavior.
|
||||
4. **Commit.** Create a commit with a message like `refactor: extract validateEmail method`.
|
||||
|
||||
Repeat until the smell is resolved.
|
||||
|
||||
### Phase 4: Verify
|
||||
|
||||
1. **All tests pass.** Non-negotiable.
|
||||
2. **Manual check.** Briefly run the application or review the diff for unintended changes.
|
||||
3. **Performance.** Ensure no performance regression. Simple refactorings rarely cause them, but check.
|
||||
|
||||
### Phase 5: Clean Up
|
||||
|
||||
1. **Remove stale comments.** If a refactoring made a comment obvious, delete the comment.
|
||||
2. **Check for dead code.** After refactorings, unused code may emerge.
|
||||
3. **Final commit.** Summarize the refactoring sequence.
|
||||
|
||||
---
|
||||
|
||||
## Refactoring Checklist
|
||||
|
||||
### Code Quality
|
||||
- [ ] Functions are small (< 20 lines preferred, < 50 lines max)
|
||||
- [ ] Each function does one thing (single responsibility)
|
||||
- [ ] No duplicated code (DRY)
|
||||
- [ ] Names describe what, not how
|
||||
- [ ] No magic numbers or strings
|
||||
- [ ] Dead code removed
|
||||
|
||||
### Structure
|
||||
- [ ] Related code is grouped together
|
||||
- [ ] Module boundaries are clear
|
||||
- [ ] Dependencies flow in one direction (no cycles)
|
||||
- [ ] No circular dependencies
|
||||
|
||||
### Conditionals
|
||||
- [ ] Guard clauses replace deep nesting
|
||||
- [ ] Complex conditions extracted to named methods
|
||||
- [ ] Polymorphism replaces type-switching conditionals
|
||||
- [ ] Null Object pattern where appropriate
|
||||
|
||||
### Type Safety (typed languages)
|
||||
- [ ] Types defined for all public APIs
|
||||
- [ ] No `any` usage without qualification
|
||||
- [ ] Nullable types explicitly marked
|
||||
- [ ] Type codes replaced with classes/enums
|
||||
|
||||
### Testing
|
||||
- [ ] Refactored code is tested
|
||||
- [ ] Edge cases are covered
|
||||
- [ ] All tests pass after each step
|
||||
- [ ] Characterization tests capture pre-refactoring behavior
|
||||
|
||||
---
|
||||
|
||||
## Language-Specific Guidance
|
||||
|
||||
### Java
|
||||
- Prefer `final` for locals that shouldn't change
|
||||
- Use IDE automated refactorings (Eclipse/IntelliJ) for mechanical steps
|
||||
- Leverage the type system: enums, records (Java 14+), sealed classes (Java 17+)
|
||||
|
||||
### JavaScript/TypeScript
|
||||
- Use destructuring to reduce parameter count
|
||||
- Prefer `const` over `let` for immutable bindings
|
||||
- Use TypeScript union types instead of type codes
|
||||
- Nullish coalescing (`??`) and optional chaining (`?.`) eliminate null-check noise
|
||||
|
||||
### Python
|
||||
- Use type hints for documenting intent during refactoring
|
||||
- Use `dataclasses` to replace tuple/data-class patterns
|
||||
- Use `@property` to replace getters
|
||||
- Context managers for resource cleanup patterns
|
||||
|
||||
### Go
|
||||
- Small interfaces preferred: accept interfaces, return structs
|
||||
- Use named return values when they improve clarity
|
||||
- Table-driven tests pair well with refactoring
|
||||
- Avoid deep nesting with early returns
|
||||
|
||||
### Rust
|
||||
- Use `Result` and `Option` instead of error codes and null
|
||||
- Pattern matching replaces if-else chains
|
||||
- `From` trait implementations clean up type conversions
|
||||
- Derive macros reduce boilerplate
|
||||
|
||||
---
|
||||
|
||||
## Common Refactoring Sequences
|
||||
|
||||
### Extract Method Sequence
|
||||
1. Create a new method named after intent
|
||||
2. Copy code fragment into new method
|
||||
3. Identify local variables → parameters / return values
|
||||
4. Call new method from original location
|
||||
5. Test
|
||||
|
||||
### Replace Conditional with Polymorphism Sequence
|
||||
1. Create subclasses for each variant
|
||||
2. Create a factory method that returns the right subclass
|
||||
3. Move the conditional body to the appropriate subclass method
|
||||
4. Delete the conditional
|
||||
|
||||
### Extract Class Sequence
|
||||
1. Identify a coherent subset of fields and methods
|
||||
2. Create a new class
|
||||
3. Create an instance from the old class
|
||||
4. Move fields and methods one at a time
|
||||
5. Update references in old class
|
||||
6. Test after each move
|
||||
|
||||
### Inline Class Sequence
|
||||
1. Identify all callers of the target class
|
||||
2. Move all methods/fields to the absorbing class
|
||||
3. Redirect all references to the absorbing class
|
||||
4. Delete the empty class
|
||||
5. Test
|
||||
|
||||
---
|
||||
|
||||
## Safety Protocol
|
||||
|
||||
### Before You Touch Anything
|
||||
```
|
||||
1. Characterization tests → capture what the code does now
|
||||
2. Git commit → save a known-good state
|
||||
3. Branch → isolate refactoring from other work
|
||||
```
|
||||
|
||||
### Every Single Step
|
||||
```
|
||||
1. One change → one refactoring technique
|
||||
2. Compile → must compile clean
|
||||
3. Tests → every test must pass
|
||||
4. Commit → message: "refactor: <technique> <what>"
|
||||
```
|
||||
|
||||
### If Tests Break
|
||||
```
|
||||
1. Undo the last change
|
||||
2. Understand what broke and why
|
||||
3. Try a smaller step
|
||||
4. If the test was wrong and behavior was correct, fix the test FIRST, then retry
|
||||
```
|
||||
|
||||
### On Completion
|
||||
```
|
||||
1. Full test suite → all tests pass
|
||||
2. Manual smoke test → quick sanity check
|
||||
3. Self-review diff → catch unintended changes
|
||||
4. Final commit → describe the overall transformation
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Design Patterns in Refactoring
|
||||
|
||||
### Strategy Pattern
|
||||
Replace a conditional that chooses an algorithm. **Smell:** Switch on type code with different behavior per branch. **Technique:** Replace Conditional with Polymorphism + Extract Method.
|
||||
|
||||
### Template Method
|
||||
Extract common algorithm skeleton to superclass, letting subclasses fill in the variants. **Smell:** Duplicate code with slight variations. **Technique:** Form Template Method.
|
||||
|
||||
### State Pattern
|
||||
Replace a state-based conditional by extracting each state's behavior into a class. **Smell:** Switch on status field with behavior variation. **Technique:** Replace Type Code with State/Strategy.
|
||||
|
||||
### Composite Pattern
|
||||
Treat individual objects and groups uniformly. **Smell:** Client code has special handling for single vs. collection cases. **Technique:** Extract Interface + Create Composite.
|
||||
|
||||
### Decorator Pattern
|
||||
Add behavior dynamically by wrapping objects. **Smell:** Conditional logic for optional behaviors. **Technique:** Extract Class + use composition.
|
||||
|
||||
### Null Object Pattern
|
||||
Replace null checks with a default object. **Smell:** Repeated `if (x == null)` checks. **Technique:** Introduce Null Object.
|
||||
|
||||
---
|
||||
|
||||
## Edge Cases & Gotchas
|
||||
|
||||
| Scenario | Handling |
|
||||
|----------|----------|
|
||||
| No tests exist | Write characterization tests first. Run the code with various inputs, capture outputs. These are your safety net. |
|
||||
| Refactoring breaks a distant test | FIRST understand why. Maybe the test relied on implementation detail. If so, fix the test to test behavior, not implementation. Then resume. |
|
||||
| User wants behavior change + refactor together | REFUSE. Do them separately. Refactor first to make the behavior change easy, commit, then change behavior. |
|
||||
| Method is too complex to step through | Use Replace Method with Method Object. Turn the whole method into a class where each step can be extracted. |
|
||||
| Refactoring across a large codebase | Extract a micro-service or module boundary first. Then refactor within the boundary. "There is a refactoring for everything except too many refactorings." |
|
||||
| IDE automated refactoring available | Use it. Modern IDEs can safely rename, extract method, introduce variable, etc. Only do it manually when the IDE can't. |
|
||||
| Undo needed | `git stash` or `git reset --hard` back to last commit. Small commits make this painless. |
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
- Martin Fowler, *Refactoring: Improving the Design of Existing Code* (2nd Edition, 2018)
|
||||
- [refactoring.com](https://refactoring.com) — Fowler's online catalog
|
||||
- [refactoring.guru](https://refactoring.guru) — Illustrated refactoring patterns
|
||||
- [refactoring.guru/refactoring/catalog](https://refactoring.guru/refactoring/catalog) — full technique catalog (6 categories, 66 techniques) and code-smell taxonomy this skill mirrors
|
||||
Reference in New Issue
Block a user