Technology

Agile Design คืออะไร — หลักการออกแบบ Software แบบ Agile

agile design คอ อะไร
agile design คืออะไร | SiamCafe Blog
2025-10-24· อ. บอม — SiamCafe.net· 1,554 คำ

Agile Design คืออะไรและหลักการสำคัญ

Agile Design คือแนวทางการออกแบบ software ที่เน้น iterative development, continuous feedback และ adaptability แทนที่จะออกแบบทุกอย่างล่วงหน้า (Big Design Up Front — BDUF) Agile Design ออกแบบเท่าที่จำเป็นแล้วปรับปรุงตามการเรียนรู้จากการใช้งานจริง

หลักการสำคัญของ Agile Design ได้แก่ YAGNI (You Aren't Gonna Need It) ไม่สร้างสิ่งที่ยังไม่ต้องการ, KISS (Keep It Simple Stupid) ทำให้เรียบง่ายที่สุด, DRY (Don't Repeat Yourself) ไม่ทำซ้ำ, SOLID principles สำหรับ object-oriented design และ Refactoring ปรับปรุง code structure อย่างต่อเนื่อง

Agile Design ต่างจาก Traditional Design ตรงที่ Traditional Design ออกแบบเสร็จก่อนแล้วค่อย implement ส่วน Agile Design ออกแบบและ implement ไปพร้อมกัน ปรับเปลี่ยนได้ตลอด Traditional Design ใช้เวลามากกับ documentation Agile Design เน้น working code และ automated tests เป็น documentation

Agile Design ไม่ได้หมายความว่าไม่ออกแบบเลย แต่ออกแบบเพียงพอสำหรับ iteration ปัจจุบัน ใช้ emergent design ให้ architecture เกิดขึ้นจากการ refactor อย่างต่อเนื่อง ทำให้ได้ design ที่เหมาะกับ requirements จริงไม่ใช่ requirements ที่คาดเดา

Agile Design Principles สำหรับ Software Development

SOLID Principles พร้อมตัวอย่าง code

#!/usr/bin/env python3
# solid_principles.py — SOLID Principles Examples

# === S: Single Responsibility Principle ===
# แต่ละ class มีเหตุผลเดียวในการเปลี่ยนแปลง

# BAD: class ทำหลายหน้าที่
class UserBad:
    def __init__(self, name, email):
        self.name = name
        self.email = email
    
    def save_to_db(self):  # persistence logic
        pass
    
    def send_email(self):  # notification logic
        pass
    
    def generate_report(self):  # reporting logic
        pass

# GOOD: แยกหน้าที่ออก
class User:
    def __init__(self, name: str, email: str):
        self.name = name
        self.email = email

class UserRepository:
    def save(self, user: User):
        # Database logic only
        print(f"Saved {user.name} to database")
    
    def find_by_email(self, email: str):
        # Query logic only
        return User("found", email)

class EmailService:
    def send_welcome(self, user: User):
        # Email logic only
        print(f"Sent welcome email to {user.email}")

class UserReportGenerator:
    def generate(self, users: list):
        # Report logic only
        return f"Report: {len(users)} users"

# === O: Open/Closed Principle ===
# เปิดสำหรับ extension, ปิดสำหรับ modification

from abc import ABC, abstractmethod

class PaymentProcessor(ABC):
    @abstractmethod
    def process(self, amount: float) -> bool:
        pass

class CreditCardProcessor(PaymentProcessor):
    def process(self, amount: float) -> bool:
        print(f"Processing  via credit card")
        return True

class PayPalProcessor(PaymentProcessor):
    def process(self, amount: float) -> bool:
        print(f"Processing  via PayPal")
        return True

class CryptoProcessor(PaymentProcessor):
    def process(self, amount: float) -> bool:
        print(f"Processing  via crypto")
        return True

# เพิ่ม payment method ใหม่โดยไม่ต้องแก้ code เดิม
class PaymentService:
    def __init__(self, processor: PaymentProcessor):
        self.processor = processor
    
    def pay(self, amount: float):
        return self.processor.process(amount)

