Loading...
Loading...
Master core refactoring operations: Extract Method, Extract Class, Replace Conditional with Polymorphism, Introduce Variable, Simplify Conditionals, Move Method, and Rename. Organized by operation type with before/after examples. Use when refactoring code structure, improving clarity, reducing duplication, or dealing with complex conditionals.
npx skill4agent add bsene/skills refactoring-patterns// ❌ Logic buried in larger method
function processOrder(order: Order) {
// ... 20 lines of setup ...
// Validate payment
if (order.payment.amount <= 0) {
throw new Error("Amount must be positive");
}
if (!order.payment.cardToken) {
throw new Error("Card required");
}
if (order.payment.amount > order.payment.cardLimit) {
throw new Error("Exceeds card limit");
}
// ... 20 more lines ...
}// ✅ Validation isolated and reusable
function processOrder(order: Order) {
// ... setup ...
validatePayment(order.payment);
// ... rest of logic ...
}
function validatePayment(payment: Payment) {
if (payment.amount <= 0) {
throw new Error("Amount must be positive");
}
if (!payment.cardToken) {
throw new Error("Card required");
}
if (payment.amount > payment.cardLimit) {
throw new Error("Exceeds card limit");
}
}// ❌ Mixed concerns in one class
class Order {
id: string;
customerId: string;
items: OrderItem[];
// Address fields scattered
shippingStreet: string;
shippingCity: string;
shippingZip: string;
billingStreet: string;
billingCity: string;
billingZip: string;
getShippingAddress(): string { /* ... */ }
getBillingAddress(): string { /* ... */ }
updateShippingAddress(street, city, zip) { /* ... */ }
validateShippingAddress() { /* ... */ }
calculateShippingCost() { /* ... */ }
}// ✅ Address responsibility extracted
class Address {
street: string;
city: string;
zip: string;
validate(): void { /* ... */ }
toString(): string { /* ... */ }
}
class Order {
id: string;
customerId: string;
items: OrderItem[];
shippingAddress: Address;
billingAddress: Address;
calculateShippingCost(): void { /* ... */ }
}// ❌ Type-based conditionals scattered
function calculateDiscount(customer: Customer): number {
if (customer.type === "gold") {
return customer.totalSpent * 0.15;
} else if (customer.type === "silver") {
return customer.totalSpent * 0.10;
} else if (customer.type === "bronze") {
return customer.totalSpent * 0.05;
}
return 0;
}
function sendNotification(customer: Customer, message: string) {
if (customer.type === "gold") {
sendEmail(customer.email, message);
sendSMS(customer.phone, message);
} else if (customer.type === "silver") {
sendEmail(customer.email, message);
} else {
// bronze gets nothing
}
}// ✅ Each customer type knows its own behavior
interface Customer {
name: string;
totalSpent: number;
calculateDiscount(): number;
sendNotification(message: string): void;
}
class GoldCustomer implements Customer {
calculateDiscount(): number {
return this.totalSpent * 0.15;
}
sendNotification(message: string): void {
sendEmail(this.email, message);
sendSMS(this.phone, message);
}
}
class SilverCustomer implements Customer {
calculateDiscount(): number {
return this.totalSpent * 0.10;
}
sendNotification(message: string): void {
sendEmail(this.email, message);
}
}
class BronzeCustomer implements Customer {
calculateDiscount(): number {
return this.totalSpent * 0.05;
}
sendNotification(message: string): void {
// intentionally does nothing
}
}
// Call site is now simple
const discount = customer.calculateDiscount();
customer.sendNotification(msg);# ❌ Hard to parse
if user.age >= 18 and user.country in ["US", "CA", "MX"] and \
user.verification_status == "verified" and user.account_age_days > 30:
allow_checkout()# ✅ Clear intent
is_adult = user.age >= 18
is_allowed_region = user.country in ["US", "CA", "MX"]
is_verified = user.verification_status == "verified"
is_established = user.account_age_days > 30
if is_adult and is_allowed_region and is_verified and is_established:
allow_checkout()
# Even better: extract to a method
def is_eligible_for_checkout(user: User) -> bool:
return (
user.age >= 18 and
user.country in ["US", "CA", "MX"] and
user.verification_status == "verified" and
user.account_age_days > 30
)
if is_eligible_for_checkout(user):
allow_checkout()// ❌ Nested if/else (default case buried at end)
function calculateShipping(order: Order): ShippingCost {
if (order.weight > 0) {
if (order.destination !== null) {
if (order.isPriority) {
return calculateExpressShipping(order);
} else {
return calculateStandardShipping(order);
}
} else {
throw new Error("No destination");
}
} else {
throw new Error("Invalid weight");
}
}// ✅ Guards at top, happy path clear
function calculateShipping(order: Order): ShippingCost {
if (order.weight <= 0) {
throw new Error("Invalid weight");
}
if (!order.destination) {
throw new Error("No destination");
}
if (order.isPriority) {
return calculateExpressShipping(order);
}
return calculateStandardShipping(order);
}// ❌ Method belongs elsewhere
class Order {
items: OrderItem[];
customer: Customer;
// This method uses mostly customer data
calculateCustomerDiscount(): number {
if (this.customer.isPremium) {
return this.items.reduce((sum, item) => sum + item.price, 0) * 0.15;
}
return 0;
}
}// ✅ Discount logic lives with customer
class Customer {
isPremium: boolean;
calculateDiscount(orderTotal: number): number {
return this.isPremium ? orderTotal * 0.15 : 0;
}
}
class Order {
items: OrderItem[];
customer: Customer;
getTotal(): number {
const subtotal = this.items.reduce((sum, item) => sum + item.price, 0);
const discount = this.customer.calculateDiscount(subtotal);
return subtotal - discount;
}
}calctmpdata// ❌ Poor naming requires mental mapping
function proc(d: any[]): any[] {
const r = [];
for (let i = 0; i < d.length; i++) {
if (d[i].s === "active") {
r.push(d[i].amt * 1.15); // What's being calculated?
}
}
return r;
}// ✅ Intent is self-evident
function calculateTaxAdjustedAmounts(orders: Order[]): number[] {
const taxAdjustedAmounts = [];
for (const order of orders) {
if (order.status === "active") {
const taxRate = 1.15; // 15% tax
taxAdjustedAmounts.push(order.amount * taxRate);
}
}
return taxAdjustedAmounts;
}
// Even better with functional approach
function calculateTaxAdjustedAmounts(orders: Order[]): number[] {
return orders
.filter((order) => order.status === "active")
.map((order) => order.amount * 1.15);
}| Pattern | Use When | Payoff |
|---|---|---|
| Extract Method | Logic has single purpose, appears multiple times, or is hard to test | Reusable, testable, clearer intent |
| Extract Class | Class has multiple responsibilities or mixed concerns | Focused, reusable, easier to test |
| Replace Conditional | Same type/status check scattered in multiple places | Open/Closed Principle, localized behavior, extensible |
| Introduce Variable | Complex expression is hard to read or repeated | Self-documenting, testable, reusable |
| Simplify Conditional | Nested or complex if/else logic | Readable, fail-fast, happy path clear |
| Move Method/Field | Method/field belongs logically elsewhere | Better cohesion, less coupling, reusable |
| Rename | Name doesn't express intent | Self-documenting, faster onboarding, clearer design |
refactoring-catalog