Files
gh-dieshen-claude-marketpla…/commands/refactoring-patterns.md
2025-11-29 18:21:08 +08:00

14 KiB

Code Quality & Refactoring Patterns

Comprehensive refactoring patterns, SOLID principles, and clean code practices for maintainable software.

SOLID Principles

Single Responsibility Principle (SRP)

A class should have one, and only one, reason to change.

// Bad: Multiple responsibilities
class User {
  constructor(public name: string, public email: string) {}

  save() {
    // Database logic
    db.query('INSERT INTO users...');
  }

  sendEmail() {
    // Email logic
    emailService.send(this.email, 'Welcome!');
  }

  generateReport() {
    // Report generation logic
    return `User: ${this.name}`;
  }
}

// Good: Single responsibility per class
class User {
  constructor(public name: string, public email: string) {}
}

class UserRepository {
  save(user: User) {
    db.query('INSERT INTO users...', user);
  }
}

class UserEmailService {
  sendWelcomeEmail(user: User) {
    emailService.send(user.email, 'Welcome!');
  }
}

class UserReportGenerator {
  generate(user: User): string {
    return `User: ${user.name}`;
  }
}

Open/Closed Principle (OCP)

Software entities should be open for extension, but closed for modification.

// Bad: Must modify class to add new shapes
class AreaCalculator {
  calculate(shapes: any[]) {
    let area = 0;
    for (const shape of shapes) {
      if (shape.type === 'circle') {
        area += Math.PI * shape.radius ** 2;
      } else if (shape.type === 'square') {
        area += shape.side ** 2;
      }
      // Need to modify for new shapes
    }
    return area;
  }
}

// Good: Open for extension, closed for modification
interface Shape {
  area(): number;
}

class Circle implements Shape {
  constructor(public radius: number) {}

  area(): number {
    return Math.PI * this.radius ** 2;
  }
}

class Square implements Shape {
  constructor(public side: number) {}

  area(): number {
    return this.side ** 2;
  }
}

class Triangle implements Shape {
  constructor(public base: number, public height: number) {}

  area(): number {
    return (this.base * this.height) / 2;
  }
}

class AreaCalculator {
  calculate(shapes: Shape[]): number {
    return shapes.reduce((total, shape) => total + shape.area(), 0);
  }
}

Liskov Substitution Principle (LSP)

Objects of a superclass should be replaceable with objects of its subclasses without breaking the application.

// Bad: Violates LSP
class Bird {
  fly() {
    console.log('Flying');
  }
}

class Penguin extends Bird {
  fly() {
    throw new Error('Penguins cannot fly!');
  }
}

// Good: Proper abstraction
interface Bird {
  move(): void;
}

class FlyingBird implements Bird {
  move() {
    this.fly();
  }

  fly() {
    console.log('Flying');
  }
}

class Penguin implements Bird {
  move() {
    this.swim();
  }

  swim() {
    console.log('Swimming');
  }
}

Interface Segregation Principle (ISP)

Clients should not be forced to depend on interfaces they don't use.

// Bad: Fat interface
interface Worker {
  work(): void;
  eat(): void;
  sleep(): void;
}

class Human implements Worker {
  work() { console.log('Working'); }
  eat() { console.log('Eating'); }
  sleep() { console.log('Sleeping'); }
}

class Robot implements Worker {
  work() { console.log('Working'); }
  eat() { throw new Error('Robots dont eat'); }
  sleep() { throw new Error('Robots dont sleep'); }
}

// Good: Segregated interfaces
interface Workable {
  work(): void;
}

interface Eatable {
  eat(): void;
}

interface Sleepable {
  sleep(): void;
}

class Human implements Workable, Eatable, Sleepable {
  work() { console.log('Working'); }
  eat() { console.log('Eating'); }
  sleep() { console.log('Sleeping'); }
}

class Robot implements Workable {
  work() { console.log('Working'); }
}

Dependency Inversion Principle (DIP)

High-level modules should not depend on low-level modules. Both should depend on abstractions.

// Bad: High-level depends on low-level
class MySQLDatabase {
  save(data: any) {
    console.log('Saving to MySQL');
  }
}

class UserService {
  private db = new MySQLDatabase(); // Tight coupling

  saveUser(user: User) {
    this.db.save(user);
  }
}

// Good: Both depend on abstraction
interface Database {
  save(data: any): void;
}

class MySQLDatabase implements Database {
  save(data: any) {
    console.log('Saving to MySQL');
  }
}

class PostgreSQLDatabase implements Database {
  save(data: any) {
    console.log('Saving to PostgreSQL');
  }
}

