Code Smell และ Refactoring Techniques

บทนำ: “Code Smell” คืออะไร?

Code Smell = Warning signs ที่บ่งชี้ว่า code มีปัญหาโครงสร้าง (ไม่ใช่ bugs ที่ crash)

textCode Smell ≠ Bug
├─ Bug: Program crash หรือ output ผิด
└─ Code Smell: Code ทำงานได้ แต่มีปัญหาการออกแบบ

ลักษณะของ Code Smell:
❌ Hard to understand
❌ Hard to change
❌ Hard to reuse
❌ Prone to bugs
❌ Slow to develop new features

Refactoring = Restructure code เพื่อ remove smells โดยไม่เปลี่ยน behavior


Common Code Smells & Solutions

Smell 1: Duplicated Code

Problem: Code ซ้ำกันหลายที่

java// ❌ CODE SMELL: Duplicated 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, String description) {
        // Same validation logic repeated!
        if (email == null || email.isEmpty()) {
            throw new IllegalArgumentException("Email required");
        }
        if (!email.contains("@")) {
            throw new IllegalArgumentException("Invalid email");
        }
        // Create logic
    }
}

public class AdminPanel {
    public void updateUser(String email) {
        // And again... same code!
        if (email == null || email.isEmpty()) {
            throw new IllegalArgumentException("Email required");
        }
        if (!email.contains("@")) {
            throw new IllegalArgumentException("Invalid email");
        }
        // Update logic
    }
}

// PROBLEMS:
// ❌ DRY violation (Don't Repeat Yourself)
// ❌ Change validation rule ต้องแก้ 3 ที่
// ❌ Easy to miss one location
// ❌ Inconsistent implementation

Refactoring: Extract Method

java// ✓ REFACTORED: Create reusable validator
public class EmailValidator {
    public static void validate(String email) {
        if (email == null || email.isEmpty()) {
            throw new IllegalArgumentException("Email required");
        }
        if (!email.contains("@")) {
            throw new IllegalArgumentException("Invalid email");
        }
    }
}

public class PasswordValidator {
    public static void validate(String password) {
        if (password == null || password.length() < 6) {
            throw new IllegalArgumentException("Password too short");
        }
    }
}

// ✓ REUSED everywhere
public class UserController {
    public void registerUser(String email, String password) {
        EmailValidator.validate(email);      // ← Reuse
        PasswordValidator.validate(password); // ← Reuse
        // Register logic
    }
}

public class ProductService {
    public void createProduct(String email, String description) {
        EmailValidator.validate(email);  // ← Reuse same validator
        // Create logic
    }
}

public class AdminPanel {
    public void updateUser(String email) {
        EmailValidator.validate(email);  // ← Reuse
        // Update logic
    }
}

// BENEFITS:
// ✓ Single source of truth
// ✓ Change validation once
// ✓ Consistent behavior
// ✓ Reusable across project

Smell 2: Long Method

Problem: Single method ทำหลายอย่าง

java// ❌ CODE SMELL: Long method (40+ lines)
public class OrderService {
    public void processOrder(Order order) {
        // Step 1: Validate order (10 lines)
        if (order.getItems().isEmpty()) {
            throw new IllegalArgumentException("Order must have items");
        }
        for (OrderItem item : order.getItems()) {
            if (item.getQuantity() <= 0) {
                throw new IllegalArgumentException("Invalid quantity");
            }
        }
        
        // Step 2: Calculate total (8 lines)
        double subtotal = 0;
        for (OrderItem item : order.getItems()) {
            subtotal += item.getPrice() * item.getQuantity();
        }
        double tax = subtotal * 0.1;
        double total = subtotal + tax;
        
        // Step 3: Process payment (10 lines)
        PaymentGateway gateway = new PaymentGateway();
        boolean paid = gateway.charge(order.getCustomer().getCard(), total);
        if (!paid) {
            throw new RuntimeException("Payment failed");
        }
        
        // Step 4: Update inventory (8 lines)
        InventoryService inventory = new InventoryService();
        for (OrderItem item : order.getItems()) {
            inventory.decreaseStock(item.getProductId(), item.getQuantity());
        }
        
        // Step 5: Send email (5 lines)
        EmailService email = new EmailService();
        email.send(order.getCustomer().getEmail(), 
                   "Order confirmed: #" + order.getId());
    }
    
