Devfavor

Back

SOLID, DRY, KISS, YAGNI — หลักการออกแบบโค้ดที่ทีมควรรู้Blur image

เคยไหม — แก้บั๊กง่าย ๆ แต่ต้องนั่งไล่โค้ดอยู่ครึ่งวัน ไม่รู้ว่า logic นี้อยู่ที่ไหน หรือทำไมคนเขียนถึงทำแบบนี้ ปัญหาเหล่านี้ส่วนมากไม่ได้เกิดจากขาดทักษะ แต่เกิดจากไม่มี shared mental model ว่าโค้ดที่ “ดี” ควรเป็นยังไง

หลักการอย่าง SOLID, DRY, KISS, YAGNI มีไว้เพื่อตอบคำถามนั้น — ไม่ใช่กฎตายตัว แต่เป็น vocabulary กลางที่ทีมใช้ตัดสินใจร่วมกันได้


🔁 DRY — อย่าให้ความรู้มีหลายแหล่ง#

DRY ย่อมาจาก Don’t Repeat Yourself นิยามจริง ๆ คือ:

“Every piece of knowledge must have a single, unambiguous, authoritative representation within a system”

สังเกตว่าใช้คำว่า knowledge ไม่ใช่ code — ความต่างนี้ดูเล็ก แต่เปลี่ยนวิธีคิดไปเลย

ถ้าคัดลอกโค้ดบวกเลขแบบเดิมสองที่ นั่นคือการซ้ำ code แต่ถ้า discount VIP กับค่าจัดส่งพิเศษบังเอิญเขียนออกมาหน้าตาเหมือนกัน — นั่นไม่ใช่ DRY violation เพราะเป็นความรู้คนละเรื่องกัน การดึงไป abstract รวมกันอาจทำให้แก้ทีหลังลำบากกว่าเดิม

# สิ่งที่คนเข้าใจผิดว่าต้อง DRY
def apply_vip_discount(price):
    return price * 0.9  # ลด 10%

def apply_shipping_discount(price):
    return price * 0.9  # ลด 10% เหมือนกัน

# ดูเหมือนซ้ำ แต่ความรู้คนละชุด — วันนึงอาจเปลี่ยนแยกกัน
python

✨ KISS — ความเรียบง่ายคือเป้าหมาย ไม่ใช่ผลพลอยได้#

KISS ย่อมาจาก Keep It Simple, Stupid — ถ้ามีสองวิธีทำสิ่งเดียวกัน เลือกวิธีที่เข้าใจง่ายกว่า

ที่หลายคนเข้าใจผิดคือ KISS ไม่ได้แปลว่า “เขียนโค้ดน้อย ๆ” แต่แปลว่า “อย่าเพิ่ม complexity โดยไม่จำเป็น” ถ้า solution ต้องมีหลาย step แต่ละ step ชัดเจนและมีเหตุผล นั่นก็ยัง simple อยู่

# ไม่จำเป็นต้อง clever
users = [u for u in get_all_users() if u.active and u.verified and not u.deleted]

# ถ้าต้องคิดนาน เขียนให้ชัดกว่านี้ก็ไม่เสียหาย
def get_eligible_users():
    all_users = get_all_users()
    return [u for u in all_users if is_eligible(u)]

def is_eligible(user):
    return user.active and user.verified and not user.deleted
python

🚫 YAGNI — อย่าสร้างสิ่งที่ยังไม่ต้องการ#

YAGNI ย่อมาจาก You Aren’t Gonna Need It — อย่า implement feature หรือ abstraction ที่ “น่าจะมีประโยชน์ในอนาคต” ถ้าตอนนี้ยังไม่มี requirement จริง ๆ

เหตุผลคือ code ที่เขียนแล้วต้องดูแล ทดสอบ และทำความเข้าใจ ถ้าโค้ดส่วนนั้นไม่ถูกใช้งาน ก็มีแต่จะเพิ่ม cognitive load ให้ทีม

# YAGNI violation — เขียน plugin system ทั้งที่มีแค่ use case เดียว
class PaymentProcessor:
    def __init__(self):
        self.plugins = []  # "เผื่อ" ต่อยอด

    def register_plugin(self, plugin):
        self.plugins.append(plugin)

    def process(self, amount):
        for plugin in self.plugins:
            plugin.before_process(amount)
        # ... actual logic
python

ถ้าตอนนี้มีแค่ Stripe และไม่มี roadmap จะเพิ่ม gateway อื่น — plugin system นี้แค่เพิ่มโค้ดที่ไม่มีใครใช้

YAGNI บอกว่าอย่าสร้างสิ่งที่ยังไม่ต้องการ — แต่สิ่งที่ต้องสร้างจริง ๆ ควรจัดโครงสร้างยังไง? นั่นคือสิ่งที่ SOLID ตอบ


🧱 SOLID — ห้าหลักการที่ทำให้ OOP มีทิศทาง#

SOLID ประกอบด้วยห้าหลักการสำหรับการออกแบบโค้ดเชิงวัตถุ (OOP) ให้มีทิศทางและดูแลรักษาได้ง่าย

S — Single Responsibility Principle#

class ควรมีหน้าที่เดียว ถ้าเราเปลี่ยน business logic แล้ว class นั้นต้องเปลี่ยน — โอเค แต่ถ้าเราเปลี่ยน format การ log แล้ว class เดิมต้องเปลี่ยนด้วย — นั่นแสดงว่า class นี้ทำสองอย่างในตัวเดียวกัน

