

SOLID, DRY, KISS, YAGNI — หลักการออกแบบโค้ดที่ทีมควรรู้
รู้จักหลักการออกแบบโค้ดที่ทีมพูดถึงบ่อยที่สุด ทั้ง SOLID, DRY, KISS, YAGNI พร้อมตัวอย่างจริงและข้อควรระวังเมื่อนำมาใช้ร่วมกัน
เคยไหม — แก้บั๊กง่าย ๆ แต่ต้องนั่งไล่โค้ดอยู่ครึ่งวัน ไม่รู้ว่า 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.deletedpython🚫 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 logicpythonถ้าตอนนี้มีแค่ 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): ...pythonO — 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.9pythonL — 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): ...pythonD — 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 = dbpython⚖️ เมื่อหลักการขัดกัน#
สิ่งที่น่าสนใจกว่าการรู้แต่ละ 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 ครั้งหน้า แล้วดูว่าทีมคุยกันง่ายขึ้นไหม
😎 ☕