    // PROBLEMS:
    // ❌ Hard to understand (need to read 40+ lines)
    // ❌ Hard to test (need to mock everything)
    // ❌ Hard to reuse individual steps
    // ❌ High complexity
}

Refactoring: Extract Methods

java// ✓ REFACTORED: Break into smaller methods
public class OrderService {
    
    public void processOrder(Order order) {
        validateOrder(order);
        double total = calculateTotal(order);
        processPayment(order, total);
        updateInventory(order);
        sendConfirmation(order);
    }
    
    private void validateOrder(Order order) {
        if (order.getItems().isEmpty()) {
            throw new IllegalArgumentException("Order must have items");
        }
        for (OrderItem item : order.getItems()) {
            if (item.getQuantity() <= 0) {
                throw new IllegalArgumentException("Invalid quantity");
            }
        }
    }
    
    private double calculateTotal(Order order) {
        double subtotal = 0;
        for (OrderItem item : order.getItems()) {
            subtotal += item.getPrice() * item.getQuantity();
        }
        double tax = subtotal * 0.1;
        return subtotal + tax;
    }
    
    private void processPayment(Order order, double total) {
        PaymentGateway gateway = new PaymentGateway();
        boolean paid = gateway.charge(order.getCustomer().getCard(), total);
        if (!paid) {
            throw new RuntimeException("Payment failed");
        }
    }
    
    private void updateInventory(Order order) {
        InventoryService inventory = new InventoryService();
        for (OrderItem item : order.getItems()) {
            inventory.decreaseStock(item.getProductId(), item.getQuantity());
        }
    }
    
    private void sendConfirmation(Order order) {
        EmailService email = new EmailService();
        email.send(order.getCustomer().getEmail(), 
                   "Order confirmed: #" + order.getId());
    }
}

// BENEFITS:
// ✓ Easy to understand (processOrder() ชัดเจนว่าทำอะไร)
// ✓ Easy to test (test each step independently)
// ✓ Easy to reuse (calculateTotal() ใช้ที่อื่นได้)
// ✓ Low complexity (each method ทำ one thing)

Smell 3: Large Class (God Class)

Problem: Class ทำหลายอย่างต่างกัน

java// ❌ CODE SMELL: God Class (150+ lines, 10+ methods)
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 service
    public void sendWelcomeEmail(User user) {
        // Email logic
    }
    public void sendResetPasswordEmail(User user) {
        // Email logic
    }
    
    // Responsibility 3: Authentication
    public boolean authenticate(String username, String password) {
        // Auth logic
    }
    public void resetPassword(User user, String newPassword) {
        // Password logic
    }
    
    // Responsibility 4: Logging
    public void logUserActivity(User user, String action) {
        // Logging logic
    }
    public void logLoginAttempt(String username, boolean success) {
        // Logging logic
    }
    
    // More methods...
    
    // PROBLEMS:
    // ❌ Multiple reasons to change (4+)
    // ❌ Hard to understand
    // ❌ Hard to test
    // ❌ Components can't be reused separately
}

Refactoring: Extract Class

java// ✓ REFACTORED: Split into focused classes

// 1. Repository for data access
public class UserRepository {
    public User findById(int id) { /* ... */ }
    public void save(User user) { /* ... */ }
}

// 2. Email service
public class EmailService {
    public void sendWelcomeEmail(User user) { /* ... */ }
    public void sendResetPasswordEmail(User user) { /* ... */ }
}

// 3. Authentication service
public class AuthenticationService {
    public boolean authenticate(String username, String password) { /* ... */ }
    public void resetPassword(User user, String newPassword) { /* ... */ }
}

