Back to Blog
RefactoringCode QualityClean CodeEngineering

Refactoring Strategies: Improving Code Without Breaking It

Refactor code safely and effectively. Learn refactoring techniques, when to refactor, and how to do it without introducing bugs.

B
Bootspring Team
Engineering
February 27, 2026
5 min read

Refactoring improves code structure without changing behavior. This guide covers techniques for safe, effective refactoring.

When to Refactor#

Rule of Three#

1// First time: Just do it 2function calculateTaxUS(amount: number): number { 3 return amount * 0.08; 4} 5 6// Second time: Note the duplication 7function calculateTaxCA(amount: number): number { 8 return amount * 0.13; 9} 10 11// Third time: Refactor 12function calculateTax(amount: number, rate: number): number { 13 return amount * rate; 14} 15 16const TAX_RATES = { 17 US: 0.08, 18 CA: 0.13, 19 UK: 0.20, 20};

Before Adding Features#

1// Before: Hard to add new payment method 2function processPayment(type: string, amount: number) { 3 if (type === 'credit') { 4 // 50 lines of credit card logic 5 } else if (type === 'paypal') { 6 // 50 lines of PayPal logic 7 } 8 // Adding 'apple_pay' means more conditions 9} 10 11// After: Easy to extend 12interface PaymentProcessor { 13 process(amount: number): Promise<PaymentResult>; 14} 15 16class CreditCardProcessor implements PaymentProcessor { } 17class PayPalProcessor implements PaymentProcessor { } 18class ApplePayProcessor implements PaymentProcessor { } // Easy to add

Safe Refactoring Process#

1. Ensure Test Coverage#

1// Before refactoring, add tests for current behavior 2describe('calculateOrderTotal', () => { 3 it('should calculate subtotal correctly', () => { 4 const order = createOrder([ 5 { price: 10, quantity: 2 }, 6 { price: 5, quantity: 3 }, 7 ]); 8 expect(calculateOrderTotal(order).subtotal).toBe(35); 9 }); 10 11 it('should apply percentage discount', () => { 12 const order = createOrder([{ price: 100, quantity: 1 }]); 13 order.discount = { type: 'percentage', value: 10 }; 14 expect(calculateOrderTotal(order).total).toBe(90); 15 }); 16 17 // Cover all edge cases before refactoring 18});

2. Small, Incremental Changes#

1// Step 1: Extract variable 2const total = items.reduce((sum, item) => sum + item.price * item.quantity, 0) * (1 - discount / 100); 3 4// Becomes: 5const subtotal = items.reduce((sum, item) => sum + item.price * item.quantity, 0); 6const discountAmount = subtotal * (discount / 100); 7const total = subtotal - discountAmount; 8 9// Step 2: Extract function (separate commit) 10function calculateSubtotal(items: Item[]): number { 11 return items.reduce((sum, item) => sum + item.price * item.quantity, 0); 12} 13 14function applyDiscount(amount: number, discountPercent: number): number { 15 return amount * (1 - discountPercent / 100); 16} 17 18const total = applyDiscount(calculateSubtotal(items), discount);

3. Run Tests After Each Change#

# After every small change npm test # Or with watch mode npm test -- --watch

Common Refactoring Patterns#

Extract Method#

1// Before 2function printOwing() { 3 let outstanding = 0; 4 5 console.log("***********************"); 6 console.log("**** Customer Owes ****"); 7 console.log("***********************"); 8 9 for (const order of orders) { 10 outstanding += order.amount; 11 } 12 13 console.log(`name: ${name}`); 14 console.log(`amount: ${outstanding}`); 15} 16 17// After 18function printOwing() { 19 printBanner(); 20 const outstanding = calculateOutstanding(); 21 printDetails(outstanding); 22} 23 24function printBanner() { 25 console.log("***********************"); 26 console.log("**** Customer Owes ****"); 27 console.log("***********************"); 28} 29 30function calculateOutstanding(): number { 31 return orders.reduce((sum, order) => sum + order.amount, 0); 32} 33 34function printDetails(outstanding: number) { 35 console.log(`name: ${name}`); 36 console.log(`amount: ${outstanding}`); 37}

