บทนำ: ทำไมต้อง Review Diagram?
เมื่อเขียนโปรแกรม หลายทีมคิดว่า “code เสร็จแล้ว ก็เสร็จ” แต่ในความเป็นจริง:
text❌ ปัญหา 1: Design ไม่ชัดเจน → Team ไม่เข้าใจ architecture
❌ ปัญหา 2: Dependency ยุ่งเหยิง → ยากต่อการ maintain
❌ ปัญหา 3: Code pattern ไม่สม่ำเสมอ → ยากต่อการ extend
❌ ปัญหา 4: Technical debt สะสม → เปลี่ยนแปลงช้า
❌ ปัญหา 5: Refactoring ไม่มีแผน → ผิดพลาด
Review Diagram & Refactor Design = การ systematically analyze และ improve architecture ของ system
Step 1: Creating Design Diagrams
Class Diagram: Visualize Structure
Purpose: Show classes, relationships, และ dependencies
textNotation:
┌─────────────────────┐
│ ClassName │
├─────────────────────┤
│ - attribute1: Type │ ← Attributes
│ - attribute2: Type │
├─────────────────────┤
│ + method1(): Type │ ← Methods
│ + method2(param) │
└─────────────────────┘
Relationships:
- Inheritance: ▲────
- Implementation: △────
- Association: ────→
- Composition: ◆────
- Dependency: ··→
ตัวอย่างที่ 1: E-Commerce System Class Diagram
textSimple textual representation:
┌──────────────────┐
│ Order │
├──────────────────┤
│ - id: int │
│ - date: Date │
│ - status: String │
├──────────────────┤
│ + getTotal() │
│ + changeStatus() │
└────────┬─────────┘
│ contains 1..*
▼
┌──────────────────┐
│ OrderItem │
├──────────────────┤
│ - quantity: int │
│ - price: double │
├──────────────────┤
│ + getSubtotal() │
└────────┬─────────┘
│ references
▼
┌──────────────────┐
│ Product │
├──────────────────┤
│ - id: int │
│ - name: String │
│ - price: double │
├──────────────────┤
│ + getPrice() │
└──────────────────┘
▲
│ uses
│
┌────────┴──────────┐
│ PaymentService │
├───────────────────┤
│ + process() │
│ + refund() │
└───────────────────┘
ตัวอย่างที่ 2: Pattern-Based Diagram
textFactory Pattern Architecture:
┌──────────────────────┐
│ ReportFactory │ ← Factory (creates objects)
├──────────────────────┤
│ + createReport() │
└────────┬─────────────┘
│ creates
│
┌────┴────┬─────────┬──────────┐
│ │ │ │
▼ ▼ ▼ ▼
┌────────┐ ┌──────┐ ┌──────┐ ┌────────┐
│Report │◄─┤PDF │ │Excel │ │ CSV │
│(I) │ │Report│ │Report│ │Report │
└────────┘ └──────┘ └──────┘ └────────┘
▲
│ implements
│
┌─────────────────┐
│ClientCode │ ← Uses abstraction, not concrete
├─────────────────┤
│+ generate() │
└─────────────────┘
Step 2: Analyzing the Diagram
ตัวอย่างที่ 3: Identifying Problems in Design
java// Bad design (as a diagram):
┌─────────────────────────────┐
│ MonolithicService │ ← GOD CLASS!
├─────────────────────────────┤
│ - database: Database │
│ - emailService: Email │
│ - logger: Logger │
│ - paymentGateway: Payment │
│ - analyticsEngine: Analytics
│ - notificationManager: Notif│
├─────────────────────────────┤
│ + createUser() │ ← Too many methods
│ + deleteUser() │
│ + sendEmail() │
│ + logActivity() │
│ + processPayment() │
│ + trackEvent() │
│ + notifyUser() │
│ + generateReport() │
│ + updateInventory() │
│ + ... (20+ more methods) │
└─────────────────────────────┘
│
├──→ DatabaseImpl
├──→ SMTPEmailService
├──→ FileLogger
├──→ PaymentGatewayImpl
└──→ GoogleAnalytics
PROBLEMS:
❌ Single Responsibility Principle violated
❌ Too many dependencies (6+)
❌ Hard to test
❌ Hard to reuse
❌ Too many reasons to change
วิธีแก้: Refactor into Layered Architecture
textGood design (as a diagram):
┌─────────────────────────────────────────┐
│ PRESENTATION LAYER │
│ ┌────────────────────────────────────┐ │
│ │ UserController │ │
│ └────────────────────────────────────┘ │
└─────────────┬─────────────────────────┘
│ uses
▼
┌─────────────────────────────────────────┐
│ SERVICE LAYER │
│ ┌─────────────────┐ ┌─────────────┐ │
│ │ UserService │ │EmailService │ │
│ └─────────────────┘ └─────────────┘ │
│ ┌─────────────────┐ ┌─────────────┐ │
│ │PaymentService │ │LogService │ │
│ └─────────────────┘ └─────────────┘ │
└─────────────┬──────────────────────────┘
│ uses
▼
┌─────────────────────────────────────────┐
│ REPOSITORY LAYER │
│ ┌────────────────────────────────────┐ │
│ │ UserRepository (interface) │ │
│ │ + findById() │ │
│ │ + save() │ │
│ │ + update() │ │
│ └────────────────────────────────────┘ │
│ ┌────────────────────────────────────┐ │
│ │ UserRepositoryImpl (MySQL impl) │ │
│ └────────────────────────────────────┘ │
└─────────────┬──────────────────────────┘
│ connects
▼
┌─────────────────────────────────────────┐
│ DATABASE LAYER │
│ MySQL Database │
└─────────────────────────────────────────┘
BENEFITS:
✓ Clear separation of concerns
✓ Each layer has single responsibility
✓ Easy to test each layer
✓ Easy to swap implementations
✓ Easier to understand and maintain
Step 3: Identifying Refactoring Opportunities
ตัวอย่างที่ 4: Code Smell → Refactoring Checklist
textRED FLAGS in Code/Diagram:
1. GOD CLASS (Too many responsibilities)
❌ One class doing everything
✓ Refactor: Split into multiple classes
2. TIGHT COUPLING (Too many dependencies)
❌ Class A depends on Class B, C, D, E
✓ Refactor: Use interfaces and DI
3. DUPLICATE CODE (Same logic repeated)
❌ calculateDiscount() in 3 different classes
✓ Refactor: Extract to common service
4. LONG METHOD (Method does too much)
❌ processOrder() has 50+ lines
✓ Refactor: Break into smaller methods
5. FEATURE ENVY (Using other class's data too much)
❌ Controller accessing repository directly
✓ Refactor: Go through service layer
6. INAPPROPRIATE INTIMACY (Classes know too much about each other)
❌ UserController directly accessing DatabaseConnection
✓ Refactor: Use repository pattern
Real Scenario: Refactoring a Payment System
java// BEFORE: Problematic Design
public class PaymentProcessor {
// ← Multiple responsibilities
// Responsibility 1: Calculate discount
public double calculateDiscount(Order order) {
if (order.getTotal() > 1000) {
return order.getTotal() * 0.1;
} else if (order.getTotal() > 500) {
return order.getTotal() * 0.05;
}
return 0;
}
// Responsibility 2: Process payment
public boolean processPayment(Order order, PaymentMethod method) {
// Complex payment logic
}
// Responsibility 3: Send notification
public void sendNotification(Order order) {
// Email logic
}
// Responsibility 4: Log transaction
public void logTransaction(Order order) {
// Logging logic
}
}
// PROBLEMS:
// ❌ 4 reasons to change
// ❌ Hard to test
// ❌ Hard to reuse discount logic
Refactored Design
java// AFTER: Improved Design with Separation of Concerns
// 1. Discount calculation → separate service
public interface DiscountStrategy {
double calculateDiscount(Order order);
}
public class PremiumDiscount implements DiscountStrategy {
@Override
public double calculateDiscount(Order order) {
if (order.getTotal() > 1000) return order.getTotal() * 0.1;
return 0;
}
}
// 2. Payment processing → separate service
public interface PaymentService {
boolean charge(Order order, PaymentMethod method);
}
public class CreditCardPaymentService implements PaymentService {
@Override
public boolean charge(Order order, PaymentMethod method) {
// Credit card logic only
}
}
// 3. Notification → separate service
public interface NotificationService {
void sendOrderConfirmation(Order order);
}
public class EmailNotificationService implements NotificationService {
@Override
public void sendOrderConfirmation(Order order) {
// Email logic only
}
}
// 4. Logging → separate service
public interface TransactionLogger {
void log(Order order, String action);
}
public class FileTransactionLogger implements TransactionLogger {
@Override
public void log(Order order, String action) {
// Logging logic only
}
}
// 5. Orchestrator: Composes all services
public class OrderProcessor {
private DiscountStrategy discountStrategy;
private PaymentService paymentService;
private NotificationService notificationService;
private TransactionLogger logger;
// Dependency Injection
public OrderProcessor(
DiscountStrategy discountStrategy,
PaymentService paymentService,
NotificationService notificationService,
TransactionLogger logger
) {
this.discountStrategy = discountStrategy;
this.paymentService = paymentService;
this.notificationService = notificationService;
this.logger = logger;
}
public void processOrder(Order order, PaymentMethod method) {
try {
// 1. Calculate discount
double discount = discountStrategy.calculateDiscount(order);
order.setDiscount(discount);
// 2. Process payment
boolean paid = paymentService.charge(order, method);
if (!paid) {
throw new RuntimeException("Payment failed");
}
// 3. Notify customer
notificationService.sendOrderConfirmation(order);
// 4. Log transaction
logger.log(order, "COMPLETED");
} catch (Exception e) {
logger.log(order, "FAILED: " + e.getMessage());
throw e;
}
}
}
// BENEFITS:
// ✓ Each class has ONE responsibility
// ✓ Easy to test each component independently
// ✓ Easy to swap implementations (e.g., different discount strategy)
// ✓ Easy to reuse services (e.g., DiscountStrategy used elsewhere)
// ✓ Only 1 reason to change per class
Step 4: Creating a Refactoring Plan
Refactoring Roadmap Template
text┌─────────────────────────────────────────────────────┐
│ REFACTORING PLAN DOCUMENT │
├─────────────────────────────────────────────────────┤
│ │
│ PROJECT: Order Management System │
│ DATE: 2025-11-06 │
│ │
├─────────────────────────────────────────────────────┤
│ 1. CURRENT STATE ANALYSIS │
│ │
│ Problem 1: PaymentProcessor is a God Class │
│ - 4 different responsibilities │
│ - 15+ methods │
│ - High complexity │
│ - Hard to test │
│ │
│ Problem 2: Tight coupling │
│ - PaymentProcessor depends on: │
│ * Database │
│ * EmailService │
│ * Logger │
│ * ExternalPaymentAPI │
│ - Difficult to mock for testing │
│ │
├─────────────────────────────────────────────────────┤
│ 2. DESIRED STATE (TARGET ARCHITECTURE) │
│ │
│ - Extract services (Discount, Payment, Email) │
│ - Use dependency injection │
│ - Each class: single responsibility │
│ - Interfaces for abstraction │
│ │
├─────────────────────────────────────────────────────┤
│ 3. REFACTORING STEPS │
│ │
│ Phase 1 (Week 1): Extract discount logic │
│ ├─ Create DiscountStrategy interface │
│ ├─ Create implementations (Premium, Standard) │
│ ├─ Update PaymentProcessor to use it │
│ ├─ Write unit tests │
│ └─ Deploy with feature flag │
│ │
│ Phase 2 (Week 2): Extract payment service │
│ ├─ Create PaymentService interface │
│ ├─ Create implementations (CreditCard, eWallet) │
│ ├─ Update PaymentProcessor │
│ ├─ Write integration tests │
│ └─ Deploy with feature flag │
│ │
│ Phase 3 (Week 3): Extract notifications │
│ ├─ Create NotificationService interface │
│ ├─ Create EmailNotification impl │
│ ├─ Update PaymentProcessor │
│ ├─ Test end-to-end │
│ └─ Deploy │
│ │
│ Phase 4 (Week 4): Extract logging │
│ ├─ Create TransactionLogger interface │
│ ├─ Create implementations │
│ ├─ Final cleanup │
│ ├─ Remove old code │
│ └─ Deploy final version │
│ │
├─────────────────────────────────────────────────────┤
│ 4. RISKS & MITIGATION │
│ │
│ Risk: Breaking existing code during refactor │
│ Mitigation: Feature flags + comprehensive tests │
│ │
│ Risk: Performance regression │
│ Mitigation: Load testing before each deploy │
│ │
│ Risk: Team doesn't understand new design │
│ Mitigation: Code review + documentation │
│ │
├─────────────────────────────────────────────────────┤
│ 5. SUCCESS CRITERIA │
│ │
│ ✓ All tests pass │
│ ✓ Code coverage > 80% │
│ ✓ No performance regression │
│ ✓ Team understands new architecture │
│ ✓ Documentation updated │
│ ✓ Zero production incidents │
│ │
└─────────────────────────────────────────────────────┘
Step 5: Testing the Refactored Code
ตัวอย่างที่ 5: Before & After Testing
java// BEFORE: Hard to test
@Test
public void testProcessOrder() {
// Problem: Must use real services
PaymentProcessor processor = new PaymentProcessor();
// Real database connection
// Real email service
// Real payment gateway
// = Slow, flaky, unreliable tests
Order order = new Order();
boolean result = processor.processOrder(order, new CreditCard());
assertTrue(result);
// Can't verify specific behavior
}
// AFTER: Easy to test with mocks
@Test
public void testProcessOrderSuccess() {
// Mock all dependencies
DiscountStrategy mockDiscount = mock(DiscountStrategy.class);
when(mockDiscount.calculateDiscount(any())).thenReturn(10.0);
PaymentService mockPayment = mock(PaymentService.class);
when(mockPayment.charge(any(), any())).thenReturn(true);
NotificationService mockNotification = mock(NotificationService.class);
TransactionLogger mockLogger = mock(TransactionLogger.class);
// Create processor with mocks (DI)
OrderProcessor processor = new OrderProcessor(
mockDiscount,
mockPayment,
mockNotification,
mockLogger
);
// Test
Order order = new Order();
order.setTotal(100);
processor.processOrder(order, new CreditCard());
// Verify behavior
verify(mockDiscount).calculateDiscount(order);
verify(mockPayment).charge(order, new CreditCard());
verify(mockNotification).sendOrderConfirmation(order);
verify(mockLogger).log(order, "COMPLETED");
// Fast, reliable, focused test
}
@Test
public void testProcessOrderPaymentFails() {
// Test specific scenario
PaymentService mockPayment = mock(PaymentService.class);
when(mockPayment.charge(any(), any())).thenReturn(false);
NotificationService mockNotification = mock(NotificationService.class);
TransactionLogger mockLogger = mock(TransactionLogger.class);
OrderProcessor processor = new OrderProcessor(
mock(DiscountStrategy.class),
mockPayment,
mockNotification,
mockLogger
);
// Verify payment failure is handled
assertThrows(RuntimeException.class, () -> {
processor.processOrder(new Order(), new CreditCard());
});
// Email should NOT be sent on failure
verify(mockNotification, never()).sendOrderConfirmation(any());
}
Best Practices: Refactoring Checklist
textBEFORE REFACTORING:
☑ Create comprehensive test suite
☑ Ensure all tests pass
☑ Version control: create feature branch
☑ Document current architecture
☑ Communicate with team
☑ Create refactoring plan
☑ Get code review approval
DURING REFACTORING:
☑ Refactor in small steps
☑ Run tests after each change
☑ Use feature flags for gradual rollout
☑ Keep commits small and focused
☑ Document decisions in commit messages
☑ Regular communication with team
AFTER REFACTORING:
☑ All tests pass
☑ Code review approval
☑ Performance testing
☑ Update documentation
☑ Monitor production
☑ Gather team feedback
☑ Update architecture diagrams
TOOLS FOR DIAGRAMS:
✓ UML Editors: Lucidchart, Draw.io, Visual Paradigm
✓ Team Collaboration: Miro, Mural
✓ Documentation: ArchiMate, C4 Model
Common Refactoring Patterns
textPATTERN 1: Extract Class
Problem: Class has multiple responsibilities
Solution: Split into multiple classes
PaymentProcessor → PaymentService + DiscountService
PATTERN 2: Extract Method
Problem: Method is too long
Solution: Break into smaller methods
processOrder() → calculateTotal() + processPayment() + sendEmail()
PATTERN 3: Extract Interface
Problem: Tight coupling to implementation
Solution: Depend on interface, not concrete class
Database → DatabaseService (interface)
PATTERN 4: Replace Magic Numbers with Constants
Problem: Hard-coded values scattered
Solution: Define constants
0.1 → PREMIUM_DISCOUNT_RATE
PATTERN 5: Move Method
Problem: Method should be in different class
Solution: Move to appropriate class
calculateOrderTotal() from Controller → Order class
PATTERN 6: Replace Conditional with Polymorphism
Problem: Many if-else branches
Solution: Use strategy pattern
if (type == "pdf") → use PdfStrategy
สรุป
การ Review Diagram และ Refactor Design ไม่ใช่ “optional activity” แต่เป็น critical part ของ software development lifecycle:
Key Activities:
- Visualize – Draw diagrams เพื่อเข้าใจ architecture
- Analyze – Identify code smells และ architectural issues
- Plan – Create refactoring roadmap
- Execute – Refactor incrementally กับ tests
- Verify – Test thoroughly ก่อน deploy
ประโยชน์ของ Regular Review & Refactoring:
- Code quality ยังคง high
- Technical debt ไม่สะสม
- New features เพิ่มได้เร็ว
- Bug ลด
- Team productivity เพิ่ม
Common Mistakes to Avoid:
- ✗ Refactor without tests
- ✗ Refactor too much at once
- ✗ Ignore team input
- ✗ Forget to document changes
- ✗ Deploy without verification
Design review และ refactoring คือ “health checkup” ของระบบ – ถ้าทำอย่างต่อเนื่องและ systematic คุณจะรักษา codebase ให้ healthy, scalable และ maintainable สำหรับปีต่อปีมาได้ สิ่งนี้คือสิ่งที่ distinguish professionals จาก amateurs – ability ที่จะ see beyond “it works” และ envision “how can we make it better”
