ข้าม​ไป​ยัง​เนื้อหา

Tolerance for Imperfection

ไม่มี​สถาปัตยกรรม​ใด​สมบูรณ์​แบบ ทุก​อย่าง​คือ​การ​แลกเปลี่ยน

หลักการ Tolerance for Imperfection ยอมรับ​ความ​จริง​ที่​ว่า​ไม่มี​สถาปัตยกรรม​ซอฟต์แวร์​ใด​สมบูรณ์​แบบ และ​สถาปนิก​ต้อง​สร้าง​สมดุล​ระหว่าง​ข้อ​กังวล​ที่​ขัดแย้ง​กัน พร้อม​ทำการ​แลกเปลี่ยน (trade-off) มัน​เน้น​ความ​จำเป็น​ใน​การ​จัด​ลำดับ​ความ​สำคัญ​ของ​ข้อ​กำหนด​และ​ข้อ​จำกัด โดย​โฟกัส​ที่​การ​จัดการ​ส่วน​ที่​สำคัญ​ที่สุด​ของ​ระบบ ใน​ขณะ​ที่​ยอมรับ​ว่า​ความ​ไม่​สมบูรณ์​บาง​อย่าง​เป็น​สิ่ง​ที่​หลีก​เลี่ยง​ไม่​ได้