// 4. Logging service
public class UserActivityLogger {
    public void logUserActivity(User user, String action) { /* ... */ }
    public void logLoginAttempt(String username, boolean success) { /* ... */ }
}

// 5. Facade: Orchestrates everything
public class UserService {
    private UserRepository repository;
    private EmailService emailService;
    private AuthenticationService authService;
    private UserActivityLogger logger;
    
    public UserService(UserRepository repository, EmailService emailService,
                      AuthenticationService authService, UserActivityLogger logger) {
        this.repository = repository;
        this.emailService = emailService;
        this.authService = authService;
        this.logger = logger;
    }
    
    public void registerUser(String username, String email, String password) {
        User user = new User(username, email);
        repository.save(user);
        emailService.sendWelcomeEmail(user);
        logger.logUserActivity(user, "REGISTERED");
    }
    
    public boolean login(String username, String password) {
        boolean authenticated = authService.authenticate(username, password);
        logger.logLoginAttempt(username, authenticated);
        return authenticated;
    }
}

// BENEFITS:
// ✓ Each class: single responsibility
// ✓ Each class: easy to understand
// ✓ Each component: easy to test
// ✓ Each component: reusable

Smell 4: Feature Envy

Problem: Method uses another object’s data too much

java// ❌ CODE SMELL: Feature envy
public class OrderProcessor {
    
    public double calculateOrderTotal(Order order) {
        // ← "Envying" Order's data
        double subtotal = 0;
        
        // Accessing Order's items directly
        for (OrderItem item : order.getItems()) {
            subtotal += item.getPrice() * item.getQuantity();
        }
        
        // Accessing Order's customer
        if (order.getCustomer().isPremium()) {
            subtotal *= 0.9;  // 10% premium discount
        }
        
        // Accessing Order's tax rate
        double tax = subtotal * order.getTaxRate();
        
        return subtotal + tax;
    }
    
    // PROBLEM:
    // ❌ OrderProcessor knows too much about Order's internals
    // ❌ If Order structure changes, OrderProcessor breaks
    // ❌ Tight coupling
}

Refactoring: Move Method to Appropriate Class

java// ✓ REFACTORED: Move calculation to Order class

public class Order {
    private List<OrderItem> items;
    private Customer customer;
    private double taxRate;
    
    // ← Move calculation HERE where data lives
    public double calculateTotal() {
        double subtotal = 0;
        
        // Access own data
        for (OrderItem item : items) {
            subtotal += item.getPrice() * item.getQuantity();
        }
        
        // Access own customer
        if (customer.isPremium()) {
            subtotal *= 0.9;
        }
        
        // Access own tax rate
        double tax = subtotal * taxRate;
        
        return subtotal + tax;
    }
}

public class OrderProcessor {
    
    public double calculateOrderTotal(Order order) {
        // ← Simply delegate to Order
        return order.calculateTotal();
    }
}

// BENEFITS:
// ✓ Order "owns" its calculation logic
// ✓ Loose coupling
// ✓ Easy to change Order logic
// ✓ Order can be reused independently

Smell 5: Primitive Obsession

Problem: Using primitives instead of objects

java// ❌ CODE SMELL: Primitive obsession
public class Customer {
    public String email;        // ← Primitive
    public String phoneNumber;  // ← Primitive
    public String address;      // ← Primitive
    public double creditLimit;  // ← Primitive
}

public class EmailValidator {
    public static boolean isValidEmail(String email) {
        // Validation logic repeated in many places
    }
}

public class PhoneValidator {
    public static boolean isValidPhone(String phone) {
        // Validation logic
    }
}

// PROBLEMS:
// ❌ Validation logic scattered
// ❌ Easy to pass wrong value
// ❌ No encapsulation
// ❌ Same validation repeated

Refactoring: Create Value Objects

java// ✓ REFACTORED: Create value objects

public class Email {
    private final String value;
    
    public Email(String value) {
        if (!isValid(value)) {
            throw new IllegalArgumentException("Invalid email: " + value);
        }
        this.value = value;
    }
    
    private static boolean isValid(String email) {
        return email != null && email.contains("@");
    }
    
