Simple Design
สี่กฎของ Kent Beck สำหรับ design ที่เรียบง่าย
คืออะไร
หัวข้อที่มีชื่อว่า “คืออะไร”Simple Design คือแนวปฏิบัติที่มาจาก Extreme Programming (XP) ในยุค 1990s ซึ่ง Kent Beck เสนอกฎสี่ข้อเพื่อใช้ตัดสินว่า “design นี้เรียบง่ายพอหรือยัง” กฎเหล่านี้บางครั้งถูกเรียกว่า The Four Commandments หรือ XP Simplicity Rules และเรียงตามลำดับความสำคัญจากมากไปน้อย code ควร:
- ผ่าน test ทั้งหมด (Passes all tests)
- สื่อเจตนาของผู้เขียน (Reveals intention)
- ไม่มีความซ้ำซ้อน (No duplication)
- มีจำนวน class method และ module น้อยที่สุด (Fewest elements)
จุดที่สำคัญที่สุดของแนวคิดนี้ไม่ใช่ตัวกฎแต่ละข้อ แต่คือ ลำดับความสำคัญ ระหว่างกฎ Martin Fowler อธิบายไว้ในบทความ BeckDesignRules ว่าลำดับนี้บอกเราว่าเมื่อกฎสองข้อขัดแย้งกัน ควรยึดกฎที่มาก่อนเป็นหลัก และแม้กฎข้อที่ 2 (สื่อเจตนา) กับข้อที่ 3 (ไม่ซ้ำซ้อน) จะดูเหมือนแข่งกันในทางปฏิบัติ แต่ Fowler ชี้ว่าทั้งสองมัก “ป้อนกลับซึ่งกันและกัน” ในการขัดเกลา code ให้ดีขึ้นเรื่อย ๆ มากกว่าจะขัดแย้งกันจริงจัง
Simple Design ไม่ใช่การหลีกเลี่ยง design หรือการเขียน code แบบไม่คิดหน้าคิดหลัง แต่คือวินัยที่คอยถามซ้ำ ๆ ระหว่างทำ TDD และ Refactoring ว่า “ตอนนี้ design เรียบง่ายที่สุดเท่าที่จำเป็นสำหรับ test ที่ผ่านอยู่แล้วหรือยัง” มันจึงเป็นเครื่องมือคานกับแนวโน้มตามธรรมชาติของนักพัฒนาที่มักออกแบบเผื่ออนาคตเกินจำเป็น (over-engineering)
ทำอย่างไร
หัวข้อที่มีชื่อว่า “ทำอย่างไร”กฎข้อที่ 1 — ผ่าน test ทั้งหมด
หากคุณเป็นนักพัฒนาสาย XP วิธีที่คุณรู้ว่า code “ทำงานได้” คือมันผ่าน automated unit test ที่พิสูจน์ว่าถูกต้อง กฎนี้อยู่อันดับหนึ่งเสมอ เพราะไม่ว่า design จะสวยงามแค่ไหน หากมันไม่ทำงานตามที่ตั้งใจ ก็ไร้ค่า นี่คือเหตุผลที่ Simple Design มักถูกฝึกฝนคู่กับวงจร Red-Green-Refactor — เขียน test ให้ผ่านก่อน (Red → Green) แล้วค่อยขัดเกลา design (Refactor) โดยมี test คอยเป็นตาข่ายนิรภัย
กฎข้อที่ 2 — สื่อเจตนาของผู้เขียน
เมื่อ code ทำงานได้แล้ว สิ่งสำคัญรองลงมาคือทำให้มัน ชัดเจน สอดคล้อง และสื่อความหมาย code ลึกลับที่ทำงานได้นั้นมีคุณค่าน้อย เพราะยากที่จะแก้ไขหรือดูแลในอนาคต การตั้งชื่อตัวแปร method และ class ให้สื่อความหมาย (ดู Naming) คือเครื่องมือหลักของกฎข้อนี้ เพื่อให้เจตนานั้นชัดเจนต่อนักพัฒนาในอนาคต รวมถึงตัวผู้เขียนเองเมื่อกลับมาอ่าน code ในอีกหกเดือนข้างหน้า
กฎข้อที่ 3 — ไม่มีความซ้ำซ้อน
กฎนี้คือหลักการ Don’t Repeat Yourself (DRY) ในทางปฏิบัติ Fowler เรียกกฎนี้ว่า “ทรงพลังอย่างแยบยล” (powerfully subtle) เพราะการไล่กำจัดความซ้ำซ้อนอย่างเป็นระบบมักจะ “ขับดัน” (drive out) design ที่ดีออกมาเองโดยธรรมชาติ — เมื่อเห็นตรรกะซ้ำกันสองที่ ให้รวบเข้าไว้ใน method หรือ class แยก แล้วอ้างอิงจากทุกจุดที่เคยซ้ำ นี่เป็นขั้นตอน refactoring ที่พบบ่อยที่สุดขั้นตอนหนึ่ง
แต่หากการทำตามกฎนี้ทำให้ code หยุดทำงาน (ผิดกฎข้อ 1) หรือทำให้เข้าใจยากขึ้น (ผิดกฎข้อ 2) การยอมให้มีความซ้ำซ้อนบ้างในกรณีนั้นสำคัญกว่าการยึด DRY อย่างเคร่งครัด — ความซ้ำซ้อนโดยบังเอิญ (coincidental duplication) ที่มาจากแนวคิดคนละเรื่องกัน ไม่ควรถูกบังคับให้ใช้โครงสร้างร่วมกันเพียงเพราะหน้าตาเหมือนกัน
กฎข้อที่ 4 — มีจำนวนองค์ประกอบน้อยที่สุด
กฎข้อสุดท้ายแทบไม่จำเป็นด้วยซ้ำ — จะสร้าง code มากกว่าที่จำเป็นไปทำไม โดยเฉพาะเมื่อกำจัดความซ้ำซ้อนไปแล้ว แต่สิ่งสำคัญที่สุดที่ต้องจำคือมันมี ลำดับความสำคัญต่ำที่สุด อย่ากังวลว่ากำลังสร้าง class และ method เล็ก ๆ จำนวนมากที่ทำสิ่งเดียวและทำได้ดี ตราบใดที่ไม่ได้ละเมิดกฎสามข้อแรก และหากระหว่างทำตามกฎสามข้อแรกอยู่ คุณพบว่าสามารถลดจำนวนโครงสร้างใน design ลงได้ ก็จงทำเพื่อทำให้ทุกอย่างเรียบง่ายขึ้น กฎนี้คือคำเตือนไม่ให้เผื่อความยืดหยุ่นในอนาคตที่ยังไม่มี test รองรับ ซึ่งสอดคล้องกับหลักการ YAGNI
แผนภาพด้านล่างสรุปว่าเวลามี design ให้ทบทวน ควรไล่ตรวจตามลำดับความสำคัญอย่างไร:
flowchart TD
Start[เริ่มทบทวน design] --> Q1{test ผ่านทั้งหมดไหม}
Q1 -->|ไม่ผ่าน| Fix1[แก้จนผ่านก่อน ยังไม่ต้อง refactor]
Q1 -->|ผ่าน| Q2{code สื่อเจตนาชัดเจนไหม}
Q2 -->|ไม่ชัด| Fix2[ปรับชื่อและโครงสร้างให้สื่อความหมาย]
Q2 -->|ชัดแล้ว| Q3{มีความซ้ำซ้อนไหม}
Q3 -->|มี| Fix3[รวบตรรกะซ้ำเข้าจุดเดียว]
Q3 -->|ไม่มี| Q4{ลดจำนวนองค์ประกอบได้อีกไหม}
Q4 -->|ได้| Fix4[ลบ class method module ที่เกินจำเป็น]
Q4 -->|ไม่ได้แล้ว| Done[design เรียบง่ายเพียงพอ]
Fix2 --> Q3
Fix3 --> Q4
มุมมองของ JB Rainsberger
หัวข้อที่มีชื่อว่า “มุมมองของ JB Rainsberger”JB Rainsberger เสนอมุมมองของเขาเองในชื่อ The Four Elements of Simple Design ซึ่งเรียนรู้มาจากงานของ Kent Beck โดยตรง แต่จัดลำดับความสำคัญต่างออกไปเล็กน้อย: ผ่าน test → ลดความซ้ำซ้อนให้น้อยที่สุด → เพิ่มความชัดเจนให้มากที่สุด → มีองค์ประกอบน้อยที่สุด ความต่างสำคัญคือ Rainsberger ให้ “ลดความซ้ำซ้อน” มาก่อน “เพิ่มความชัดเจน” เพราะความซ้ำซ้อนวัดผลได้ค่อนข้างเป็นวัตถุวิสัย (มองเห็นแล้วเห็นตรงกัน) ในขณะที่ความชัดเจนของชื่อยังเป็นเรื่องอัตวิสัยกว่า เขาสรุปว่าทักษะหลักของการออกแบบเชิงวัตถุแทบทั้งหมดย่อลงเหลือแค่สองอย่าง: การกำจัดความซ้ำซ้อน และการปรับปรุงชื่อให้ดีขึ้น
ตัวอย่าง
หัวข้อที่มีชื่อว่า “ตัวอย่าง”ตัวอย่างต่อไปนี้แสดงการไล่ refactor code ตามลำดับกฎทั้งสี่ข้อ เริ่มจาก code ที่ผ่าน test แต่ยังไม่เรียบง่าย ไปจนถึง design ที่สื่อเจตนา ไม่ซ้ำซ้อน และมีองค์ประกอบน้อยที่สุด
// ก่อน: ผ่าน test (กฎ 1) แต่ตรรกะซ้ำและตั้งชื่อไม่สื่อเจตนาpublic class OrderProcessor{ public decimal CalcA(decimal price, int qty) { decimal total = price * qty; if (total > 1000) total = total - (total * 0.1m); return total; }
public decimal CalcB(decimal price, int qty, bool isVip) { decimal total = price * qty; if (total > 1000) total = total - (total * 0.1m); if (isVip) total = total - (total * 0.05m); return total; }}
// หลัง: สื่อเจตนา (กฎ 2) ไม่ซ้ำซ้อน (กฎ 3)// และรวม2 method เหลือหนึ่งจุดตัดสินใจ (กฎ 4)public class OrderTotalCalculator{ private const decimal BulkOrderThreshold = 1000m; private const decimal BulkDiscountRate = 0.10m; private const decimal VipDiscountRate = 0.05m;
public decimal CalculateTotal(decimal unitPrice, int quantity, bool isVipCustomer = false) { decimal subtotal = unitPrice * quantity; decimal total = ApplyBulkDiscount(subtotal);
if (isVipCustomer) { total = ApplyDiscount(total, VipDiscountRate); }
return total; }
private decimal ApplyBulkDiscount(decimal amount) => amount > BulkOrderThreshold ? ApplyDiscount(amount, BulkDiscountRate) : amount;
private decimal ApplyDiscount(decimal amount, decimal rate) => amount - (amount * rate);}สังเกตว่า code “หลัง” ไม่ได้มี class หรือ interface เพิ่มเผื่ออนาคต (เช่น IDiscountStrategy ที่ยังไม่มี test เรียกร้อง) — นั่นคือการเคารพกฎข้อที่ 4 อย่างเคร่งครัด: เพิ่มโครงสร้างก็ต่อเมื่อ test หรือความซ้ำซ้อนบังคับให้ต้องเพิ่มจริง ๆ ไม่ใช่เพราะ “คิดว่าอาจต้องใช้ในอนาคต”
ประโยชน์และข้อควรระวัง
หัวข้อที่มีชื่อว่า “ประโยชน์และข้อควรระวัง”ประโยชน์
- ให้เกณฑ์ที่เป็นรูปธรรมและเรียงลำดับได้ชัดเจน แทนที่จะถกเถียงว่า “design นี้ดีไหม” อย่างเลื่อนลอย
- ลดต้นทุนการดูแลรักษาในระยะยาว เพราะ code ที่ผ่าน test สื่อเจตนา และไม่ซ้ำซ้อน มักจะแก้ไขและต่อยอดได้ง่ายกว่า
- ทำงานเข้าคู่กับ TDD ได้เป็นอย่างดี — วงจร Red-Green-Refactor คือสนามฝึกกฎทั้งสี่ข้อซ้ำ ๆ ในทุกรอบเล็ก ๆ ของการพัฒนา
- ป้องกันการออกแบบเผื่ออนาคตที่ยังไม่เกิดขึ้นจริง (speculative generality) ซึ่งมักเพิ่มความซับซ้อนโดยไม่จำเป็น
ข้อควรระวัง
- ลำดับความสำคัญมีไว้ให้ใช้เวลากฎขัดแย้งกันเท่านั้น ไม่ใช่ข้ออ้างให้ละเลยกฎข้อหลัง ๆ ไปเลย
- การไล่ตาม “ไม่ซ้ำซ้อน” อย่างสุดโต่งอาจนำไปสู่ความซ้ำซ้อนโดยบังเอิญถูกรวมเข้าด้วยกันผิดที่ผิดทาง กลายเป็นการผูกมัด (coupling) กับ abstraction ที่ผิด ซึ่งแก้ไขยากกว่าความซ้ำซ้อนเสียอีก
- Simple Design ไม่ได้แปลว่า “ง่ายในสายตาแรก” แต่หมายถึงเรียบง่ายที่สุดเท่าที่จำเป็นสำหรับความต้องการที่มี test รองรับอยู่จริง — design ที่ง่ายเกินไปจนขาดโครงสร้างที่จำเป็นก็ผิดกฎข้อ 2 เช่นกัน
- ต้องมีชุด test ที่ครอบคลุมและเชื่อถือได้ก่อน มิฉะนั้นกฎข้อ 1 จะกลายเป็นภาพลวงตา และการ refactor ตามกฎข้ออื่นจะเสี่ยงทำ code พัง