Principle of Reusable & Scalable Code

บทนำ: “Code ที่ดี” หมายความว่าอะไร?

เมื่อเขียนโปรแกรม หลายคนคิดว่า “code ทำงานได้” ก็เพียงพอ แต่ในโลก production:

text❌ ปัญหา 1: ต้องเพิ่ม feature ใหม่ แต่ต้องแก้ code เก่า
❌ ปัญหา 2: Code ซ้ำซ้อนทั่ว project
❌ ปัญหา 3: ไม่รู้ว่า module นี้ใช้ได้ที่ไหนบ้าง
❌ ปัญหา 4: เปลี่ยนสิ่งหนึ่ง แต่ทำให้ส่วนอื่น break
❌ ปัญหา 5: ไม่รู้วิธีทำให้ code scale ขึ้น

Reusable & Scalable Code = Code ที่ ดูแลรักษาง่าย, ขยายได้, และใช้ซ้ำได้


SOLID Principles: Foundation of Good Design

Principle 1: Single Responsibility Principle (SRP)

“Class ควรมีเพียง ONE reason to change”

java// ❌ ไม่ดี: Class มี multiple responsibilities
public class UserManager {
    
    // Responsibility 1: User data management
    public User getUser(int id) {
        // Database query
    }
    
    public void saveUser(User user) {
        // Database save
    }
    
    // Responsibility 2: Email sending
    public void sendWelcomeEmail(User user) {
        // Email logic
    }
    
    // Responsibility 3: Logging
    public void logUserActivity(User user) {
        // Logging logic
    }
    
    // ← ต้อง change class นี้ถ้า:
    // - Database layer เปลี่ยน
    // - Email service เปลี่ยน
    // - Logging format เปลี่ยน
    // = 3 reasons to change!
}

// ✓ ดี: Each class มี ONE responsibility
public class UserRepository {
    public User getUser(int id) {
        // Only database operations
    }
    
    public void saveUser(User user) {
        // Only database operations
    }
}

public class EmailService {
    public void sendWelcomeEmail(User user) {
        // Only email operations
    }
}

public class UserLogger {
    public void logUserActivity(User user) {
        // Only logging operations
    }
}

// Usage: Compose responsibilities
public class UserOnboarding {
    private UserRepository userRepository;
    private EmailService emailService;
    private UserLogger userLogger;
    
    public void registerUser(User user) {
        userRepository.saveUser(user);
        emailService.sendWelcomeEmail(user);
        userLogger.logUserActivity(user);
    }
}

// ← Each class มี ONE reason to change
// ← Easier to test
// ← Easier to maintain

ประโยชน์ของ SRP:

  • Focused – Each class ทำ one thing ดี
  • Testable – Test หนึ่ง responsibility ต่อครั้ง
  • Reusable – UserRepository ใช้ได้ที่ใดที่ไหน
  • Maintainable – Change ใน one class ไม่ affect others

Principle 2: Open/Closed Principle (OCP)

“Code ควร OPEN for extension, CLOSED for modification”

java// ❌ ไม่ดี: ต้อง modify ทุกครั้งที่เพิ่ม discount type
public class PriceCalculator {
    
    public double calculatePrice(double price, String discountType) {
        if (discountType.equals("SUMMER")) {
            return price * 0.8;  // 20% off
        } else if (discountType.equals("WINTER")) {
            return price * 0.9;  // 10% off
        } else if (discountType.equals("CLEARANCE")) {
            return price * 0.5;  // 50% off
        }
        return price;
    }
    // ← ต้อง modify method นี้ทุกครั้งที่เพิ่ม discount
}

// ✓ ดี: Extend by creating new classes, not modifying existing
public interface DiscountStrategy {
    double applyDiscount(double price);
}

public class SummerDiscount implements DiscountStrategy {
    @Override
    public double applyDiscount(double price) {
        return price * 0.8;  // 20% off
    }
}