    @Override
    public String toString() { return value; }
}

public class PhoneNumber {
    private final String value;
    
    public PhoneNumber(String value) {
        if (!isValid(value)) {
            throw new IllegalArgumentException("Invalid phone: " + value);
        }
        this.value = value;
    }
    
    private static boolean isValid(String phone) {
        return phone != null && phone.matches("\\d{10}");
    }
    
    @Override
    public String toString() { return value; }
}

public class Address {
    private final String street;
    private final String city;
    private final String zipCode;
    
    public Address(String street, String city, String zipCode) {
        this.street = street;
        this.city = city;
        this.zipCode = zipCode;
    }
    
    // Methods to work with address
}

public class Customer {
    private Email email;           // ← Value object
    private PhoneNumber phone;     // ← Value object
    private Address address;       // ← Value object
    private double creditLimit;
    
    public Customer(Email email, PhoneNumber phone, Address address) {
        this.email = email;
        this.phone = phone;
        this.address = address;
    }
}

// Usage: Validation happens automatically
Email email = new Email("invalid");  // ← Throws immediately
Email validEmail = new Email("[email protected]");  // ← OK

// BENEFITS:
// ✓ Validation encapsulated
// ✓ Single source of truth
// ✓ Type-safe
// ✓ Self-documenting

Smell 6: Long Parameter List

Problem: Method มี parameters เยอะเกิน

java// ❌ CODE SMELL: Long parameter list
public void createOrder(String customerId, String productId, int quantity,
                       String shippingAddress, String billingAddress,
                       String shippingMethod, String paymentMethod,
                       String couponCode, String giftMessage) {
    // 9 parameters! Hard to use correctly
}

// Usage: Error-prone
createOrder("CUST-123", "PROD-456", 2,
           "123 Main St", "123 Main St",
           "EXPRESS", "CREDIT_CARD",
           "SAVE10", "Happy Birthday!");

// PROBLEMS:
// ❌ Easy to pass parameters in wrong order
// ❌ Hard to remember parameter order
// ❌ Hard to add new parameters
// ❌ Hard to test with many combinations

Refactoring: Create Object to Hold Parameters

java// ✓ REFACTORED: Group parameters into object

public class OrderRequest {
    private String customerId;
    private String productId;
    private int quantity;
    private String shippingAddress;
    private String billingAddress;
    private String shippingMethod;
    private String paymentMethod;
    private String couponCode;
    private String giftMessage;
    
    // Use builder pattern for clarity
    public static class Builder {
        private String customerId;
        private String productId;
        private int quantity;
        private String shippingAddress;
        private String billingAddress = "";
        private String shippingMethod = "STANDARD";
        private String paymentMethod;
        private String couponCode = "";
        private String giftMessage = "";
        
        public Builder withCustomerId(String id) {
            this.customerId = id;
            return this;
        }
        
        public Builder withProductId(String id) {
            this.productId = id;
            return this;
        }
        
        public Builder withQuantity(int qty) {
            this.quantity = qty;
            return this;
        }
        
        public Builder withShippingAddress(String addr) {
            this.shippingAddress = addr;
            return this;
        }
        
        // ... more setters
        
        public OrderRequest build() {
            return new OrderRequest(this);
        }
    }
    
    private OrderRequest(Builder builder) {
        this.customerId = builder.customerId;
        this.productId = builder.productId;
        this.quantity = builder.quantity;
        this.shippingAddress = builder.shippingAddress;
        this.billingAddress = builder.billingAddress;
        this.shippingMethod = builder.shippingMethod;
        this.paymentMethod = builder.paymentMethod;
        this.couponCode = builder.couponCode;
        this.giftMessage = builder.giftMessage;
    }
    
    // Getters
    public String getCustomerId() { return customerId; }
    // ... more getters
}

public class OrderService {
    
    public void createOrder(OrderRequest request) {
        // Much cleaner!
        // Can validate request object
        validateRequest(request);
        // Process order
    }
}

