refactoring
Safely improve software internal design and maintainability without altering external behavior or breaking API contracts. Use when applying Martin Fowler refactoring patterns (Extract Function, Replac
Files1
View on GitHubSKILL.mdskills/refactoring/SKILL.md
GitHubSafe Code Refactoring
Refactoring is the disciplined process of restructuring existing code—improving its readability, modularity, and internal architecture—without changing its external observable behavior. Safe refactoring requires a reliable test suite, small incremental steps, continuous verification, and strict preservation of public API contracts.
Core Rules
- Never refactor without a passing test suite: Ensure all tests are green before starting. If tests are missing, write characterization tests first.
- Never change behavior and refactor at the same time: Separate structural changes from behavioral fixes or new features into distinct git commits.
- Take small, incremental steps: Make one structural transformation at a time, run the test suite, and commit when green.
- Preserve public API contracts: If renaming or restructuring a public function, leave a deprecated backward-compatible wrapper or alias during the transition.
- Listen to code smells: Refactor in response to concrete code smells (Long Function, Large Class, Feature Envy, Primitive Obsession, Duplicated Code).
1. Essential Refactoring Patterns
Pattern 1: Extract Function / Method
Turn a chunk of code within a long function that can be grouped together into its own function whose name explains its purpose:
# BEFORE: Monolithic function with mixed abstraction levels
def print_owing(invoice):
print_banner()
# Calculate outstanding
outstanding = 0.0
for order in invoice.orders:
outstanding += order.amount
# Print details
print(f"Customer: {invoice.customer}")
print(f"Amount: {outstanding}")
# AFTER: Extracted dedicated functions
def print_owing(invoice):
print_banner()
outstanding = calculate_outstanding(invoice.orders)
print_details(invoice.customer, outstanding)
def calculate_outstanding(orders: list[Order]) -> float:
return sum(order.amount for order in orders)
def print_details(customer: str, outstanding: float) -> None:
print(f"Customer: {customer}")
print(f"Amount: {outstanding}")
Pattern 2: Replace Conditional with Polymorphism
Eliminate brittle switch or if/elif/else ladders that check type codes:
# BEFORE: Fragile type check branching
class Employee:
def get_pay(self):
if self.type == "ENGINEER":
return self.base_salary
elif self.type == "SALES":
return self.base_salary + self.commission
elif self.type == "MANAGER":
return self.base_salary + self.bonus
# AFTER: Polymorphic strategy / subclassing
class Employee(Protocol):
def get_pay(self) -> float: ...
class Engineer:
def __init__(self, base_salary: float):
self.base_salary = base_salary
def get_pay(self) -> float:
return self.base_salary
class SalesPerson:
def __init__(self, base_salary: float, commission: float):
self.base_salary = base_salary
self.commission = commission
def get_pay(self) -> float:
return self.base_salary + self.commission
Pattern 3: Introduce Parameter Object (Preserving Primitive Obsession)
When groups of parameters naturally travel together:
# BEFORE: 6 separate parameters passed around
def search_entries(query: str, start_date: str, end_date: str, max_results: int, offset: int, sort_order: str): ...
# AFTER: Cohesive value object
from dataclasses import dataclass
@dataclass(frozen=True)
class SearchFilter:
query: str
date_range: tuple[str, str]
limit: int = 50
offset: int = 0
sort_order: str = "desc"
def search_entries(filter_params: SearchFilter): ...
2. The Refactoring Workflow
1. Verify Green: Run existing test suite (`pytest`, `cargo test`). Must be 100% passing.
│
▼
2. Small Refactoring Step: Apply a single mechanical change (e.g. Extract Function).
│
▼
3. Run Tests Immediately: Verify tests remain 100% green.
│
├──> [Tests Failed] ──> Revert immediately (`git restore .`), do not debug.
│
▼ [Tests Passed]
4. Git Commit: `refactor(core): extract calculate_outstanding helper`
│
▼
5. Repeat: Take next small step.
3. Common Mistakes and Anti-Patterns
| Anti-Pattern | Risk | Correct Practice |
|---|---|---|
| Mixing refactoring with new feature work | Makes diffs impossible to review; hard to bisect regressions | Separate commits: one for refactor, one for feature |
| "Big Bang" refactoring across 30 files | Test suite breaks, changes cannot be merged | Incremental commits: keep master branch shippable |
| Breaking public API signatures | Breaks downstream client code | Use deprecation wrappers or optional parameters |
| Refactoring without test coverage | Unnoticed behavioral regressions | Write characterization tests first |
4. Verification Checklist
- All tests passed before refactoring began.
- No changes to public API function signatures without backward-compatible shims.
- All tests pass after every refactoring step.
- Git commit history shows clean, focused
refactor(...)commits. - Code metrics (cyclomatic complexity, line count) improved.