บทนำ: “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:
- Duplicated Code → Extract to common location
- Long Method → Break into smaller methods
- Large Class → Split into focused classes
- Feature Envy → Move method to appropriate class
- Primitive Obsession → Create value objects
- 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 ได้ยาวนาน