public class WinterDiscount implements DiscountStrategy {
    @Override
    public double applyDiscount(double price) {
        return price * 0.9;  // 10% off
    }
}

public class ClearanceDiscount implements DiscountStrategy {
    @Override
    public double applyDiscount(double price) {
        return price * 0.5;  // 50% off
    }
}

// ← เพิ่ม discount type โดยไม่ modify PriceCalculator
public class BlackFridayDiscount implements DiscountStrategy {
    @Override
    public double applyDiscount(double price) {
        return price * 0.3;  // 70% off
    }
}

public class PriceCalculatorImproved {
    
    public double calculatePrice(double price, DiscountStrategy strategy) {
        return strategy.applyDiscount(price);
    }
}

// Usage
PriceCalculatorImproved calculator = new PriceCalculatorImproved();
System.out.println(calculator.calculatePrice(100, new SummerDiscount()));      // 80
System.out.println(calculator.calculatePrice(100, new BlackFridayDiscount())); // 30
// ← เพิ่ม discount ใหม่ได้ทันทีโดยไม่ modify calculator

ประโยชน์ของ OCP:

  • Easy to extend – เพิ่มฟีเจอร์โดยไม่แก้เก่า
  • Low risk – ไม่กระทบ existing code
  • Scalable – Add strategies infinitely
  • Stable – Existing code ไม่เปลี่ยน

Principle 3: Liskov Substitution Principle (LSP)

“Subclass ควรใช้ทดแทน Superclass ได้ โดยไม่ break”

java// ❌ ไม่ดี: Bird subclass ไม่ implement fly() ได้
public abstract class Bird {
    abstract void fly();
}

public class Sparrow extends Bird {
    @Override
    void fly() {
        System.out.println("Sparrow is flying");
    }
}

public class Penguin extends Bird {
    @Override
    void fly() {
        // ← Penguin ไม่บินได้!
        throw new UnsupportedOperationException("Penguin cannot fly");
    }
}

public void makeBirdFly(Bird bird) {
    bird.fly();
    // ← ถ้า bird เป็น Penguin จะ crash!
}

// ✓ ดี: Design hierarchy ให้ถูกต้อง
public abstract class Bird {
    // Only common behaviors
}

public interface Flyable {
    void fly();
}

public class Sparrow extends Bird implements Flyable {
    @Override
    public void fly() {
        System.out.println("Sparrow is flying");
    }
}

public class Penguin extends Bird {
    public void swim() {
        System.out.println("Penguin is swimming");
    }
    // ← Penguin ไม่ implement Flyable
}

public void makeBirdFly(Flyable bird) {
    bird.fly();
    // ← ทุก bird ที่ส่งเข้ามา บินได้ทั้งหมด
}

// Usage
makeBirdFly(new Sparrow());      // OK
makeBirdFly(new Penguin());      // Compile error - good!

ประโยชน์ของ LSP:

  • Type Safety – Subclass substitution ปลอดภัย
  • Predictable – No surprises when using polymorphism
  • Correct Hierarchy – Design inheritance อย่างถูกต้อง

Principle 4: Interface Segregation Principle (ISP)

“Client ไม่ควร depend on interface ที่ไม่ใช้”

java// ❌ ไม่ดี: Fat interface ที่ต้อง implement ทั้งหมด
public interface Worker {
    void work();
    void eat();
    void sleep();
}

public class Robot implements Worker {
    @Override
    public void work() {
        System.out.println("Robot is working");
    }
    
    @Override
    public void eat() {
        // ← Robot ไม่ต้องกิน!
        throw new UnsupportedOperationException();
    }
    
    @Override
    public void sleep() {
        // ← Robot ไม่ต้องนอน!
        throw new UnsupportedOperationException();
    }
}

// ✓ ดี: Segregate interface เป็นส่วนเล็ก
public interface Workable {
    void work();
}

public interface Eatable {
    void eat();
}

public interface Sleepable {
    void sleep();
}