Replace Conditional with Polymorphism#

1// Before 2function calculatePay(employee: Employee): number { 3 switch (employee.type) { 4 case 'engineer': 5 return employee.salary; 6 case 'salesman': 7 return employee.salary + employee.commission; 8 case 'manager': 9 return employee.salary + employee.bonus; 10 default: 11 throw new Error('Unknown employee type'); 12 } 13} 14 15// After 16abstract class Employee { 17 constructor(protected salary: number) {} 18 abstract calculatePay(): number; 19} 20 21class Engineer extends Employee { 22 calculatePay(): number { 23 return this.salary; 24 } 25} 26 27class Salesman extends Employee { 28 constructor(salary: number, private commission: number) { 29 super(salary); 30 } 31 32 calculatePay(): number { 33 return this.salary + this.commission; 34 } 35} 36 37class Manager extends Employee { 38 constructor(salary: number, private bonus: number) { 39 super(salary); 40 } 41 42 calculatePay(): number { 43 return this.salary + this.bonus; 44 } 45}

Introduce Parameter Object#

1// Before 2function amountInvoiced(startDate: Date, endDate: Date): number { } 3function amountReceived(startDate: Date, endDate: Date): number { } 4function amountOverdue(startDate: Date, endDate: Date): number { } 5 6// After 7class DateRange { 8 constructor( 9 readonly start: Date, 10 readonly end: Date 11 ) {} 12 13 contains(date: Date): boolean { 14 return date >= this.start && date <= this.end; 15 } 16} 17 18function amountInvoiced(range: DateRange): number { } 19function amountReceived(range: DateRange): number { } 20function amountOverdue(range: DateRange): number { }

Replace Magic Numbers with Constants#

1// Before 2if (user.age >= 18 && user.creditScore > 650) { 3 if (loanAmount <= user.income * 0.3) { 4 approveLoan(); 5 } 6} 7 8// After 9const MINIMUM_AGE = 18; 10const MINIMUM_CREDIT_SCORE = 650; 11const MAX_LOAN_TO_INCOME_RATIO = 0.3; 12 13if (user.age >= MINIMUM_AGE && user.creditScore > MINIMUM_CREDIT_SCORE) { 14 const maxLoanAmount = user.income * MAX_LOAN_TO_INCOME_RATIO; 15 if (loanAmount <= maxLoanAmount) { 16 approveLoan(); 17 } 18}

Decompose Conditional#

1// Before 2if (date.before(SUMMER_START) || date.after(SUMMER_END)) { 3 charge = quantity * winterRate + winterServiceCharge; 4} else { 5 charge = quantity * summerRate; 6} 7 8// After 9if (isSummer(date)) { 10 charge = summerCharge(quantity); 11} else { 12 charge = winterCharge(quantity); 13} 14 15function isSummer(date: Date): boolean { 16 return !date.before(SUMMER_START) && !date.after(SUMMER_END); 17} 18 19function summerCharge(quantity: number): number { 20 return quantity * summerRate; 21} 22 23function winterCharge(quantity: number): number { 24 return quantity * winterRate + winterServiceCharge; 25}

IDE Refactoring Tools#

1// Most IDEs support: 2// - Rename (F2 in VS Code) 3// - Extract Method/Function 4// - Extract Variable 5// - Inline Variable 6// - Move to File 7// - Change Signature 8 9// VS Code: Right-click → Refactor 10// Or: Cmd/Ctrl + Shift + R

When NOT to Refactor#

- Deadline is tomorrow (ship first, refactor later) - Code will be deleted soon - No tests and can't add them - You don't understand the code yet - Major rewrite is more practical

Conclusion#

Refactoring is a skill that improves with practice. Always have tests before refactoring, make small changes, and run tests frequently. Good refactoring makes future changes easier and code more enjoyable to work with.

Share this article

Help spread the word about Bootspring