ดัง​คำ​กล่าว​ยอด​นิยม​ที่​มัก​ยก​ให้ Voltaire ว่า “Perfect is the enemy of good” (ความ​สมบูรณ์​แบบ​เป็น​ศัตรู​ของ​ความ​ดี​พอ) และ​พึง​ระลึก​ถึง​กฎ​ข้อ​แรก​ของ​สถาปัตยกรรม​ซอฟต์แวร์​ที่​ว่า “ทุก​อย่าง​คือ​การ​แลกเปลี่ยน” (Everything is a tradeoff)

  • สร้าง​สมดุล​ระหว่าง​ข้อ​กังวล​ที่​ขัดแย้ง​กัน: สถาปนิก​ต้อง​ชั่ง​น้ำ​หนักข้อดี​ข้อ​เสีย​ของ​แต่ละ​แนวทาง โดย​อิง​กับ​ข้อ​กำหนด (requirements) ข้อ​จำกัด (constraints) และ​บริบท​เฉพาะ​ของ project นั้น ๆ ใน​ความ​เป็น​จริง quality attribute requirements (QAR) เช่น performance, security, maintainability, cost มัก​ขัดแย้ง​กันเอง — เพิ่ม replica เพื่อ​ความ​ทนทาน​ก็​ขยาย​พื้นที่​โจมตี​ด้าน​ความ​ปลอดภัย เพิ่ม caching เพื่อ​ความเร็ว​ก็​เพิ่ม​ความ​ซับซ้อน​ด้าน consistency
  • การ​แลกเปลี่ยน (Trade-offs) ต้อง​เปิดเผย​และ​มี​สติ: สถาปนิก​ต้อง​ประนีประนอม​เพื่อ​ออกแบบ​ระบบ​ที่​ตอบ​โจทย์​ข้อ​กำหนด​ภาย​ใต้​ข้อ​จำกัด​ที่​มี จุด​สำคัญ​คือ trade-off ที่​ดี​ต้อง​ถูก “พูด​ออก​มา” และ​บันทึก​ไว้ — ไม่ใช่​ความ​ไม่​สมบูรณ์​ที่​ซ่อน​อยู่​โดย​ไม่มี​ใคร​รู้ตัว การ​ยอมรับ​ความ​ไม่​สมบูรณ์​อย่าง​มี​สติ (deliberate) แตก​ต่าง​จาก​ความ​สะเพร่า​หรือ​มาตรฐาน​ต่ำ (careless) โดย​สิ้นเชิง
  • การ​จัด​ลำดับ​ความ​สำคัญ: โฟกัส​ที่​การ​จัดการ​ส่วน​ที่​สำคัญ​ที่สุด​ของ​ระบบ​ก่อน พร้อม​ตระหนัก​ว่า​ความ​ไม่​สมบูรณ์​บาง​อย่าง​หลีก​เลี่ยง​ไม่​ได้ การ​พยายาม​ให้​ได้​สถาปัตยกรรม​ที่​สมบูรณ์​แบบ​มัก​นำ​ไป​สู่ antipattern ที่​ชื่อ analysis paralysis (อัมพาต​จาก​การ​วิเคราะห์) ซึ่ง​ทีม​ติด​หล่ม​อยู่​กับ​การ​ถกเถียง​หา​ทางออก​ที่ “ดี​ที่สุด” จน​ไม่มี​อะไร​ถูก​ส่ง​มอบ​เลย
  • คำ​โต้แย้ง​เรื่อง​คำ​แปล: วลี “Perfect is the enemy of good” ที่​ยก​ให้ Voltaire แท้จริง​แล้ว​มา​จาก​สุภาษิต​อิตาลี “Il meglio è l’inimico del bene” ซึ่ง​แปล​ตรง​ตัว​ว่า “the best is the enemy of the good” — ความหมาย​เดิม​เน้น​ที่​การ​ไล่​ล่า “สิ่ง​ที่​ดี​ที่สุด” (the best) มากกว่า “ความ​สมบูรณ์​แบบ” (perfect) แต่​แก่น​ของ​หลักการ​ยัง​คง​เดิม คือ​การ​ไล่​ล่า​ทาง​เลือก​ที่​ดี​ที่สุด​ใน​เชิง​ทฤษฎี​อาจ​ทำให้​พลาด​การ​ส่ง​มอบ​สิ่ง​ที่ “ดีพอ” ได้​ตรง​เวลา
  • ป้องกัน analysis paralysis: ทีม​ที่​พยายาม​หา​สถาปัตยกรรม​ที่​ไม่มี​ข้อ​เสีย​เลย​จะ​ติด​หล่ม​อยู่​กับ​การ​ประชุม​ออกแบบ​ไม่รู้​จบ ใน​ขณะ​ที่​คู่แข่ง​ส่ง​มอบ​ของ​จริง​และ​เรียนรู้​จาก​ผู้​ใช้​จริง​ไป​แล้ว
  • trade-off คือ​สมมติฐาน​ที่​ต้อง​พิสูจน์​ด้วย​ของ​จริง: บทความ “Architectural Trade-Offs” ใน InfoQ ชี้​ว่าการ​วิเคราะห์​เชิง​ทฤษฎี​เพียง​อย่าง​เดียว​ไม่​สามารถ​ยืนยัน​ได้​ว่า trade-off ที่​เลือก​นั้น​ถูกต้อง — ต้อง​สร้าง ปล่อย​ใช้งาน​จริง แล้ว​เก็บ feedback มา​ปรับ การ​รอ​ให้​สมบูรณ์​แบบ​ก่อน​ส่ง​มอบ​จึง​เสีย​โอกาส​เรียนรู้​จาก​ของ​จริง
  • การ​ไล่​ล่า​ความ​สมบูรณ์​แบบ​คือ waste: เวลา​และ​ความ​พยายาม​ที่​ทุ่ม​ให้​กับ​ส่วน​ที่​ไม่​สำคัญ (gold plating, over-abstraction สำหรับ requirement ที่​ยัง​ไม่​เกิด​ขึ้น​จริง) คือ​ทรัพยากร​ที่​ควร​ใช้​กับ​ส่วน​ที่​สำคัญ​ที่สุด​ของ​ระบบ​แทน หลักการ​นี้​จึง​เชื่อม​โยง​กับ YAGNI และ Keep It Simple โดยตรง
  • Well-Architected Framework ก็​ยึด​หลัก​นี้​อย่าง​เป็น​ทางการ: Microsoft ระบุ trade-off ระหว่าง pillar ต่าง ๆ ไว้​อย่าง​ชัดเจน เช่น Reliability (ที่​มักได้​มา​จาก replica) กับ Security (ที่​ต้องการ​พื้นที่​โจมตี​น้อย​ที่สุด) ขัดแย้ง​กัน​โดย​ธรรมชาติ — ไม่มี​สถาปัตยกรรม​ใด​ตอบ​โจทย์​ทุก pillar ได้​เต็ม​ร้อย​พร้อม​กัน สถาปนิก​ต้อง​เลือก​และ​บันทึก​เหตุผล
  • ความ​ไม่​สมบูรณ์​แบบ​ที่​รู้ตัว ปลอดภัย​กว่า​ที่​ไม่รู้ตัว: ระบบ​ที่​ประกาศ​ขอบเขต​ชัดเจน​ว่า “รองรับ​กรณี​นี้ ไม่​รองรับ​กรณี​นั้น” และ fail fast เมื่อ​เจอ​กรณี​นอก​ขอบเขต ปลอดภัย​กว่า​ระบบ​ที่​แสร้ง​ทำ​เป็น​รองรับ​ทุก​อย่าง​แต่​จริง ๆ แล้ว​มี bug แฝง​อยู่