# === L: Liskov Substitution Principle ===
# Subclass ต้องใช้แทน parent class ได้

class Shape(ABC):
    @abstractmethod
    def area(self) -> float:
        pass

class Rectangle(Shape):
    def __init__(self, width: float, height: float):
        self.width = width
        self.height = height
    
    def area(self) -> float:
        return self.width * self.height

class Circle(Shape):
    def __init__(self, radius: float):
        self.radius = radius
    
    def area(self) -> float:
        return 3.14159 * self.radius ** 2

# ใช้ Shape ได้โดยไม่สนว่าเป็น Rectangle หรือ Circle
def print_area(shape: Shape):
    print(f"Area: {shape.area():.2f}")

# === I: Interface Segregation Principle ===
# แยก interface ให้เล็ก ไม่บังคับ implement สิ่งที่ไม่ต้องการ

class Readable(ABC):
    @abstractmethod
    def read(self) -> str: pass

class Writable(ABC):
    @abstractmethod
    def write(self, data: str): pass

class Deletable(ABC):
    @abstractmethod
    def delete(self): pass

# implement เฉพาะที่ต้องการ
class ReadOnlyFile(Readable):
    def read(self) -> str:
        return "file content"

class FullAccessFile(Readable, Writable, Deletable):
    def read(self) -> str:
        return "file content"
    def write(self, data: str):
        print(f"Writing: {data}")
    def delete(self):
        print("File deleted")

# === D: Dependency Inversion Principle ===
# depend on abstractions, not concretions

class Logger(ABC):
    @abstractmethod
    def log(self, message: str): pass

class FileLogger(Logger):
    def log(self, message: str):
        print(f"[FILE] {message}")

class CloudLogger(Logger):
    def log(self, message: str):
        print(f"[CLOUD] {message}")

class Application:
    def __init__(self, logger: Logger):  # depend on abstraction
        self.logger = logger
    
    def run(self):
        self.logger.log("Application started")

# Swap implementations easily
app = Application(FileLogger())
app.run()
app = Application(CloudLogger())
app.run()

Iterative Design Process ด้วย Code

ตัวอย่างการออกแบบแบบ iterative

#!/usr/bin/env python3
# iterative_design.py — Iterative Design Example

# === Iteration 1: Simplest Thing That Works ===
# เริ่มจาก simple function

def calculate_price_v1(base_price):
    """V1: Just return the price"""
    return base_price

# === Iteration 2: Add Discount ===
# Requirements เพิ่ม: ต้องรองรับ discount

def calculate_price_v2(base_price, discount_percent=0):
    """V2: Added discount support"""
    discount = base_price * (discount_percent / 100)
    return base_price - discount

# === Iteration 3: Refactor to Class ===
# Requirements เพิ่ม: หลาย discount types, tax, currency

from dataclasses import dataclass
from typing import List, Optional
from abc import ABC, abstractmethod

@dataclass
class Product:
    name: str
    price: float
    category: str

class DiscountStrategy(ABC):
    @abstractmethod
    def calculate(self, price: float) -> float:
        pass

class PercentageDiscount(DiscountStrategy):
    def __init__(self, percent: float):
        self.percent = percent
    
    def calculate(self, price: float) -> float:
        return price * (self.percent / 100)

class FixedDiscount(DiscountStrategy):
    def __init__(self, amount: float):
        self.amount = amount
    
    def calculate(self, price: float) -> float:
        return min(self.amount, price)

class BuyOneGetOneDiscount(DiscountStrategy):
    def calculate(self, price: float) -> float:
        return price * 0.5

class TaxCalculator:
    TAX_RATES = {
        "TH": 0.07,
        "US": 0.08,
        "UK": 0.20,
        "JP": 0.10,
    }
    
    def calculate(self, price: float, country: str = "TH") -> float:
        rate = self.TAX_RATES.get(country, 0.07)
        return price * rate