public class Human implements Workable, Eatable, Sleepable {
    @Override
    public void work() { System.out.println("Human is working"); }
    
    @Override
    public void eat() { System.out.println("Human is eating"); }
    
    @Override
    public void sleep() { System.out.println("Human is sleeping"); }
}

public class Robot implements Workable {
    @Override
    public void work() { System.out.println("Robot is working"); }
    // ← Robot only implement what it needs
}

ประโยชน์ของ ISP:

  • Flexible – Implement only needed behaviors
  • Clean – No unnecessary method implementations
  • Focused – Interface ทำ one thing ดี

Principle 5: Dependency Inversion Principle (DIP)

“Depend on abstractions, not concretions”

java// ❌ ไม่ดี: High-level depends on low-level (tight coupling)
public class MySQLDatabase {
    public void save(String data) {
        System.out.println("Saving to MySQL: " + data);
    }
}

public class UserService {
    private MySQLDatabase database = new MySQLDatabase();  // ← Tight coupling!
    
    public void saveUser(String user) {
        database.save(user);
    }
}
// ← ถ้าต้องเปลี่ยนจาก MySQL ไป PostgreSQL ต้อง modify UserService

// ✓ ดี: Both depend on abstraction
public interface Database {
    void save(String data);
}

public class MySQLDatabase implements Database {
    @Override
    public void save(String data) {
        System.out.println("Saving to MySQL: " + data);
    }
}

public class PostgreSQLDatabase implements Database {
    @Override
    public void save(String data) {
        System.out.println("Saving to PostgreSQL: " + data);
    }
}

public class UserService {
    private Database database;  // ← Depend on abstraction
    
    // Dependency Injection
    public UserService(Database database) {
        this.database = database;
    }
    
    public void saveUser(String user) {
        database.save(user);
    }
}

// Usage
UserService service1 = new UserService(new MySQLDatabase());
UserService service2 = new UserService(new PostgreSQLDatabase());
// ← ไม่ต้อง modify UserService!

ประโยชน์ของ DIP:

  • Decoupled – High-level ไม่ depend on low-level
  • Flexible – Easy to swap implementations
  • Testable – Mock dependency ได้ง่าย

ตัวอย่างที่ 1: Reusable Code Design

Problem: Duplicated Validation Logic

java// ❌ ไม่ดี: Validation logic ซ้ำกันทั่วที่
public class UserController {
    public void registerUser(String email, String password) {
        // Validation logic
        if (email == null || email.isEmpty()) {
            throw new IllegalArgumentException("Email required");
        }
        if (!email.contains("@")) {
            throw new IllegalArgumentException("Invalid email");
        }
        if (password == null || password.length() < 6) {
            throw new IllegalArgumentException("Password too short");
        }
        // Register logic
    }
}

public class ProductService {
    public void createProduct(String email) {
        // Same validation logic repeated!
        if (email == null || email.isEmpty()) {
            throw new IllegalArgumentException("Email required");
        }
        if (!email.contains("@")) {
            throw new IllegalArgumentException("Invalid email");
        }
        // Create logic
    }
}

// ✓ ดี: Reusable validator
public class Validator {
    public static void validateEmail(String email) {
        if (email == null || email.isEmpty()) {
            throw new IllegalArgumentException("Email required");
        }
        if (!email.contains("@")) {
            throw new IllegalArgumentException("Invalid email");
        }
    }
    
    public static void validatePassword(String password) {
        if (password == null || password.length() < 6) {
            throw new IllegalArgumentException("Password too short");
        }
    }
}

public class UserController {
    public void registerUser(String email, String password) {
        Validator.validateEmail(email);
        Validator.validatePassword(password);
        // Register logic
    }
}

public class ProductService {
    public void createProduct(String email) {
        Validator.validateEmail(email);
        // Create logic
    }
}

// ← Validation logic ใน ONE place
// ← Reusable across project
// ← Easy to modify validation rules

ตัวอย่างที่ 2: Scalable Architecture