# ปัญหา: Order class รู้จักทั้ง business logic และ persistence
class Order:
    def calculate_total(self): ...
    def save_to_database(self): ...  # ไม่ใช่หน้าที่ของ Order

# แยกให้ถูก responsibility
class Order:
    def calculate_total(self): ...

class OrderRepository:
    def save(self, order: Order): ...
python

O — Open/Closed Principle#

ถ้าจะเพิ่ม behavior ใหม่ ควรทำได้โดยไม่ต้องแก้โค้ดเดิม — open for extension, closed for modification

# ไม่ดี: ต้องแก้ calculate_discount ทุกครั้งที่เพิ่ม customer type
def calculate_discount(customer_type, price):
    if customer_type == "vip":
        return price * 0.9
    elif customer_type == "member":
        return price * 0.95

# ดีกว่า: extend ผ่าน subclass ได้เลย
class DiscountStrategy:
    def apply(self, price): ...

class VIPDiscount(DiscountStrategy):
    def apply(self, price):
        return price * 0.9
python

L — Liskov Substitution Principle#

subtype ต้องใช้แทน base type ได้โดยไม่ทำให้ behavior ของ program เปลี่ยนไป

ถ้า function รับ Bird แล้วเรียก .fly() — Penguin ที่ extend Bird แต่ throw exception ตอน fly นั่นคือ LSP violation

class Bird:
    def fly(self): ...

class Penguin(Bird):
    def fly(self):
        raise NotImplementedError("Penguins can't fly")
        # LSP violation — ใช้แทน Bird ไม่ได้จริง ๆ
python

วิธีแก้คือ redesign hierarchy — แยก FlyingBird ออกมา หรือไม่ให้ Bird มี fly() ตั้งแต่แรก

I — Interface Segregation Principle#

Interface ที่ใหญ่เกินไปบังคับให้ class ต้อง implement method ที่ไม่เกี่ยวกับตัวเอง หลักการนี้บอกว่า “clients should not be forced to depend upon interfaces they do not use” — แนวทางคือแยก interface ให้เล็กลง

# ใหญ่เกิน
class Worker:
    def work(self): ...
    def eat(self): ...   # Robot ไม่ต้อง eat

# แยกออก
class Workable:
    def work(self): ...

class Eatable:
    def eat(self): ...

class Human(Workable, Eatable): ...
class Robot(Workable): ...
python

D — Dependency Inversion Principle#

High-level module ที่ผูกติดกับ low-level module โดยตรงทำให้เปลี่ยน implementation ได้ยาก หลักการคือ depend upon abstractions, not concretions ทั้งสองฝั่งควร depend on abstraction แทน

# ไม่ดี: OrderService ผูกติดกับ MySQLDatabase โดยตรง
class OrderService:
    def __init__(self):
        self.db = MySQLDatabase()

# ดีกว่า: inject dependency ผ่าน abstraction
class OrderService:
    def __init__(self, db: DatabaseInterface):
        self.db = db
python

⚖️ เมื่อหลักการขัดกัน#

สิ่งที่น่าสนใจกว่าการรู้แต่ละ principle คือรู้ว่าหลักการเหล่านี้ขัดกันได้ยังไง

DRY vs YAGNI — การ abstract ก่อนมี use case ที่สองมักละเมิด YAGNI แม้จะดูเหมือนทำ DRY — Rule of Three เป็นแนวทางหนึ่งที่บอกว่าควรรอให้เห็น pattern ซ้ำสามครั้งก่อนจะ abstract

DRY vs KISS — บางครั้ง abstraction ที่สร้างเพื่อลดการซ้ำ ทำให้โค้ดอ่านยากขึ้นโดยไม่จำเป็น โดยเฉพาะถ้า use case ยังไม่หลากหลายพอ

SRP vs YAGNI — การแยก class ตั้งแต่เนิ่น ๆ เพราะ “น่าจะต้องการทีหลัง” คือ over-engineering — หลักการเหล่านี้ไม่ใช่กฎตายตัว แต่เป็นแนวทาง

ไม่มีสูตรตายตัวว่าต้องทำยังไง — context ของ codebase, ขนาดทีม, และ stage ของ product มีผลทั้งนั้น ดูเหมือนจะซับซ้อน แต่ไม่จำเป็นต้องเริ่มจากทุกอย่างพร้อมกัน


🚀 จะเริ่มต้นยังไง#

ถ้าอยากลองนำไปใช้จริง ไม่จำเป็นต้อง adopt ทั้งหมดพร้อมกัน เลือกสักหนึ่งอย่างที่ทีมเจอปัญหาบ่อยที่สุด แล้วเริ่มจากตรงนั้น

Code review เป็นพื้นที่ที่ดีมากสำหรับการพูดคุยเหล่านี้ — ไม่ใช่เพื่อจับผิด แต่เพื่อให้ทีมค่อย ๆ build shared vocabulary ขึ้นมาด้วยกัน

หลักการพวกนี้มีประโยชน์ที่สุดเมื่อทีมใช้เป็นภาษากลางในการพูดคุย ไม่ใช่ checklist ที่ต้องผ่านทุกข้อ ลองเอาไปพูดใน code review ครั้งหน้า แล้วดูว่าทีมคุยกันง่ายขึ้นไหม

😎 ☕

Code, coffee, and calm — the holy trinity of a good day. ☕

SOLID, DRY, KISS, YAGNI — หลักการออกแบบโค้ดที่ทีมควรรู้
Author Coffee Stack
Published at 21 มิถุนายน 2026