APPENDIX 3: REFACTORING WITH VDD
Best quote regarding refactoring the code is:
“Refactoring is like brushing your teeth. You should do it daily not monthly.”
Sometimes requirements change and we might need bigger refactors, but most of the time refactors are something code begs to have, almost as if code is pregnant to a new abstraction or new design pattern.
THE VDD REFACTORING PHILOSOPHY
In Vibe-Driven Development, refactoring isn’t about making code “clean” according to some abstract ideal. It’s about recognizing when code is telling you it wants to evolve, and guiding that evolution with AI assistance while maintaining system stability.
RECOGNIZING REFACTORING OPPORTUNITIES
Signs Code is Ready to Evolve
Look at the codebase and identify these patterns:
1. Repeated code blocks with slight variations
→ Wants to become: Abstracted function/class
2. Long parameter lists being passed around
→ Wants to become: Configuration object or context
3. Multiple if/else checking the same condition
→ Wants to become: Strategy pattern or polymorphism
4. Data and functions always traveling together
→ Wants to become: Class or module
5. Comments explaining what code does (not why)
→ Wants to become: Self-documenting code
6. Test setup code duplicated across files
→ Wants to become: Test utilities or fixtures
Document findings in devdocs/refactoring/opportunities.md
PHASE 1: REFACTORING DISCOVERY
Identifying Natural Abstractions
Analyze the codebase for emerging patterns:
1. Find all instances where similar code appears 3+ times
2. Identify data structures that always appear together
3. Look for functions that always call each other in sequence
4. Find switch statements or if/else chains on the same variable
Create devdocs/refactoring/emerging_patterns.md documenting:
- Pattern name
- Current locations (file:line)
- Proposed abstraction
- Estimated impact (files affected)
- Risk level (low/medium/high)
Code Smell Detection
Scan for these specific code smells:
Technical Debt Smells:
- God functions (>50 lines)
- Deep nesting (>3 levels)
- Magic numbers without constants
- Dead code (unreachable/unused)
- Circular dependencies
Architectural Smells:
- Business logic in UI/controllers
- Data access scattered everywhere
- No clear module boundaries
- Inconsistent error handling
- Mixed abstraction levels
Document in devdocs/refactoring/code_smells.md with:
- Smell type and severity
- Specific locations
- Suggested remedy
- Priority for fixing
PHASE 2: SAFE REFACTORING PLANNING
Creating Refactoring Anchor Points
Before refactoring, establish safety nets:
1. Create probe tests for current behavior:
- Capture existing functionality
- Document edge cases
- Record performance baselines
2. Generate comprehensive tests:
Create probe_tests/pre_refactor/
- test_current_behavior.py
- test_edge_cases.py
- test_integration_points.py
3. Document current architecture:
Create devdocs/refactoring/current_state.md
- How it works now
- Why it works this way
- Known limitations
These anchor points ensure refactoring doesn't break existing functionality.
Incremental Refactoring Plan
Create a step-by-step refactoring plan:
devdocs/refactoring/refactor_plan.md:
Phase 1: Preparation
□ Add tests for current behavior
□ Create feature flags for gradual rollout
□ Set up parallel implementations
Phase 2: Extract (Don't Change)
□ Extract methods without changing logic
□ Create new files/modules
□ Keep old code working
Phase 3: Abstract (Don't Break)
□ Introduce abstractions
□ Route through new code paths
□ Maintain backward compatibility
Phase 4: Migrate (Gradual Switch)
□ Switch features one by one
□ Monitor for issues
□ Keep rollback ready
Phase 5: Cleanup (Remove Old)
□ Delete old implementations
□ Remove feature flags
□ Update documentation
PHASE 3: REFACTORING EXECUTION
Extract Method Refactoring
For long functions, extract logical chunks:
1. Identify a cohesive block of code (5-15 lines)
2. Check what variables it needs (parameters)
3. Check what it produces (return values)
4. Extract to new method with descriptive name
Apply this extraction:
- Original: [file:lines]
- Extract lines [X-Y] to method [method_name]
- Parameters needed: [list]
- Returns: [type]
After extraction:
- Run tests to verify behavior unchanged
- Commit with message: "Extract [method_name] from [original_function]"
Introduce Abstraction Refactoring
When you find repeated patterns:
1. Identify the common structure
2. Find what varies between instances
3. Create abstraction that captures commonality
4. Make variations into parameters/strategies
Create new abstraction:
File: [new_file_path]
Class/Interface: [name]
Common behavior: [what stays same]
Variable behavior: [what changes]
Implementation strategy: [how to handle variations]
Refactor each instance to use new abstraction
Test after each instance is refactored
Data Structure Consolidation
When data always travels together:
Identify coupled data:
- Fields: [field1, field2, field3]
- Always appear in functions: [func1, func2]
- Current locations: [scattered across X files]
Create consolidated structure:
- New class/type: [StructureName]
- Properties: [organized fields]
- Methods: [related operations]
- File location: [where it lives]
Migration approach:
1. Create new structure alongside old
2. Update one function at a time
3. Run tests after each update
4. Remove old scattered fields
PHASE 4: ARCHITECTURAL REFACTORING
Repository Pattern Introduction
When database access is scattered:
Current state analysis:
- Find all database queries in codebase
- Group by entity type (User, Order, Product)
- Identify common query patterns
Create repository structure:
repositories/
├── base_repository.py # Common CRUD operations
├── user_repository.py # User-specific queries
├── order_repository.py # Order-specific queries
└── product_repository.py # Product-specific queries
Migration steps:
1. Create repository with existing queries (copy, don't move)
2. Update one module to use repository
3. Test thoroughly
4. Repeat for each module
5. Remove direct database access
Service Layer Extraction
When business logic is mixed with infrastructure:
Identify business logic:
- Find code that implements business rules
- Separate from HTTP handling, database access
- Group related business operations
Create service layer:
services/
├── user_service.py # User business logic
├── order_service.py # Order processing
├── payment_service.py # Payment rules
└── notification_service.py # Notification logic
Refactoring approach:
1. Extract business logic to service methods
2. Keep controllers thin (just HTTP handling)
3. Services call repositories (not direct DB)
4. Test services independently
Dependency Injection Implementation
When testing is hard due to tight coupling:
Identify hard dependencies:
- Direct instantiation in constructors
- Global imports used directly
- Hard-coded configuration values
Introduce dependency injection:
# Before:
class OrderService:
def __init__(self):
self.db = Database() # Hard dependency
self.email = EmailSender() # Hard dependency
# After:
class OrderService:
def __init__(self, db, email_sender):
self.db = db # Injected
self.email = email_sender # Injected
Create dependency container:
- Central place for wiring
- Configuration-driven
- Easy to swap implementations
PHASE 5: REFACTORING VALIDATION
Performance Comparison
Compare before/after performance:
Create performance tests:
1. Measure current performance
- Response times
- Memory usage
- Database queries
2. Run same tests after refactoring
- Should be same or better
- Document any degradation
- Optimize if needed
Document in devdocs/refactoring/performance_impact.md:
- Metrics before/after
- Explanation of changes
- Optimization opportunities
Behavior Verification
Ensure refactoring didn't change behavior:
1. Run all existing tests
- Should pass without changes
- If test needs updating, document why
2. Run probe tests
- Compare output before/after
- Check edge cases still work
- Verify error handling unchanged
3. Manual testing checklist
- Critical user paths
- Integration points
- Error scenarios
Document any behavior changes (there shouldn't be any!)
PHASE 6: CONTINUOUS REFACTORING
Daily Refactoring Habits
Make these part of your daily workflow:
When adding new code:
□ Extract if function > 30 lines
□ Name magic numbers as constants
□ Group related parameters into objects
□ Add types/interfaces where missing
When modifying existing code:
□ Leave it better than you found it
□ Extract repeated code you encounter
□ Improve names for clarity
□ Add missing tests
When reviewing code:
□ Identify emerging patterns
□ Note refactoring opportunities
□ Create tickets for larger refactors
Refactoring During Feature Development
Integrate refactoring into feature work:
Before starting feature:
1. Refactor the area you'll be working in
2. Clean up technical debt that would slow you down
3. Add tests for code you'll modify
During feature development:
1. Extract new abstractions as they emerge
2. Don't copy-paste, extract shared code
3. Keep consistent patterns
After feature completion:
1. Refactor based on new understanding
2. Consolidate similar code introduced
3. Update module documentation
COMMON REFACTORING PATTERNS WITH AI
Pattern 1: Extract and Generalize
When you have similar code with variations:
Prompt: "I have these 3 similar functions [paste code].
Extract the common logic into a base function and handle
variations through parameters or callbacks."
AI will:
- Identify commonality
- Create generalized version
- Show how to use for each case
Pattern 2: Simplify Conditional Logic
When you have complex nested conditions:
Prompt: "Simplify this complex conditional logic [paste code].
Use early returns, extract methods, or strategy pattern as appropriate."
AI will:
- Flatten nested conditions
- Extract boolean methods
- Suggest clearer structure
Pattern 3: Introduce Design Pattern
When code is begging for a pattern:
Prompt: "This code [paste code] seems to need a design pattern.
Identify which pattern would help and refactor to use it."
AI will:
- Recognize applicable patterns
- Implement the pattern
- Show migration path
REFACTORING SAFETY RULES
The Golden Rules
- One Thing at a Time - Never refactor multiple things simultaneously
- Test Continuously - Run tests after every small change
- Commit Frequently - Each successful refactor gets its own commit
- Keep It Working - Code should work at every step
- Document Why - Commit messages explain the refactoring reason
When NOT to Refactor
- Before a deadline - Refactoring can wait
- Without tests - Add tests first
- During debugging - Fix the bug first
- Unfamiliar code - Understand it first
- If it’s working - Don’t fix what ain’t broken (unless it’s blocking progress)
REFACTORING CHECKLIST
Before starting any refactoring:
- Current behavior is tested
- Performance baseline captured
- Rollback plan exists
- Team is informed
During refactoring:
- One change at a time
- Tests pass after each change
- Commits are atomic
- Code always compiles
After refactoring:
- All tests still pass
- Performance unchanged or better
- Documentation updated
- Team code review completed
- Old code removed
TIPS FOR AI-ASSISTED REFACTORING
- Show Context - Give AI the full picture, not just the code to refactor
- Specify Constraints - Tell AI what must not change
- Request Steps - Ask for incremental refactoring steps
- Verify Understanding - Have AI explain the refactoring before doing it
- Test Generation - Ask AI to create tests before and after
Remember: Refactoring is about making code better without changing what it does. It’s evolution, not revolution. The best refactoring is invisible to users but obvious to developers.