563 lines
13 KiB
Markdown
563 lines
13 KiB
Markdown
<!-- Powered by BMAD™ Core -->
|
|
|
|
# TDD Refactor Phase Prompts
|
|
|
|
Instructions for Dev and QA agents when refactoring code while maintaining green tests in Test-Driven Development.
|
|
|
|
## Core Refactor Phase Mindset
|
|
|
|
**You are in TDD REFACTOR PHASE. Your mission is to improve code quality while keeping ALL tests green. Every change must preserve existing behavior.**
|
|
|
|
### Primary Objectives
|
|
|
|
1. **Preserve behavior** - External behavior must remain exactly the same
|
|
2. **Improve design** - Make code more readable, maintainable, and extensible
|
|
3. **Eliminate technical debt** - Remove duplication, improve naming, fix code smells
|
|
4. **Maintain test coverage** - All tests must stay green throughout
|
|
5. **Small steps** - Make incremental improvements with frequent test runs
|
|
|
|
## Refactoring Safety Rules
|
|
|
|
### The Golden Rule
|
|
|
|
**NEVER proceed with a refactoring step if tests are red.** Always revert and try smaller changes.
|
|
|
|
### Safe Refactoring Workflow
|
|
|
|
```yaml
|
|
refactoring_cycle:
|
|
1. identify_smell: 'Find specific code smell to address'
|
|
2. plan_change: 'Decide on minimal improvement step'
|
|
3. run_tests: 'Ensure all tests are green before starting'
|
|
4. make_change: 'Apply single, small refactoring'
|
|
5. run_tests: 'Verify tests are still green'
|
|
6. commit: 'Save progress if tests pass'
|
|
7. repeat: 'Move to next improvement'
|
|
|
|
abort_conditions:
|
|
- tests_turn_red: 'Immediately revert and try smaller step'
|
|
- behavior_changes: 'Revert if external interface changes'
|
|
- complexity_increases: 'Revert if code becomes harder to understand'
|
|
```
|
|
|
|
## Code Smells and Refactoring Techniques
|
|
|
|
### Duplication Elimination
|
|
|
|
**Before: Repeated validation logic**
|
|
|
|
```javascript
|
|
function createUser(data) {
|
|
if (!data.email.includes('@')) {
|
|
throw new Error('Invalid email format');
|
|
}
|
|
return { id: generateId(), ...data };
|
|
}
|
|
|
|
function updateUser(id, data) {
|
|
if (!data.email.includes('@')) {
|
|
throw new Error('Invalid email format');
|
|
}
|
|
return { id, ...data };
|
|
}
|
|
```
|
|
|
|
**After: Extract validation function**
|
|
|
|
```javascript
|
|
function validateEmail(email) {
|
|
if (!email.includes('@')) {
|
|
throw new Error('Invalid email format');
|
|
}
|
|
}
|
|
|
|
function createUser(data) {
|
|
validateEmail(data.email);
|
|
return { id: generateId(), ...data };
|
|
}
|
|
|
|
function updateUser(id, data) {
|
|
validateEmail(data.email);
|
|
return { id, ...data };
|
|
}
|
|
```
|
|
|
|
### Long Method Refactoring
|
|
|
|
**Before: Method doing too much**
|
|
|
|
```javascript
|
|
function processUserRegistration(userData) {
|
|
// Validation (5 lines)
|
|
if (!userData.email.includes('@')) throw new Error('Invalid email');
|
|
if (!userData.name || userData.name.trim().length === 0) throw new Error('Name required');
|
|
if (userData.age < 18) throw new Error('Must be 18 or older');
|
|
|
|
// Data transformation (4 lines)
|
|
const user = {
|
|
id: generateId(),
|
|
email: userData.email.toLowerCase(),
|
|
name: userData.name.trim(),
|
|
age: userData.age,
|
|
};
|
|
|
|
// Business logic (3 lines)
|
|
if (userData.age >= 65) {
|
|
user.discountEligible = true;
|
|
}
|
|
|
|
return user;
|
|
}
|
|
```
|
|
|
|
**After: Extract methods**
|
|
|
|
```javascript
|
|
function validateUserData(userData) {
|
|
if (!userData.email.includes('@')) throw new Error('Invalid email');
|
|
if (!userData.name || userData.name.trim().length === 0) throw new Error('Name required');
|
|
if (userData.age < 18) throw new Error('Must be 18 or older');
|
|
}
|
|
|
|
function normalizeUserData(userData) {
|
|
return {
|
|
id: generateId(),
|
|
email: userData.email.toLowerCase(),
|
|
name: userData.name.trim(),
|
|
age: userData.age,
|
|
};
|
|
}
|
|
|
|
function applyBusinessRules(user) {
|
|
if (user.age >= 65) {
|
|
user.discountEligible = true;
|
|
}
|
|
return user;
|
|
}
|
|
|
|
function processUserRegistration(userData) {
|
|
validateUserData(userData);
|
|
const user = normalizeUserData(userData);
|
|
return applyBusinessRules(user);
|
|
}
|
|
```
|
|
|
|
### Magic Numbers and Constants
|
|
|
|
**Before: Magic numbers scattered**
|
|
|
|
```javascript
|
|
function calculateShipping(weight) {
|
|
if (weight < 5) {
|
|
return 4.99;
|
|
} else if (weight < 20) {
|
|
return 9.99;
|
|
} else {
|
|
return 19.99;
|
|
}
|
|
}
|
|
```
|
|
|
|
**After: Named constants**
|
|
|
|
```javascript
|
|
const SHIPPING_RATES = {
|
|
LIGHT_WEIGHT_THRESHOLD: 5,
|
|
MEDIUM_WEIGHT_THRESHOLD: 20,
|
|
LIGHT_SHIPPING_COST: 4.99,
|
|
MEDIUM_SHIPPING_COST: 9.99,
|
|
HEAVY_SHIPPING_COST: 19.99,
|
|
};
|
|
|
|
function calculateShipping(weight) {
|
|
if (weight < SHIPPING_RATES.LIGHT_WEIGHT_THRESHOLD) {
|
|
return SHIPPING_RATES.LIGHT_SHIPPING_COST;
|
|
} else if (weight < SHIPPING_RATES.MEDIUM_WEIGHT_THRESHOLD) {
|
|
return SHIPPING_RATES.MEDIUM_SHIPPING_COST;
|
|
} else {
|
|
return SHIPPING_RATES.HEAVY_SHIPPING_COST;
|
|
}
|
|
}
|
|
```
|
|
|
|
### Variable Naming Improvements
|
|
|
|
**Before: Unclear names**
|
|
|
|
```javascript
|
|
function calc(u, p) {
|
|
const t = u * p;
|
|
const d = t * 0.1;
|
|
return t - d;
|
|
}
|
|
```
|
|
|
|
**After: Intention-revealing names**
|
|
|
|
```javascript
|
|
function calculateNetPrice(unitPrice, quantity) {
|
|
const totalPrice = unitPrice * quantity;
|
|
const discount = totalPrice * 0.1;
|
|
return totalPrice - discount;
|
|
}
|
|
```
|
|
|
|
## Refactoring Strategies by Code Smell
|
|
|
|
### Complex Conditionals
|
|
|
|
**Before: Nested conditions**
|
|
|
|
```javascript
|
|
function determineUserType(user) {
|
|
if (user.age >= 18) {
|
|
if (user.hasAccount) {
|
|
if (user.isPremium) {
|
|
return 'premium-member';
|
|
} else {
|
|
return 'basic-member';
|
|
}
|
|
} else {
|
|
return 'guest-adult';
|
|
}
|
|
} else {
|
|
return 'minor';
|
|
}
|
|
}
|
|
```
|
|
|
|
**After: Guard clauses and early returns**
|
|
|
|
```javascript
|
|
function determineUserType(user) {
|
|
if (user.age < 18) {
|
|
return 'minor';
|
|
}
|
|
|
|
if (!user.hasAccount) {
|
|
return 'guest-adult';
|
|
}
|
|
|
|
return user.isPremium ? 'premium-member' : 'basic-member';
|
|
}
|
|
```
|
|
|
|
### Large Classes (God Object)
|
|
|
|
**Before: Class doing too much**
|
|
|
|
```javascript
|
|
class UserManager {
|
|
validateUser(data) {
|
|
/* validation logic */
|
|
}
|
|
createUser(data) {
|
|
/* creation logic */
|
|
}
|
|
sendWelcomeEmail(user) {
|
|
/* email logic */
|
|
}
|
|
logUserActivity(user, action) {
|
|
/* logging logic */
|
|
}
|
|
calculateUserStats(user) {
|
|
/* analytics logic */
|
|
}
|
|
}
|
|
```
|
|
|
|
**After: Single responsibility classes**
|
|
|
|
```javascript
|
|
class UserValidator {
|
|
validate(data) {
|
|
/* validation logic */
|
|
}
|
|
}
|
|
|
|
class UserService {
|
|
create(data) {
|
|
/* creation logic */
|
|
}
|
|
}
|
|
|
|
class EmailService {
|
|
sendWelcome(user) {
|
|
/* email logic */
|
|
}
|
|
}
|
|
|
|
class ActivityLogger {
|
|
log(user, action) {
|
|
/* logging logic */
|
|
}
|
|
}
|
|
|
|
class UserAnalytics {
|
|
calculateStats(user) {
|
|
/* analytics logic */
|
|
}
|
|
}
|
|
```
|
|
|
|
## Collaborative Refactoring (Dev + QA)
|
|
|
|
### When to Involve QA Agent
|
|
|
|
**QA Agent should participate when:**
|
|
|
|
```yaml
|
|
qa_involvement_triggers:
|
|
test_modification_needed:
|
|
- 'Test expectations need updating'
|
|
- 'New test cases discovered during refactoring'
|
|
- 'Mock strategies need adjustment'
|
|
|
|
coverage_assessment:
|
|
- 'Refactoring exposes untested code paths'
|
|
- 'New methods need test coverage'
|
|
- 'Test organization needs improvement'
|
|
|
|
design_validation:
|
|
- 'Interface changes affect test structure'
|
|
- 'Mocking strategy becomes complex'
|
|
- 'Test maintainability concerns'
|
|
```
|
|
|
|
### Dev-QA Collaboration Workflow
|
|
|
|
```yaml
|
|
collaborative_steps:
|
|
1. dev_identifies_refactoring: 'Dev spots code smell'
|
|
2. assess_test_impact: 'Both agents review test implications'
|
|
3. plan_refactoring: 'Agree on approach and steps'
|
|
4. dev_refactors: 'Dev makes incremental changes'
|
|
5. qa_validates_tests: 'QA ensures tests remain valid'
|
|
6. both_review: 'Joint review of improved code and tests'
|
|
```
|
|
|
|
## Advanced Refactoring Patterns
|
|
|
|
### Extract Interface for Testability
|
|
|
|
**Before: Hard to test due to dependencies**
|
|
|
|
```javascript
|
|
class OrderService {
|
|
constructor() {
|
|
this.emailSender = new EmailSender();
|
|
this.paymentProcessor = new PaymentProcessor();
|
|
}
|
|
|
|
processOrder(order) {
|
|
const result = this.paymentProcessor.charge(order.total);
|
|
this.emailSender.sendConfirmation(order.customerEmail);
|
|
return result;
|
|
}
|
|
}
|
|
```
|
|
|
|
**After: Dependency injection for testability**
|
|
|
|
```javascript
|
|
class OrderService {
|
|
constructor(emailSender, paymentProcessor) {
|
|
this.emailSender = emailSender;
|
|
this.paymentProcessor = paymentProcessor;
|
|
}
|
|
|
|
processOrder(order) {
|
|
const result = this.paymentProcessor.charge(order.total);
|
|
this.emailSender.sendConfirmation(order.customerEmail);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
// Usage in production:
|
|
const orderService = new OrderService(new EmailSender(), new PaymentProcessor());
|
|
|
|
// Usage in tests:
|
|
const mockEmail = { sendConfirmation: jest.fn() };
|
|
const mockPayment = { charge: jest.fn().mockReturnValue('success') };
|
|
const orderService = new OrderService(mockEmail, mockPayment);
|
|
```
|
|
|
|
### Replace Conditional with Polymorphism
|
|
|
|
**Before: Switch statement**
|
|
|
|
```javascript
|
|
function calculateArea(shape) {
|
|
switch (shape.type) {
|
|
case 'circle':
|
|
return Math.PI * shape.radius * shape.radius;
|
|
case 'rectangle':
|
|
return shape.width * shape.height;
|
|
case 'triangle':
|
|
return 0.5 * shape.base * shape.height;
|
|
default:
|
|
throw new Error('Unknown shape type');
|
|
}
|
|
}
|
|
```
|
|
|
|
**After: Polymorphic classes**
|
|
|
|
```javascript
|
|
class Circle {
|
|
constructor(radius) {
|
|
this.radius = radius;
|
|
}
|
|
|
|
calculateArea() {
|
|
return Math.PI * this.radius * this.radius;
|
|
}
|
|
}
|
|
|
|
class Rectangle {
|
|
constructor(width, height) {
|
|
this.width = width;
|
|
this.height = height;
|
|
}
|
|
|
|
calculateArea() {
|
|
return this.width * this.height;
|
|
}
|
|
}
|
|
|
|
class Triangle {
|
|
constructor(base, height) {
|
|
this.base = base;
|
|
this.height = height;
|
|
}
|
|
|
|
calculateArea() {
|
|
return 0.5 * this.base * this.height;
|
|
}
|
|
}
|
|
```
|
|
|
|
## Refactoring Safety Checks
|
|
|
|
### Before Each Refactoring Step
|
|
|
|
```bash
|
|
# 1. Ensure all tests are green
|
|
npm test
|
|
pytest
|
|
go test ./...
|
|
|
|
# 2. Consider impact
|
|
# - Will this change external interfaces?
|
|
# - Are there hidden dependencies?
|
|
# - Could this affect performance significantly?
|
|
|
|
# 3. Plan the smallest possible step
|
|
# - What's the minimal change that improves code?
|
|
# - Can this be broken into smaller steps?
|
|
```
|
|
|
|
### After Each Refactoring Step
|
|
|
|
```bash
|
|
# 1. Run tests immediately
|
|
npm test
|
|
|
|
# 2. If tests fail:
|
|
git checkout -- . # Revert changes
|
|
# Plan smaller refactoring step
|
|
|
|
# 3. If tests pass:
|
|
git add .
|
|
git commit -m "REFACTOR: Extract validateEmail function [maintains UC-001, UC-002]"
|
|
```
|
|
|
|
## Refactoring Anti-Patterns
|
|
|
|
### Don't Change Behavior
|
|
|
|
```javascript
|
|
// Wrong: Changing logic during refactoring
|
|
function calculateDiscount(amount) {
|
|
// Original: 10% discount
|
|
return amount * 0.1;
|
|
|
|
// Refactored: DON'T change the discount rate
|
|
return amount * 0.15; // This changes behavior!
|
|
}
|
|
|
|
// Right: Only improve structure
|
|
const DISCOUNT_RATE = 0.1; // Extract constant
|
|
function calculateDiscount(amount) {
|
|
return amount * DISCOUNT_RATE; // Same behavior
|
|
}
|
|
```
|
|
|
|
### Don't Add Features
|
|
|
|
```javascript
|
|
// Wrong: Adding features during refactoring
|
|
function validateUser(userData) {
|
|
validateEmail(userData.email); // Existing
|
|
validateName(userData.name); // Existing
|
|
validateAge(userData.age); // DON'T add new validation
|
|
}
|
|
|
|
// Right: Only improve existing code
|
|
function validateUser(userData) {
|
|
validateEmail(userData.email);
|
|
validateName(userData.name);
|
|
// Age validation needs its own failing test first
|
|
}
|
|
```
|
|
|
|
### Don't Make Large Changes
|
|
|
|
```javascript
|
|
// Wrong: Massive refactoring in one step
|
|
class UserService {
|
|
// Completely rewrite entire class structure
|
|
}
|
|
|
|
// Right: Small, incremental improvements
|
|
class UserService {
|
|
// Extract one method at a time
|
|
// Rename one variable at a time
|
|
// Improve one code smell at a time
|
|
}
|
|
```
|
|
|
|
## Refactor Phase Checklist
|
|
|
|
Before considering refactoring complete:
|
|
|
|
- [ ] **All tests remain green** - No test failures introduced
|
|
- [ ] **Code quality improved** - Measurable improvement in readability/maintainability
|
|
- [ ] **No behavior changes** - External behavior is identical
|
|
- [ ] **Technical debt reduced** - Specific code smells addressed
|
|
- [ ] **Small commits made** - Each improvement committed separately
|
|
- [ ] **Documentation updated** - Comments and docs reflect changes
|
|
- [ ] **Performance maintained** - No significant performance degradation
|
|
- [ ] **Story metadata updated** - Refactoring notes and improvements documented
|
|
|
|
## Success Indicators
|
|
|
|
**Refactoring is successful when:**
|
|
|
|
1. **All tests consistently pass** throughout the process
|
|
2. **Code is noticeably easier to read** and understand
|
|
3. **Duplication has been eliminated** or significantly reduced
|
|
4. **Method/class sizes are more reasonable** (functions < 15 lines)
|
|
5. **Variable and function names clearly express intent**
|
|
6. **Code complexity has decreased** (fewer nested conditions)
|
|
7. **Future changes will be easier** due to better structure
|
|
|
|
**Refactoring is complete when:**
|
|
|
|
- No obvious code smells remain in the story scope
|
|
- Code quality metrics show improvement
|
|
- Tests provide comprehensive safety net
|
|
- Ready for next TDD cycle or story completion
|
|
|
|
Remember: Refactoring is about improving design, not adding features. Keep tests green, make small changes, and focus on making the code better for the next developer!
|