class PriceCalculator:
    def __init__(self):
        self.tax_calculator = TaxCalculator()
    
    def calculate(self, product: Product,
                  discounts: Optional[List[DiscountStrategy]] = None,
                  country: str = "TH",
                  include_tax: bool = True) -> dict:
        
        base_price = product.price
        total_discount = 0.0
        
        if discounts:
            for discount in discounts:
                total_discount += discount.calculate(base_price - total_discount)
        
        subtotal = base_price - total_discount
        tax = self.tax_calculator.calculate(subtotal, country) if include_tax else 0
        total = subtotal + tax
        
        return {
            "product": product.name,
            "base_price": round(base_price, 2),
            "discount": round(total_discount, 2),
            "subtotal": round(subtotal, 2),
            "tax": round(tax, 2),
            "total": round(total, 2),
        }

# Usage
calc = PriceCalculator()

product = Product("Laptop", 35000, "electronics")
result = calc.calculate(
    product,
    discounts=[PercentageDiscount(10), FixedDiscount(500)],
    country="TH",
)
print(f"{result['product']}: {result['total']:,.2f} THB")

# === Iteration 4: Add Validation and Events ===

class PriceEvent:
    def __init__(self, event_type, data):
        self.event_type = event_type
        self.data = data
        self.timestamp = __import__("datetime").datetime.utcnow()

class EventBus:
    def __init__(self):
        self._handlers = {}
    
    def subscribe(self, event_type, handler):
        self._handlers.setdefault(event_type, []).append(handler)
    
    def publish(self, event: PriceEvent):
        for handler in self._handlers.get(event.event_type, []):
            handler(event)

# Design evolves with requirements
# Each iteration adds just enough complexity

Design Patterns ใน Agile Development

Patterns ที่ใช้บ่อยใน Agile projects

#!/usr/bin/env python3
# agile_patterns.py — Common Design Patterns in Agile

# === 1. Repository Pattern ===
# Decouple data access from business logic

from abc import ABC, abstractmethod
from typing import Optional, List
from dataclasses import dataclass

@dataclass
class Task:
    id: Optional[int]
    title: str
    status: str = "pending"
    priority: int = 0

class TaskRepository(ABC):
    @abstractmethod
    def save(self, task: Task) -> Task: pass
    
    @abstractmethod
    def find_by_id(self, task_id: int) -> Optional[Task]: pass
    
    @abstractmethod
    def find_all(self, status: Optional[str] = None) -> List[Task]: pass
    
    @abstractmethod
    def delete(self, task_id: int) -> bool: pass

class InMemoryTaskRepository(TaskRepository):
    def __init__(self):
        self._tasks = {}
        self._next_id = 1
    
    def save(self, task: Task) -> Task:
        if task.id is None:
            task.id = self._next_id
            self._next_id += 1
        self._tasks[task.id] = task
        return task
    
    def find_by_id(self, task_id: int) -> Optional[Task]:
        return self._tasks.get(task_id)
    
    def find_all(self, status: Optional[str] = None) -> List[Task]:
        tasks = list(self._tasks.values())
        if status:
            tasks = [t for t in tasks if t.status == status]
        return tasks
    
    def delete(self, task_id: int) -> bool:
        return self._tasks.pop(task_id, None) is not None

# SQLite implementation (swap without changing business logic)
# class SQLiteTaskRepository(TaskRepository):
#     def __init__(self, db_path):
#         self.conn = sqlite3.connect(db_path)
#     ...

# === 2. Service Layer Pattern ===
class TaskService:
    def __init__(self, repo: TaskRepository):
        self.repo = repo
    
    def create_task(self, title: str, priority: int = 0) -> Task:
        if not title.strip():
            raise ValueError("Title cannot be empty")
        
        task = Task(id=None, title=title.strip(), priority=priority)
        return self.repo.save(task)
    
    def complete_task(self, task_id: int) -> Task:
        task = self.repo.find_by_id(task_id)
        if not task:
            raise ValueError(f"Task {task_id} not found")
        
        task.status = "completed"
        return self.repo.save(task)
    
    def get_pending_tasks(self) -> List[Task]:
        return self.repo.find_all(status="pending")

