it

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

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

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

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

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 ที่คาดเดา

เนื้อหาเกี่ยวข้อง — ดูเพิ่มเติมเรื่อง Linux Perf Tools Troubleshooting แก้ปัญหา

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

แนะนำเพิ่มเติม — SiamCafeBook

#!/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

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

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

เนื้อหาเกี่ยวข้อง — บทความที่เกี่ยวข้อง: Linux Systemd Advanced Message Queue Design

#!/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 ต่างกันอย่างไร?

แนะนำเพิ่มเติม — แหล่งความรู้ Forex iCafeForex

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

เนื้อหาเกี่ยวข้อง — อ่านต่อ: C# Minimal API Remote Work Setup

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?

เนื้อหาเกี่ยวข้อง — บทความที่เกี่ยวข้อง: Kotlin Coroutines Blue Green Canary Deploy —

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

XM Legend · เทรดเดอร์ & ผู้สอน Forex 13 ปี

ผู้ก่อตั้ง SiamCafe ตั้งแต่ปี 1997 · เทรดเดอร์สาย Forex มากกว่า 13 ปี ได้รับการยกย่องเป็น XM Legend · แบ่งปันความรู้ Forex, ไอที, AI และการเทรด จากประสบการณ์จริงในตลาดจริง