Problem: Hard to Add New Features

java// ❌ ไม่ดี: Monolithic service, hard to scale
public class OrderService {
    
    public void processOrder(Order order) {
        // 1. Validate order
        if (order.getItems().isEmpty()) {
            throw new IllegalArgumentException("Order must have items");
        }
        
        // 2. Check inventory
        for (OrderItem item : order.getItems()) {
            if (!hasStock(item)) {
                throw new RuntimeException("Out of stock");
            }
        }
        
        // 3. Process payment
        PaymentGateway gateway = new PaymentGateway();
        boolean paid = gateway.charge(order.getTotal());
        if (!paid) {
            throw new RuntimeException("Payment failed");
        }
        
        // 4. Send email
        EmailService email = new EmailService();
        email.send(order.getCustomer().getEmail(), 
                   "Order confirmed: " + order.getId());
        
        // 5. Update inventory
        updateInventory(order);
        
        // 6. Log order
        System.out.println("Order processed: " + order.getId());
    }
    
    // ← ต้องแก้ method นี้ทุกครั้งที่เพิ่ม step
}

// ✓ ดี: Scalable with composable services
public interface OrderStep {
    void execute(Order order);
}

public class OrderValidationStep implements OrderStep {
    @Override
    public void execute(Order order) {
        if (order.getItems().isEmpty()) {
            throw new IllegalArgumentException("Order must have items");
        }
    }
}

public class InventoryCheckStep implements OrderStep {
    @Override
    public void execute(Order order) {
        for (OrderItem item : order.getItems()) {
            if (!hasStock(item)) {
                throw new RuntimeException("Out of stock");
            }
        }
    }
}

public class PaymentProcessingStep implements OrderStep {
    private PaymentGateway gateway;
    
    public PaymentProcessingStep(PaymentGateway gateway) {
        this.gateway = gateway;
    }
    
    @Override
    public void execute(Order order) {
        boolean paid = gateway.charge(order.getTotal());
        if (!paid) {
            throw new RuntimeException("Payment failed");
        }
    }
}

public class NotificationStep implements OrderStep {
    private EmailService emailService;
    
    public NotificationStep(EmailService emailService) {
        this.emailService = emailService;
    }
    
    @Override
    public void execute(Order order) {
        emailService.send(order.getCustomer().getEmail(), 
                         "Order confirmed: " + order.getId());
    }
}

public class OrderServiceScalable {
    private List<OrderStep> steps = new ArrayList<>();
    
    // Add steps dynamically
    public void addStep(OrderStep step) {
        steps.add(step);
    }
    
    public void processOrder(Order order) {
        for (OrderStep step : steps) {
            step.execute(order);
        }
    }
}

// Usage: Easy to add/remove steps
OrderServiceScalable service = new OrderServiceScalable();
service.addStep(new OrderValidationStep());
service.addStep(new InventoryCheckStep());
service.addStep(new PaymentProcessingStep(new PaymentGateway()));
service.addStep(new NotificationStep(new EmailService()));

service.processOrder(order);

// ← เพิ่ม step ใหม่ได้ง่าย (e.g., ApplyCouponStep, LoggingStep)
// ← ไม่ต้องแก้ OrderServiceScalable
// ← Each step ใช้ซ้ำได้ที่อื่น

ตัวอย่างที่ 3: Testable Architecture

Problem: Hard to Test Due to Dependencies

java// ❌ ไม่ดี: Hard to test (real database, real email)
public class UserService {
    
    public void registerUser(User user) {
        // Real database
        MySQLDatabase db = new MySQLDatabase();
        db.save(user);
        
        // Real email service
        SMTPEmailService email = new SMTPEmailService();
        email.send(user.getEmail(), "Welcome!");
    }
}

// Test นี้ต้อง:
// - Create real database
// - Create real email service
// - ใช้เวลา
// - ไม่ reliable