class UserService {
  constructor(private db: Database) {} // Dependency injection

  saveUser(user: User) {
    this.db.save(user);
  }
}

// Usage
const service = new UserService(new MySQLDatabase());

Common Code Smells and Refactoring

Long Method

// Bad: Long method
function processOrder(order: Order) {
  // Validate order (20 lines)
  if (!order.items || order.items.length === 0) {
    throw new Error('No items');
  }
  // Calculate totals (30 lines)
  let total = 0;
  for (const item of order.items) {
    total += item.price * item.quantity;
  }
  // Apply discounts (25 lines)
  if (order.coupon) {
    total -= total * order.coupon.discount;
  }
  // Process payment (40 lines)
  // Send notifications (20 lines)
  // Update inventory (30 lines)
}

// Good: Extract methods
function processOrder(order: Order) {
  validateOrder(order);
  const total = calculateTotal(order);
  const finalTotal = applyDiscounts(total, order.coupon);
  processPayment(order, finalTotal);
  sendNotifications(order);
  updateInventory(order);
}

function validateOrder(order: Order) {
  if (!order.items || order.items.length === 0) {
    throw new Error('Order must contain items');
  }
}

function calculateTotal(order: Order): number {
  return order.items.reduce(
    (sum, item) => sum + item.price * item.quantity,
    0
  );
}

function applyDiscounts(total: number, coupon?: Coupon): number {
  if (!coupon) return total;
  return total * (1 - coupon.discount);
}

Duplicated Code

// Bad: Duplicated logic
class UserController {
  async getUser(req, res) {
    try {
      const user = await userService.findById(req.params.id);
      if (!user) {
        return res.status(404).json({ error: 'User not found' });
      }
      res.json(user);
    } catch (error) {
      console.error(error);
      res.status(500).json({ error: 'Internal server error' });
    }
  }

  async updateUser(req, res) {
    try {
      const user = await userService.findById(req.params.id);
      if (!user) {
        return res.status(404).json({ error: 'User not found' });
      }
      const updated = await userService.update(req.params.id, req.body);
      res.json(updated);
    } catch (error) {
      console.error(error);
      res.status(500).json({ error: 'Internal server error' });
    }
  }
}

// Good: Extract common logic
class UserController {
  private async handleRequest(
    req: Request,
    res: Response,
    handler: (user: User) => Promise<any>
  ) {
    try {
      const user = await userService.findById(req.params.id);
      if (!user) {
        return res.status(404).json({ error: 'User not found' });
      }
      const result = await handler(user);
      res.json(result);
    } catch (error) {
      console.error(error);
      res.status(500).json({ error: 'Internal server error' });
    }
  }

  async getUser(req: Request, res: Response) {
    await this.handleRequest(req, res, (user) => user);
  }

  async updateUser(req: Request, res: Response) {
    await this.handleRequest(req, res, (user) =>
      userService.update(user.id, req.body)
    );
  }
}

Large Class (God Object)

// Bad: Too many responsibilities
class Order {
  items: Item[];
  customer: Customer;
  total: number;

  calculateTotal() { /* ... */ }
  applyDiscount() { /* ... */ }
  validateOrder() { /* ... */ }
  processPayment() { /* ... */ }
  sendEmail() { /* ... */ }
  updateInventory() { /* ... */ }
  generateInvoice() { /* ... */ }
  shipOrder() { /* ... */ }
}

// Good: Split responsibilities
class Order {
  constructor(
    public items: Item[],
    public customer: Customer
  ) {}
}

class OrderCalculator {
  calculateTotal(order: Order): number { /* ... */ }
  applyDiscount(order: Order, discount: Discount): number { /* ... */ }
}

class OrderValidator {
  validate(order: Order): boolean { /* ... */ }
}

class PaymentProcessor {
  process(order: Order, amount: number): Payment { /* ... */ }
}

class OrderNotificationService {
  sendConfirmation(order: Order): void { /* ... */ }
}

class InventoryService {
  updateFromOrder(order: Order): void { /* ... */ }
}

class InvoiceGenerator {
  generate(order: Order): Invoice { /* ... */ }
}

class ShippingService {
  ship(order: Order): Shipment { /* ... */ }
}

Feature Envy

// Bad: Method more interested in other class
class Order {
  items: Item[];

  getTotalWeight() {
    let weight = 0;
    for (const item of this.items) {
      weight += item.product.weight * item.quantity;
    }
    return weight;
  }
}

// Good: Move method to where data is
class Order {
  items: Item[];

  getTotalWeight(): number {
    return this.items.reduce((sum, item) => sum + item.getWeight(), 0);
  }
}