# === 3. Observer Pattern ===
class EventEmitter:
    def __init__(self):
        self._listeners = {}
    
    def on(self, event: str, callback):
        self._listeners.setdefault(event, []).append(callback)
    
    def emit(self, event: str, data=None):
        for callback in self._listeners.get(event, []):
            callback(data)

class TaskManager:
    def __init__(self, service: TaskService):
        self.service = service
        self.events = EventEmitter()
    
    def create(self, title: str) -> Task:
        task = self.service.create_task(title)
        self.events.emit("task:created", task)
        return task
    
    def complete(self, task_id: int) -> Task:
        task = self.service.complete_task(task_id)
        self.events.emit("task:completed", task)
        return task

# === 4. Builder Pattern (fluent API) ===
class QueryBuilder:
    def __init__(self, table: str):
        self._table = table
        self._conditions = []
        self._order = []
        self._limit = None
    
    def where(self, condition: str):
        self._conditions.append(condition)
        return self
    
    def order_by(self, column: str, direction: str = "ASC"):
        self._order.append(f"{column} {direction}")
        return self
    
    def limit(self, n: int):
        self._limit = n
        return self
    
    def build(self) -> str:
        sql = f"SELECT * FROM {self._table}"
        if self._conditions:
            sql += " WHERE " + " AND ".join(self._conditions)
        if self._order:
            sql += " ORDER BY " + ", ".join(self._order)
        if self._limit:
            sql += f" LIMIT {self._limit}"
        return sql

query = (QueryBuilder("tasks")
    .where("status = 'pending'")
    .where("priority > 3")
    .order_by("created_at", "DESC")
    .limit(10)
    .build())
print(query)

# Usage
repo = InMemoryTaskRepository()
service = TaskService(repo)
manager = TaskManager(service)

manager.events.on("task:created", lambda t: print(f"Created: {t.title}"))
manager.events.on("task:completed", lambda t: print(f"Done: {t.title}"))

task = manager.create("Implement login")
manager.complete(task.id)

Testing-Driven Design ใน Agile

TDD workflow สำหรับ Agile Design

#!/usr/bin/env python3
# tdd_example.py — Test-Driven Design Example
import unittest
from dataclasses import dataclass
from typing import List, Optional

# === TDD Cycle: Red -> Green -> Refactor ===

# Step 1: Write a failing test (RED)
# Step 2: Write minimal code to pass (GREEN)
# Step 3: Refactor while keeping tests green (REFACTOR)

# === Shopping Cart TDD Example ===

@dataclass
class CartItem:
    name: str
    price: float
    quantity: int = 1

class ShoppingCart:
    def __init__(self):
        self._items: List[CartItem] = []
    
    def add_item(self, name: str, price: float, quantity: int = 1):
        if price < 0:
            raise ValueError("Price cannot be negative")
        if quantity < 1:
            raise ValueError("Quantity must be at least 1")
        
        for item in self._items:
            if item.name == name:
                item.quantity += quantity
                return
        
        self._items.append(CartItem(name, price, quantity))
    
    def remove_item(self, name: str):
        self._items = [i for i in self._items if i.name != name]
    
    @property
    def item_count(self) -> int:
        return sum(item.quantity for item in self._items)
    
    @property
    def subtotal(self) -> float:
        return sum(item.price * item.quantity for item in self._items)
    
    def apply_discount(self, percent: float) -> float:
        if percent < 0 or percent > 100:
            raise ValueError("Discount must be 0-100")
        discount = self.subtotal * (percent / 100)
        return round(self.subtotal - discount, 2)
    
    @property
    def items(self) -> List[CartItem]:
        return self._items.copy()
    
    def clear(self):
        self._items = []