ทีม​กำลัง​สร้าง feature คำนวณ​ส่วนลด​คำ​สั่ง​ซื้อ แล้ว​เถียง​กัน​ว่า​จะ​ต้อง​รองรับ​ทุก​สกุล​เงิน​และ​กฎ​การ​ปัดเศษ​ที่​เป็น​ไป​ได้​ใน​โลก​ก่อน​หรือ​ไม่ ก่อน​จะ​ปล่อย feature นี้​ออก​ไป​แม้แต่​บรรทัด​เดียว

// ไม่ดี: ไล่ล่าความสมบูรณ์แบบ — พยายามครอบคลุมทุกสกุลเงินและทุกกฎการปัดเศษ
// ที่จินตนาการได้ตั้งแต่ยังไม่รู้ด้วยซ้ำว่าลูกค้าใช้สกุลเงินอะไรบ้าง
// ผลคือ feature ง่าย ๆ ถูกดองไว้หลายสัปดาห์เพื่อรอ "การออกแบบที่สมบูรณ์"
public interface ICurrencyRoundingStrategy
{
decimal Round(decimal amount, string currencyCode, RoundingMode mode, CultureInfo culture);
}
public class UniversalDiscountCalculator
{
private readonly IDictionary<string, ICurrencyRoundingStrategy> _strategies;
// ต้อง implement กลยุทธ์ปัดเศษให้ครบทุกสกุลเงินในระบบ ISO 4217
// ก่อนที่จะยอมให้ code ส่วนนี้ผ่าน code review ได้
public decimal Calculate(Order order, Discount discount)
{
var strategy = _strategies[order.CurrencyCode]; // ล้มเงียบ ๆ ถ้าไม่มีสกุลเงินนั้นใน dictionary
var discounted = order.Total * (1 - discount.Percent);
return strategy.Round(discounted, order.CurrencyCode, RoundingMode.BankersRounding, order.Culture);
}
}
// ดี: ยอมรับความไม่สมบูรณ์อย่างมีสติและเปิดเผย — รองรับเฉพาะสกุลเงินที่ระบบ
// ใช้งานจริงตอนนี้ (THB, USD) แล้ว fail fast พร้อมข้อความชัดเจนสำหรับกรณี
// ที่ยังไม่รองรับ แทนที่จะพยายามครอบคลุมทุกกรณีตั้งแต่ต้น
public class DiscountCalculator
{
private static readonly HashSet<string> SupportedCurrencies = new() { "THB", "USD" };
public decimal Calculate(Order order, Discount discount)
{
if (!SupportedCurrencies.Contains(order.CurrencyCode))
{
// ข้อจำกัดที่รู้ตัวและบันทึกไว้ ไม่ใช่ bug ที่ซ่อนอยู่
// อ้างอิง TODO-123 สำหรับแผนขยายสกุลเงินเมื่อมีความต้องการจริง
throw new NotSupportedException(
$"ยังไม่รองรับสกุลเงิน {order.CurrencyCode} — ดู TODO-123");
}
var discounted = order.Total * (1 - discount.Percent);
return Math.Round(discounted, 2, MidpointRounding.ToEven);
}
}