// Usage: Clear and maintainable
OrderRequest request = new OrderRequest.Builder()
    .withCustomerId("CUST-123")
    .withProductId("PROD-456")
    .withQuantity(2)
    .withShippingAddress("123 Main St")
    .withShippingMethod("EXPRESS")
    .withPaymentMethod("CREDIT_CARD")
    .withCouponCode("SAVE10")
    .withGiftMessage("Happy Birthday!")
    .build();

orderService.createOrder(request);

// BENEFITS:
// ✓ Clear and self-documenting
// ✓ Easy to add new fields
// ✓ Defaults for optional parameters
// ✓ Can validate in one place

Refactoring Techniques Quick Reference

textTECHNIQUE 1: Extract Method
When: Code can be isolated into logical unit
How: Move code to new method, replace with call
Example: calculateDiscount() extracted from processOrder()

TECHNIQUE 2: Extract Class
When: Class has multiple responsibilities
How: Create new class, move related methods/fields
Example: PaymentService extracted from OrderService

TECHNIQUE 3: Extract Interface
When: Want to decouple from implementation
How: Create interface, move method signatures
Example: PaymentStrategy from concrete payment classes

TECHNIQUE 4: Rename
When: Name doesn't reflect purpose
How: Change name everywhere (IDE refactor tool)
Example: temp → discountedPrice

TECHNIQUE 5: Remove Duplicated Code
When: Same code in multiple places
How: Extract to common method or class
Example: Email validation moved to EmailValidator

TECHNIQUE 6: Move Method
When: Method should be in different class
How: Copy method to new class, adjust, delete from old
Example: calculateTotal() moved to Order

TECHNIQUE 7: Replace Conditional with Polymorphism
When: Many if-else branches
How: Create strategy classes, use polymorphism
Example: if (type == "pdf") → PdfStrategy class

Safety: Refactoring Checklist

textBEFORE REFACTORING:
  ☑ Comprehensive test suite exists
  ☑ All tests pass
  ☑ Version control is ready
  ☑ Communicate with team
  ☑ Create feature branch
  ☑ Document current behavior

DURING REFACTORING:
  ☑ Small, focused changes
  ☑ Run tests after each change
  ☑ Keep IDE's refactor tools
  ☑ Review changes carefully
  ☑ Commit frequently

AFTER REFACTORING:
  ☑ All tests pass
  ☑ Performance unchanged
  ☑ Code review approved
  ☑ Tests added for edge cases
  ☑ Documentation updated
  ☑ Team understands changes

TOOLS:
  ✓ IDE refactoring: IntelliJ, Eclipse, VS Code
  ✓ Static analysis: SonarQube, Checkstyle
  ✓ Test coverage: JaCoCo, Cobertura
  ✓ Version control: Git with branches

สรุป

Code Smell และ Refactoring Techniques ไม่ใช่ “cosmetic cleanup” แต่เป็น essential practice สำหรับ sustainable development:

6 Common Code Smells:

  1. Duplicated Code → Extract to common location
  2. Long Method → Break into smaller methods
  3. Large Class → Split into focused classes
  4. Feature Envy → Move method to appropriate class
  5. Primitive Obsession → Create value objects
  6. Long Parameter List → Use parameter object

Refactoring Principles:

  • Small Steps – Change one thing at a time
  • Tests First – Ensure tests pass
  • Incremental – Don’t refactor everything at once
  • Safe – Use IDE tools, control version

ผลประโยชน์ของ Regular Refactoring:

  • Code ง่าย read และ understand
  • New features เพิ่มได้เร็ว
  • Bugs ลด (code clear เกิด bug ยาก)
  • Team productivity เพิ่ม
  • Technical debt ไม่สะสม

Refactoring คือ “housekeeping” ของการเขียนโปรแกรม – ถ้าไม่เก็บกวาดเสมอ code จะ messy และ hard to work with ถ้าทำ refactoring อย่างสม่ำเสมอและ systematic คุณ maintain high quality codebase ที่ทำให้ team สุข และ project succeed ได้ยาวนาน