class TestShoppingCart(unittest.TestCase):
    def setUp(self):
        self.cart = ShoppingCart()
    
    # === Basic functionality ===
    def test_new_cart_is_empty(self):
        self.assertEqual(self.cart.item_count, 0)
        self.assertEqual(self.cart.subtotal, 0)
    
    def test_add_single_item(self):
        self.cart.add_item("Laptop", 35000)
        self.assertEqual(self.cart.item_count, 1)
        self.assertEqual(self.cart.subtotal, 35000)
    
    def test_add_multiple_items(self):
        self.cart.add_item("Laptop", 35000)
        self.cart.add_item("Mouse", 500)
        self.assertEqual(self.cart.item_count, 2)
        self.assertEqual(self.cart.subtotal, 35500)
    
    def test_add_item_with_quantity(self):
        self.cart.add_item("Cable", 200, quantity=3)
        self.assertEqual(self.cart.item_count, 3)
        self.assertEqual(self.cart.subtotal, 600)
    
    def test_add_same_item_increases_quantity(self):
        self.cart.add_item("Pen", 50)
        self.cart.add_item("Pen", 50, quantity=2)
        self.assertEqual(self.cart.item_count, 3)
    
    # === Edge cases ===
    def test_negative_price_raises_error(self):
        with self.assertRaises(ValueError):
            self.cart.add_item("Bad", -100)
    
    def test_zero_quantity_raises_error(self):
        with self.assertRaises(ValueError):
            self.cart.add_item("Bad", 100, quantity=0)
    
    # === Remove items ===
    def test_remove_item(self):
        self.cart.add_item("Laptop", 35000)
        self.cart.add_item("Mouse", 500)
        self.cart.remove_item("Laptop")
        self.assertEqual(self.cart.item_count, 1)
        self.assertEqual(self.cart.subtotal, 500)
    
    def test_remove_nonexistent_item(self):
        self.cart.remove_item("Nothing")
        self.assertEqual(self.cart.item_count, 0)
    
    # === Discount ===
    def test_apply_discount(self):
        self.cart.add_item("Laptop", 10000)
        total = self.cart.apply_discount(10)
        self.assertEqual(total, 9000)
    
    def test_invalid_discount_raises_error(self):
        self.cart.add_item("Item", 1000)
        with self.assertRaises(ValueError):
            self.cart.apply_discount(150)
    
    # === Clear cart ===
    def test_clear_cart(self):
        self.cart.add_item("A", 100)
        self.cart.add_item("B", 200)
        self.cart.clear()
        self.assertEqual(self.cart.item_count, 0)

if __name__ == "__main__":
    unittest.main(verbosity=2)

เครื่องมือและ Workflow สำหรับ Agile Design

เครื่องมือที่ช่วย Agile Design

# === Agile Design Tools and Workflow ===

# 1. Code Quality Tools
# ===================================

# Python: ruff (fast linter + formatter)
pip install ruff
ruff check .                    # Lint
ruff format .                   # Format
ruff check --fix .              # Auto-fix

# Pre-commit hooks
# .pre-commit-config.yaml
# repos:
#   - repo: https://github.com/astral-sh/ruff-pre-commit
#     rev: v0.3.0
#     hooks:
#       - id: ruff
#         args: [--fix]
#       - id: ruff-format
#
#   - repo: https://github.com/pre-commit/mirrors-mypy
#     rev: v1.8.0
#     hooks:
#       - id: mypy

pip install pre-commit
pre-commit install

# 2. Architecture Decision Records (ADRs)
# ===================================
# docs/adr/001-use-postgresql.md
# # 1. Use PostgreSQL as Primary Database
# 
# ## Status: Accepted
# ## Context
# We need a reliable database for our application.
# ## Decision
# Use PostgreSQL for its ACID compliance, JSON support, and ecosystem.
# ## Consequences
# - Need PostgreSQL expertise on team
# - Good tooling and community support
# - May need read replicas for scale