// ✓ ดี: Testable with dependency injection
public interface UserRepository {
    void save(User user);
}

public interface EmailService {
    void send(String email, String message);
}

public class UserService {
    private UserRepository repository;
    private EmailService emailService;
    
    // Dependency Injection
    public UserService(UserRepository repository, EmailService emailService) {
        this.repository = repository;
        this.emailService = emailService;
    }
    
    public void registerUser(User user) {
        repository.save(user);
        emailService.send(user.getEmail(), "Welcome!");
    }
}

// ✓ Test with mock objects
@Test
public void testRegisterUser() {
    // Mock repository
    UserRepository mockRepo = new UserRepository() {
        @Override
        public void save(User user) {
            System.out.println("Mock: Saving user");
        }
    };
    
    // Mock email service
    EmailService mockEmail = new EmailService() {
        @Override
        public void send(String email, String message) {
            System.out.println("Mock: Sending email");
        }
    };
    
    UserService service = new UserService(mockRepo, mockEmail);
    User user = new User("[email protected]");
    service.registerUser(user);
    
    // Test passes instantly, no real DB/email
}

Measuring Code Quality

Metrics ที่ต้องตระหนัก

text✓ HIGH COHESION:
  - Class มี related responsibilities
  - Methods collaborate ใน same class
  - Indicator: Class ทำ ONE thing ดี

✓ LOW COUPLING:
  - Class ไม่ depend much on others
  - Easy to change one class independently
  - Indicator: Few dependencies

✓ HIGH REUSABILITY:
  - Components ใช้ซ้ำได้หลาย context
  - Small, focused classes
  - Indicator: Components reused in project

✓ EASY TESTABILITY:
  - ทุก component test ได้ independently
  - Mock dependencies ได้
  - Indicator: Test suite comprehensive

❌ CODE SMELLS (ต้องระวัง):
  - God Class (class ทำหลายอย่าง)
  - Duplicated Code
  - Long Method
  - Tight Coupling
  - Too Many Parameters

Best Practices: Creating Reusable & Scalable Code

text✓ DO:
  ☑ Follow SOLID principles
  ☑ Extract common logic to utilities/services
  ☑ Use interfaces for abstraction
  ☑ Apply Dependency Injection
  ☑ Design for extension, not modification
  ☑ Write focused, single-purpose classes
  ☑ Test with mocks and interfaces
  ☑ Document why design was chosen

✗ DON'T:
  ☐ Create God Classes (too many responsibilities)
  ☐ Duplicate code across project
  ☐ Tight couple components
  ☐ Mix layers (business logic with data access)
  ☐ Over-engineer for hypothetical scenarios
  ☐ Hardcode dependencies
  ☐ Make everything abstract immediately
  ☐ Skip testing because code "seems simple"

สรุป

Principle of Reusable & Scalable Code ไม่ใช่ “nice to have” แต่เป็น essential skill สำหรับ professional developers:

5 SOLID Principles:

  • SRP: One responsibility per class
  • OCP: Open for extension, closed for modification
  • LSP: Substitutable subclasses
  • ISP: Segregated interfaces
  • DIP: Depend on abstractions

3 Key Outcomes:

  1. Reusable – Components ใช้ซ้ำได้ที่หลาย projects
  2. Scalable – Add features ได้โดยไม่break existing
  3. Maintainable – Easy to test, fix, และ modify

Real-world Impact:

  • Team productivity เพิ่ม – code reuse ลด duplication
  • Bug reduction – focused classes ใช้ง่ายและ test ง่าย
  • Faster onboarding – clear responsibility ทำให้ code เข้าใจ
  • Technical debt ลด – design ที่ดี ใช้ระยะยาวได้

Principles เหล่านี้ not rules to follow blindly แต่เป็น guidelines ที่help คุณ write code ที่ stands the test of time – code ที่ยังใช้ได้และ maintain ได้เมื่อ requirements เปลี่ยน ทีมเปลี่ยน และ project grow ขึ้น