class Item {
  constructor(
    public product: Product,
    public quantity: number
  ) {}

  getWeight(): number {
    return this.product.weight * this.quantity;
  }
}

Refactoring Patterns

Extract Method

// Before
function printOwing(invoice: Invoice) {
  console.log('***********************');
  console.log('**** Customer Owes ****');
  console.log('***********************');

  let outstanding = 0;
  for (const order of invoice.orders) {
    outstanding += order.amount;
  }

  console.log(`name: ${invoice.customer}`);
  console.log(`amount: ${outstanding}`);
}

// After
function printOwing(invoice: Invoice) {
  printBanner();
  const outstanding = calculateOutstanding(invoice);
  printDetails(invoice, outstanding);
}

function printBanner() {
  console.log('***********************');
  console.log('**** Customer Owes ****');
  console.log('***********************');
}

function calculateOutstanding(invoice: Invoice): number {
  return invoice.orders.reduce((sum, order) => sum + order.amount, 0);
}

function printDetails(invoice: Invoice, outstanding: number) {
  console.log(`name: ${invoice.customer}`);
  console.log(`amount: ${outstanding}`);
}

Replace Conditional with Polymorphism

// Before
class Bird {
  type: string;

  getSpeed(): number {
    switch (this.type) {
      case 'european':
        return this.getBaseSpeed();
      case 'african':
        return this.getBaseSpeed() - this.getLoadFactor() * this.numberOfCoconuts;
      case 'norwegian':
        return this.isNailed ? 0 : this.getBaseSpeed();
      default:
        throw new Error('Unknown type');
    }
  }
}

// After
abstract class Bird {
  abstract getSpeed(): number;
  protected abstract getBaseSpeed(): number;
}

class EuropeanBird extends Bird {
  getSpeed(): number {
    return this.getBaseSpeed();
  }

  protected getBaseSpeed(): number {
    return 35;
  }
}

class AfricanBird extends Bird {
  constructor(private numberOfCoconuts: number) {
    super();
  }

  getSpeed(): number {
    return this.getBaseSpeed() - this.getLoadFactor() * this.numberOfCoconuts;
  }

  protected getBaseSpeed(): number {
    return 40;
  }

  private getLoadFactor(): number {
    return 2;
  }
}

class NorwegianBird extends Bird {
  constructor(private isNailed: boolean) {
    super();
  }

  getSpeed(): number {
    return this.isNailed ? 0 : this.getBaseSpeed();
  }

  protected getBaseSpeed(): number {
    return 30;
  }
}

Replace Magic Numbers with Constants

// Before
function calculatePrice(quantity: number, price: number): number {
  if (quantity > 100) {
    return quantity * price * 0.9; // What is 0.9?
  }
  return quantity * price;
}

// After
const BULK_ORDER_THRESHOLD = 100;
const BULK_DISCOUNT_RATE = 0.1;

function calculatePrice(quantity: number, price: number): number {
  if (quantity > BULK_ORDER_THRESHOLD) {
    return quantity * price * (1 - BULK_DISCOUNT_RATE);
  }
  return quantity * price;
}

Clean Code Principles

Meaningful Names

// Bad
function d(t: number) {
  return t * 86400000;
}

// Good
const MILLISECONDS_PER_DAY = 86400000;

function daysToMilliseconds(days: number): number {
  return days * MILLISECONDS_PER_DAY;
}

Functions Should Do One Thing

// Bad
function emailClients(clients: Client[]) {
  clients.forEach(client => {
    const clientRecord = database.lookup(client);
    if (clientRecord.isActive()) {
      email(client);
    }
  });
}

// Good
function emailActiveClients(clients: Client[]) {
  clients
    .filter(isActiveClient)
    .forEach(email);
}

function isActiveClient(client: Client): boolean {
  const clientRecord = database.lookup(client);
  return clientRecord.isActive();
}

Use Default Parameters

// Bad
function createMenu(title: string, body: string, buttonText: string, cancellable: boolean) {
  title = title || 'Default Title';
  body = body || 'Default Body';
  buttonText = buttonText || 'OK';
  cancellable = cancellable !== undefined ? cancellable : true;
}

// Good
function createMenu(
  title: string = 'Default Title',
  body: string = 'Default Body',
  buttonText: string = 'OK',
  cancellable: boolean = true
) {
  // ...
}

Best Practices

  1. Follow SOLID principles
  2. Keep functions small and focused
  3. Use meaningful names
  4. Avoid code duplication (DRY)
  5. Write self-documenting code
  6. Refactor continuously
  7. Test your code
  8. Use consistent formatting
  9. Handle errors properly
  10. Review and improve regularly