version ที่​สอง​ไม่​ได้ “สมบูรณ์​แบบ” กว่า version แรก​ใน​เชิง​ทฤษฎี แต่​มัน​ส่ง​มอบ​คุณค่า​ได้​ทันที มี​ขอบเขต​ชัดเจน และ fail fast อย่าง​ปลอดภัย​เมื่อ​เจอ​กรณี​นอก​ขอบเขต — เป็นการ​แลกเปลี่ยน​ที่​จัด​ลำดับ​ความ​สำคัญ​ถูกต้อง แทนที่​จะ​รอ​ความ​ครบถ้วน​สมบูรณ์​ที่​อาจ​ไม่มี​วัน​มา​ถึง

กระบวนการ​ตัดสิน​ใจ​แบบ​นี้​สรุป​ได้​เป็น​วงจร ไม่ใช่​จุดจบ​ครั้ง​เดียว:

flowchart LR
    A[ระบุข้อกำหนดและข้อจำกัด] --> B{ขัดแย้งกันหรือไม่}
    B -->|ไม่ขัดแย้ง| C[ออกแบบตรงไปตรงมา]
    B -->|ขัดแย้ง| D[จัดลำดับความสำคัญ]
    D --> E[เลือก trade-off ที่ยอมรับได้]
    E --> F[บันทึกความไม่สมบูรณ์ที่รู้ตัวไว้]
    F --> G[ส่งมอบและเรียนรู้จากของจริง]
    G --> D
  • ทีม​ติด​อยู่​ใน analysis paralysis: ประชุม​ออกแบบ​ซ้ำ​แล้ว​ซ้ำ​เล่า​เพื่อ​หา​ทางออก​ที่ “ไม่มี​ข้อ​เสีย​เลย” แต่​ไม่มี code หรือ feature ถูก​ส่ง​มอบ​ออก​ไป​จริง
  • Gold plating: ทุ่ม​เวลา​สร้าง abstraction หรือ​ความ​สามารถ​ที่​ยัง​ไม่มี​ใคร​ร้องขอ “เผื่อ​อนาคต” ทั้ง​ที่ requirement ปัจจุบัน​เรียบ​ง่าย​กว่า​นั้น​มาก (สัญญาณ​เดียว​กับ​การ​ละเมิด YAGNI)
  • ปฏิเสธ​ที่​จะ​ยอมรับ​ว่า​มี trade-off: พูด​หรือ​เขียน​เอกสาร​ราวกับ​ว่า​ทางออก​ที่​เลือก​ไม่มี​ข้อ​เสีย​เลย แทนที่​จะ​ระบุ​อย่าง​ตรง​ไป​ตรง​มา​ว่า​เลือก​อะไร​และ​ยอม​สละ​อะไร​ไป
  • ความ​ไม่​สมบูรณ์​ที่​ซ่อน​อยู่​แทนที่​จะ​เปิดเผย: code ที่​แสร้ง​ทำ​เป็น​รองรับ​ทุก​กรณี (เช่น catch-all แบบ​เงียบ ๆ) แต่​จริง ๆ แล้ว​มี bug แฝง แทนที่​จะ​ประกาศ​ขอบเขต​ชัดเจน​และ fail fast — นี่​ไม่ใช่ tolerance for imperfection แต่​เป็น​ความ​สะเพร่า​ที่​ปลอม​ตัว​มา
  • ไล่​ตาม 100% test coverage หรือ zero technical debt ก่อน ship: เป้าหมาย​เชิง​ตัวเลข​ที่​สมบูรณ์​แบบ​มัก​ไม่​คุ้ม​กับ​ต้นทุน​เวลา​เมื่อ​เทียบ​กับ​การ​โฟกัส​ส่วน​ที่​สำคัญ​ที่สุด​ของ​ระบบ
  • Refactor ส่วน​ที่​ทำงาน​ได้​ดี​อยู่​แล้ว​ซ้ำ​ไป​ซ้ำ​มา เพื่อ​ไล่​ตาม​อุดมคติ​ทาง​สถาปัตยกรรม​โดย​ไม่มี​คุณค่า​ทาง​ธุรกิจ​รองรับ