Refactoring Protocol
This skill enforces disciplined refactoring practices to improve code quality without breaking functionality.
The Prime Directive
"Working Code is Sacred"
- •NEVER refactor without tests verifying current behavior
- •NEVER refactor and add features simultaneously
- •NEVER proceed if tests fail after refactoring
- •If stuck, REVERT IMMEDIATELY (do not stack fixes on broken code)
Phase 1: Assessment & Planning
Step 1.1: Identify Refactoring Need
Common triggers:
- •God Object: File > 600 lines
- •Duplicate Code: Same logic in multiple places
- •Architectural Violation: SQL in UI, business logic in repository, etc.
- •Poor Naming: Unclear variable/method names
- •Deep Nesting: If/else/for blocks > 3 levels deep
- •Long Methods: Functions > 50 lines
- •Feature Envy: Class A uses Class B's data more than its own
Step 1.2: Capture Current Behavior
- •Run Existing Tests: Ensure all tests pass BEFORE starting
bash
python tools/run_tests.py
- •Document Current Behavior: If no tests exist, write them first
- •Create Reproduction Script if needed (for complex scenarios)
Step 1.3: Plan the Refactoring
- •Define the Goal: What will be better after refactoring?
- •Identify Risk Areas: What could break?
- •Plan Small Steps: Break into incremental changes
- •Present Plan to User: Get approval before starting
Phase 2: Execution (The Refactoring Steps)
General Principles
- •One Change at a Time: Rename OR extract OR move, not all three
- •Keep Tests Green: Run tests after each step
- •Commit Each Step: Small, atomic commits (though user controls when)
Common Refactoring Patterns
Pattern A: Extract Method
When: Method > 50 lines or does multiple things
Steps:
- •Identify block of code to extract
- •Determine required parameters and return value
- •Create new method with descriptive name
- •Move code to new method
- •Replace original code with method call
- •Run tests
Example:
# BEFORE
def process_song(self, file_path: str):
# 20 lines of file validation
if not os.path.exists(file_path):
raise FileNotFoundError(...)
if not file_path.endswith('.mp3'):
raise ValueError(...)
# ... more validation
# 30 lines of metadata extraction
audio = MP3(file_path)
title = audio.get('TIT2')
# ... more extraction
# AFTER
def process_song(self, file_path: str):
self._validate_file(file_path)
metadata = self._extract_metadata(file_path)
# ...
def _validate_file(self, file_path: str):
# Validation logic here
def _extract_metadata(self, file_path: str) -> dict:
# Extraction logic here
Pattern B: Extract Class
When: Class > 600 lines or has multiple responsibilities
Steps:
- •Identify cohesive group of methods/attributes
- •Create new class
- •Move methods/attributes to new class
- •Update original class to delegate to new class
- •Update all references
- •Run tests
Example:
# BEFORE
class LibraryWidget: # 800 lines
def filter_by_artist(self): ...
def filter_by_genre(self): ...
def apply_complex_filter(self): ...
# ... 20 more filter methods
# ... plus 30 other widget methods
# AFTER
class LibraryWidget: # 400 lines
def __init__(self):
self._filter_manager = FilterManager()
def apply_filter(self, filter_spec):
self._filter_manager.apply(filter_spec)
class FilterManager: # New class, 300 lines
def filter_by_artist(self): ...
def filter_by_genre(self): ...
def apply_complex_filter(self): ...
Pattern C: Move Method (Layer Correction)
When: Method is in wrong architectural layer
Steps:
- •Create method in correct layer
- •Update original method to delegate (temporarily)
- •Update all callers to use new location
- •Delete old method
- •Run tests
Example:
# BEFORE - SQL in UI layer (VIOLATION)
class LibraryWidget:
def load_songs(self):
cursor.execute("SELECT * FROM songs") # WRONG LAYER!
# AFTER - SQL in Repository
class SongRepository:
def get_all(self) -> List[Song]:
cursor.execute("SELECT * FROM songs") # CORRECT LAYER
return [self._row_to_song(row) for row in cursor.fetchall()]
class LibraryService:
def get_all_songs(self) -> List[Song]:
return self._song_repo.get_all()
class LibraryWidget:
def load_songs(self):
songs = self._library_service.get_all_songs() # CORRECT
Pattern D: Rename (Clarity Improvement)
When: Names are unclear or misleading
Steps:
- •Use IDE's rename refactoring OR grep to find all usages
- •Rename in all locations
- •Run tests
- •Update related documentation
Example:
# BEFORE
def proc(self, d): # Unclear
return d.get('val')
# AFTER
def extract_title(self, metadata: dict) -> str: # Clear
return metadata.get('title')
Pattern E: Introduce Parameter Object
When: Function has > 4 parameters
Steps:
- •Create dataclass for parameters
- •Update function signature
- •Update all call sites
- •Run tests
Example:
# BEFORE
def create_song(self, title, artist, year, genre, bpm, isrc):
pass
# AFTER
@dataclass
class SongCreationData:
title: str
artist: str
year: Optional[int] = None
genre: Optional[str] = None
bpm: Optional[int] = None
isrc: Optional[str] = None
def create_song(self, data: SongCreationData):
pass
Phase 3: Verification
Step 3.1: Test Suite Validation
# Run full test suite python tools/run_tests.py # If any tests fail: # 1. DO NOT PROCEED # 2. Analyze failure # 3. If it's a test issue, fix test # 4. If it's a refactoring issue, REVERT
Step 3.2: Manual Smoke Testing
- •Test Main Workflows: Import song, play song, edit metadata, etc.
- •Test Edge Cases: Empty library, invalid files, cancellations
- •Visual Inspection: UI looks correct, no layout issues
Step 3.3: Performance Check
- •Compare Before/After: Did refactoring affect performance?
- •Profile Hot Paths: Use cProfile if concerned
- •If Slower: Consider optimizing or reverting
Phase 4: The Hard Revert Rule
When to Revert
- •Tests fail and root cause is unclear
- •Refactoring is taking too long (> 1 hour)
- •Discovering unexpected dependencies
- •User requests to stop
How to Revert
# Discard all changes git checkout . # Or revert specific file git checkout -- path/to/file.py
After Reverting
- •Analyze What Went Wrong: Why did it fail?
- •Plan Smaller Steps: Break into even smaller increments
- •Ask User: Should we try a different approach?
Phase 5: Completion
Step 5.1: Documentation Updates
- •Update comments if method signatures changed
- •Update ARCHITECTURE.md if patterns changed
- •Update docstrings if behavior clarified
Step 5.2: Checkpoint Reminder
- •Remind User to Commit:
"Refactoring complete and verified. Tests pass. Please commit when ready."
- •Suggest Commit Message:
code
refactor(domain): Brief description - Extract FilterManager from LibraryWidget (800 -> 400 lines) - Move SQL queries from UI to SongRepository - No functional changes Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Refactoring Safety Checklist
Before starting:
- •✅ All tests pass
- •✅ User approves refactoring plan
- •✅ Architectural goal is clear
During refactoring:
- •✅ Making one type of change at a time
- •✅ Tests pass after each step
- •✅ Not adding features simultaneously
After completion:
- •✅ All tests still pass
- •✅ No new warnings or errors
- •✅ Code is clearer/cleaner than before
- •✅ No performance regression
Common Refactoring Anti-Patterns
❌ Big Bang Refactoring
Don't refactor entire codebase at once. Small, incremental changes.
❌ Refactoring Without Tests
Always have tests verifying current behavior first.
❌ Refactoring + Feature Addition
Never mix refactoring with new functionality. Refactor first, then add feature.
❌ Premature Abstraction
Don't create abstractions until you have 3+ similar cases.
❌ Ignoring Test Failures
If tests fail, STOP. Don't continue with "I'll fix it later."
❌ Silent Behavior Changes
Refactoring should NOT change behavior. If it does, it's not refactoring.
Example Walkthrough
Scenario: LibraryService is 850 lines and growing
Phase 1: Assessment
- •Identify: God Object (> 600 lines)
- •Current behavior: All tests pass
- •Plan: Extract playlist-related methods to
PlaylistService
Phase 2: Execution
- •Create
src/business/services/playlist_service.py - •Move
create_playlist()method → Test - •Move
add_to_playlist()method → Test - •Move
remove_from_playlist()method → Test - •Update
LibraryServiceto delegate toPlaylistService - •Update all UI callers
Phase 3: Verification
- •Run full test suite → All pass
- •Manual test: Create playlist, add songs → Works
- •Check file sizes:
LibraryServicenow 620 lines,PlaylistService180 lines
Phase 4: Completion
- •Remind user to commit
- •Suggest: "refactor(services): Extract PlaylistService from LibraryService"