What is Refactoring?
Refactoring is the process of restructuring existing code without changing its external behavior. The goal is to improve code readability, reduce complexity, and make it easier to maintain โ all while keeping the tests passing.
Refactoring to design patterns means recognizing code smells (signs of poor design) and applying the appropriate pattern to fix them. It's not about adding features โ it's about making the code better at what it already does.
Common Code Smells
1. Long Method
โโโโโโโโโโโโโ
A method that does too many things.
Solution: Extract Method, Strategy, Command
2. Large Class
โโโโโโโโโโโโ
A class with too many responsibilities.
Solution: Extract Class, SRP
3. Switch Statements / Type Checking
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Long if/else or switch based on type.
Solution: Polymorphism, Strategy, State
4. Duplicated Code
โโโโโโโโโโโโโโโโ
The same logic appears in multiple places.
Solution: Extract Method, Template Method
5. Feature Envy
โโโโโโโโโโโโโโ
A method uses more data from another class than its own.
Solution: Move Method
6. Data Clumps
โโโโโโโโโโโโ
Same groups of data appearing together repeatedly.
Solution: Extract Class
7. Primitive Obsession
โโโโโโโโโโโโโโโโโโโโ
Using primitives instead of small objects.
Solution: Replace with Value Object
Refactoring to Strategy
// BEFORE: Switch statement on type
class ShippingCalculator {
calculate(order, shippingType) {
switch (shippingType) {
case "standard":
return order.weight * 0.5 + 5;
case "express":
return order.weight * 1.5 + 15;
case "overnight":
return order.weight * 3 + 30;
case "international":
return order.weight * 2 + 25 + order.customsFee;
default:
throw new Error("Unknown shipping type");
}
}
}
// AFTER: Refactored to Strategy
class ShippingStrategy {
calculate(order) { throw new Error("Implement me"); }
}
class StandardShipping extends ShippingStrategy {
calculate(order) {
return order.weight * 0.5 + 5;
}
}
class ExpressShipping extends ShippingStrategy {
calculate(order) {
return order.weight * 1.5 + 15;
}
}
class OvernightShipping extends ShippingStrategy {
calculate(order) {
return order.weight * 3 + 30;
}
}
class InternationalShipping extends ShippingStrategy {
calculate(order) {
return order.weight * 2 + 25 + order.customsFee;
}
}
class ShippingCalculator {
calculate(order, strategy) {
return strategy.calculate(order);
}
}
// Usage
const calculator = new ShippingCalculator();
const order = { weight: 10, customsFee: 50 };
console.log(calculator.calculate(order, new StandardShipping())); // 10
console.log(calculator.calculate(order, new ExpressShipping())); // 30
console.log(calculator.calculate(order, new InternationalShipping())); // 95
// Benefits:
// โ Adding new shipping types doesn't modify existing code (OCP)
// โ Each strategy is small and focused (SRP)
// โ No more switch statement to maintain
Refactoring to Observer
// BEFORE: Tight coupling โ Order directly calls multiple services
class Order {
place() {
// Business logic...
this.status = "placed";
// Directly coupled to multiple services
inventoryService.updateStock(this.items);
emailService.sendConfirmation(this.customer);
analyticsService.trackOrder(this);
loyaltyService.addPoints(this.customer, this.total);
}
}
// AFTER: Refactored to Observer
class Order {
#eventEmitter;
constructor() {
this.#eventEmitter = new EventEmitter();
}
on(event, handler) {
this.#eventEmitter.on(event, handler);
}
place() {
// Business logic only
this.status = "placed";
this.#eventEmitter.emit("orderPlaced", this);
}
}
// Setup observers (loose coupling)
const order = new Order();
order.on("orderPlaced", (o) => inventoryService.updateStock(o.items));
order.on("orderPlaced", (o) => emailService.sendConfirmation(o.customer));
order.on("orderPlaced", (o) => analyticsService.trackOrder(o));
order.on("orderPlaced", (o) => loyaltyService.addPoints(o.customer, o.total));
// Benefits:
// โ Order doesn't know about inventory, email, analytics, loyalty
// โ New observers can be added without modifying Order
// โ Observers can be removed or reordered independently
Refactoring to Factory
// BEFORE: Constructor with type-based branching
class Notification {
constructor(type, recipient, message) {
this.message = message;
this.recipient = recipient;
if (type === "email") {
this.send = () => console.log(`Email to ${recipient}: ${message}`);
} else if (type === "sms") {
this.send = () => console.log(`SMS to ${recipient}: ${message}`);
} else if (type === "push") {
this.send = () => console.log(`Push to ${recipient}: ${message}`);
} else {
throw new Error("Unknown notification type");
}
}
}
// AFTER: Refactored to Factory Method
class Notification {
send(message) { throw new Error("Implement send()"); }
}
class EmailNotification extends Notification {
#recipient;
constructor(recipient) { super(); this.#recipient = recipient; }
send(message) {
console.log(`Email to ${this.#recipient}: ${message}`);
}
}
class SMSNotification extends Notification {
#recipient;
constructor(recipient) { super(); this.#recipient = recipient; }
send(message) {
console.log(`SMS to ${this.#recipient}: ${message}`);
}
}
class PushNotification extends Notification {
#deviceId;
constructor(deviceId) { super(); this.#deviceId = deviceId; }
send(message) {
console.log(`Push to ${this.#deviceId}: ${message}`);
}
}
class NotificationFactory {
static create(type, recipient) {
switch (type) {
case "email": return new EmailNotification(recipient);
case "sms": return new SMSNotification(recipient);
case "push": return new PushNotification(recipient);
default: throw new Error("Unknown type");
}
}
}
// Usage
const notification = NotificationFactory.create("email", "user@example.com");
notification.send("Hello!");
Refactoring Checklist
Before Refactoring:
โ Ensure you have tests that verify current behavior
โ Make sure tests pass before you start
โ Commit your working code (safe rollback point)
During Refactoring:
โ Make one small change at a time
โ Run tests after every change
โ Keep methods small and focused
โ Rename for clarity
โ Extract when a method does too much
After Refactoring:
โ All tests pass
โ Code is more readable
โ No new code smells introduced
โ Commit with a clear message
Common Refactoring Patterns:
โโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Code Smell โ Refactoring Pattern โ
โโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Long method โ Extract Method / Strategy โ
โ Large class โ Extract Class โ
โ Switch on type โ Polymorphism / Strategy โ
โ Duplicated code โ Extract / Template Method โ
โ Feature envy โ Move Method โ
โ Data clumps โ Extract Class / Value Objectโ
โ Null checks everywhere โ Null Object Pattern โ
โ God class โ Facade / SRP โ
โโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