# 3. Sprint Design Workflow
# ===================================
# Sprint Planning:
#   1. Review user stories for the sprint
#   2. Quick design discussion (15-30 min max)
#   3. Identify design patterns needed
#   4. Create technical tasks
#
# During Sprint:
#   1. Write tests first (TDD)
#   2. Implement simplest solution
#   3. Refactor as patterns emerge
#   4. Code review focusing on design
#
# Sprint Review:
#   1. Demo working software
#   2. Review design decisions
#   3. Update ADRs if needed
#   4. Plan refactoring for next sprint

# 4. Refactoring Checklist
# ===================================
# Before refactoring:
# - [ ] All tests pass
# - [ ] Identified code smells
# - [ ] Planned target design
#
# During refactoring:
# - [ ] Small steps (compile/test between each)
# - [ ] One refactoring at a time
# - [ ] Run tests after each change
#
# After refactoring:
# - [ ] All tests still pass
# - [ ] No new functionality added
# - [ ] Code is simpler/cleaner
# - [ ] Committed with descriptive message

# 5. Code Review Checklist for Design
# ===================================
# - [ ] Single Responsibility: Does each class/function do one thing?
# - [ ] Naming: Are names descriptive and consistent?
# - [ ] Dependencies: Are they injected, not hard-coded?
# - [ ] Testability: Can this be tested in isolation?
# - [ ] Duplication: Is there copy-paste code?
# - [ ] Complexity: Is there a simpler approach?
# - [ ] Error handling: Are errors handled appropriately?

echo "Agile Design workflow configured"

FAQ คำถามที่พบบ่อย

Q: Agile Design กับ No Design ต่างกันอย่างไร?

A: Agile Design ไม่ใช่การไม่ออกแบบ แต่เป็นการออกแบบ just enough สำหรับ current iteration ใช้ TDD, refactoring และ design patterns อย่างมีวินัย No Design คือการเขียน code ไปเรื่อยๆ ไม่คิด structure ไม่ refactor ทำให้ได้ code ที่ maintain ยาก Agile Design ให้ code ที่ clean, testable และ maintainable ผ่าน continuous improvement

Q: เมื่อไหร่ควรใช้ Big Design Up Front (BDUF)?

A: BDUF เหมาะสำหรับ systems ที่เปลี่ยนแปลงยาก (embedded systems, hardware interfaces), safety-critical systems (medical devices, aviation), ระบบที่มี regulatory requirements เข้มงวด และ projects ที่ requirements ชัดเจนและไม่เปลี่ยนแปลง สำหรับ web applications, mobile apps และ SaaS products ที่ requirements เปลี่ยนบ่อย Agile Design เหมาะกว่า

Q: จะรู้ได้อย่างไรว่าต้อง refactor?

A: สัญญาณที่บอกว่าต้อง refactor ได้แก่ duplicate code (copy-paste), long methods (>20 lines), large classes (>200 lines), deep nesting (>3 levels), feature envy (class ใช้ data ของ class อื่นมากกว่าของตัวเอง), shotgun surgery (เปลี่ยน feature ต้องแก้หลายที่) และ test difficulty (ยากที่จะเขียน unit test) ใช้ metrics tools เช่น radon (Python) หรือ SonarQube เพื่อวัด code complexity

Q: SOLID Principles ใช้ทุก project ไหม?

A: ไม่จำเป็น สำหรับ scripts เล็กๆ หรือ prototypes SOLID อาจ over-engineering ได้ ใช้ SOLID เมื่อ project มีหลายคนทำ, code จะ maintain ระยะยาว, ต้องการ testability สูง และมี business logic ซับซ้อน เริ่มจาก Single Responsibility ก่อนเพราะให้ผลมากที่สุด แล้วค่อยใช้ principles อื่นเมื่อจำเป็น อย่า apply blindly ทุก principle กับทุก class

📖 บทความที่เกี่ยวข้อง

Webhook Design Pattern Agile Scrum Kanbanอ่านบทความ → WiFi 6E Design Agile Scrum Kanbanอ่านบทความ → การทํางานแบบ agile คืออะไรอ่านบทความ → REST API Design Agile Scrum Kanbanอ่านบทความ → agile methodology คืออะไรอ่านบทความ →

📚 ดูบทความทั้งหมด →