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

Gall’s Law

ระบบ​ซับซ้อน​ที่​ใช้งาน​ได้ ล้วน​วิวัฒน์​มา​จาก​ระบบ​ง่าย ๆ ที่​ใช้งาน​ได้

Gall’s Law เป็น​ข้อสังเกต​เกี่ยว​กับ​ธรรมชาติ​และ​วิวัฒนาการ​ของ​ระบบ​ซับซ้อน มา​จาก​หนังสือ Systemantics: How Systems Work and Especially How They Fail (1975) ของ John Gall กุมาร​แพทย์​และ​นัก​เขียน​ชาว​อเมริกัน ซึ่ง​ภายหลัง​ตี​พิมพ์​ซ้ำ​ใน​ชื่อ The Systems Bible

“A complex system that works is invariably found to have evolved from a simple system that worked. A complex system designed from scratch never works and cannot be patched up to make it work. You have to start over, beginning with a working simple system.”

— John Gall, Systemantics

แปล​ใจความ: “ระบบ​ซับซ้อน​ที่​ทำงาน​ได้ ล้วน​พบ​ว่า​วิวัฒน์​มา​จาก​ระบบ​ง่าย ๆ ที่​ทำงาน​ได้​เสมอ ระบบ​ซับซ้อน​ที่​ออกแบบ​จาก​ศูนย์​ไม่มี​ทาง​ทำงาน​ได้ และ​แก้​ให้​ใช้ได้​ก็​ไม่​ได้ คุณ​ต้อง​เริ่ม​ใหม่​ด้วย​ระบบ​ง่าย ๆ ที่​ทำงาน​ได้เสีย​ก่อน”

Gall ไม่​ได้​เขียน​เรื่อง​นี้​จาก​มุมมอง​วิศวกรรม​ซอฟต์แวร์​โดยตรง แต่​จาก​การ​สังเกต​ระบบ​ราชการ องค์กร และ​ระบบ​วิศวกรรม​โดย​ทั่วไป​ว่า​ทำไม​จึง​มัก​ล้มเหลว ทว่า​นัก​ออกแบบ​ระบบ​ซอฟต์แวร์ — โดย​เฉพาะ Grady Booch ที่​หยิบยก​กฎ​นี้​มา​อ้างอิง​ตั้งแต่​ปี 1991 — ทำให้ Gall’s Law กลาย​เป็น​หลัก​คิด​หลัก​ใน​วงการ​วิศวกรรม​ซอฟต์แวร์​และ​สถาปัตยกรรม​ระบบ​สมัย​ใหม่

หนังสือ​ของ Gall ตี​พิมพ์​ครั้ง​แรก​ใน​ปี 1975 ใน​ชื่อ General Systemantics ก่อน​จะ​ขยาย​และ​ตี​พิมพ์​ซ้ำ​ใน​ปี 1986 ใน​ชื่อ Systemantics: The Underground Text of Systems Lore และ​อีก​ครั้ง​ใน​ปี 2002 ใน​ชื่อ The Systems Bible งาน​ชิ้น​นี้​ถูก​แปล​เป็น​ภาษา​สเปน เยอรมัน ฮีบรู และ​ญี่ปุ่น และ​มี​อิทธิพล​ต่อ​นัก​คิด​เชิง​ระบบ​คน​สำคัญ​อย่าง Mario Bunge, Paul Watzlawick และ Russell Ackoff แม้​เนื้อหา​ส่วน​ใหญ่​ใน​หนังสือ​จะ​เขียน​ด้วย​น้ำเสียง​เสียดสี​องค์กร​และ​ระบบ​ราชการ แต่​ข้อสังเกต​แกน​กลาง​เรื่อง​วิวัฒนาการ​ของ​ความ​ซับซ้อน​กลับ​สอดคล้อง​อย่าง​ลึกซึ้ง​กับ​แนวทาง agile และ extreme programming ที่​เกิด​ขึ้น​ภายหลัง

กฎ​นี้​ตั้ง​อยู่​บน​ข้อสังเกต​เชิง​ประจักษ์ (empirical observation) ไม่ใช่​ทฤษฎี​ที่​พิสูจน์​ได้​ทาง​คณิตศาสตร์ แต่​สะท้อน​รูปแบบ​ที่​พบ​ซ้ำ ๆ ใน​ระบบ​ที่​ประสบ​ความ​สำเร็จ:

  • ระบบ​ซับซ้อน​ต้อง​ผ่าน​การ​ทดสอบ​กับ​ความ​จริง​ที​ละ​ขั้น ระบบ​ที่​ออกแบบ​จาก​ศูนย์​โดย​ไม่​เคย​รัน​จริง​เลย ไม่มี​ทาง​คาด​เดา​ได้​ครบ​ทุก​กรณี​ขอบ (edge case) ทุก​ปฏิสัมพันธ์​ระหว่าง​ส่วนประกอบ หรือ​ทุก​ความ​ต้องการ​ที่แท้​จริง​ของ​ผู้​ใช้
  • โครงสร้าง​ที่​ใช้งาน​ได้​ใน​ระบบ​ง่าย เป็น​ฐาน​ที่​ระบบ​ซับซ้อน​สร้าง​ต่อ​ได้ เมื่อ​ระบบ​ง่าย​ทำงาน​ได้​จริง มัน​พิสูจน์​แล้ว​ว่า​ส่วนประกอบ​พื้นฐาน​ถูกต้อง จาก​นั้น​ค่อย​เพิ่ม​ความ​ซับซ้อน​ที​ละ​ชั้น​โดย​ยัง​คง​มี​ระบบ​ที่​ใช้งาน​ได้​เสมอ​ใน​ทุก​ขั้นตอน
  • นี่​คือ​ข้อ​โต้แย้ง​ต่อ Big Design Up Front (BDUF) — การ​พยายาม​ออกแบบ​ทุก​ส่วน​ของ​ระบบ​ใหญ่​ให้​เสร็จ​สมบูรณ์​ก่อน​เริ่ม​ลงมือ​สร้าง มัก​จบ​ด้วย​ระบบ​ที่​ซับซ้อน​เกิน​จะ debug หรือ​แก้ไข​ได้ เพราะ​ไม่​เคย​ผ่าน​การ​พิสูจน์​ที​ละ​ส่วน​กับ​โลก​จริง​เลย
  • ข้อสังเกต​นี้​อธิบาย​ทั้ง​ความ​สำเร็จ​และ​ความ​ล้มเหลว ตัวอย่าง​คลาสสิก​ที่​มัก​ถูก​อ้าง​ถึง​คือ World Wide Web ที่​เริ่ม​จากระบบไฮเปอร์​เท็กซ์​เรียบ​ง่าย​แล้ว​ค่อย​ขยาย​จน​ซับซ้อน​อย่าง​ทุก​วัน​นี้ เทียบ​กับ CORBA ซึ่ง​เริ่มต้น​ด้วย​ข้อ​กำหนด (specification) ที่​ซับซ้อน​ตั้งแต่​ต้น​และ​ประสบ​ปัญหา​การ​ยอมรับ​ใช้งาน​อย่าง​กว้าง​ขวาง

สิ่ง​สำคัญ​คือ Gall’s Law ไม่​ได้​บอกว่า “อย่า​สร้าง​ระบบ​ซับซ้อน” แต่​บอกว่า เส้นทาง​ไป​สู่​ระบบ​ซับซ้อน​ที่​ทำงาน​ได้​ต้อง​ผ่าน​ระบบ​ง่าย​ที่​ทำงาน​ได้​ก่อน​เสมอ — ไม่มี​ทาง​ลัด​ข้าม​ขั้นตอน​นี้​ไป​ได้

อีก​มุม​หนึ่ง​ที่​ควร​ทำความ​เข้าใจ​คือ ทำไม​การ​ออกแบบ​ระบบ​ซับซ้อน​จาก​ศูนย์ (design from scratch) จึง​ล้มเหลว​ซ้ำ ๆ ใน​ทาง​ปฏิบัติ เหตุผล​ไม่ใช่​เพราะ​นัก​ออกแบบ​ไม่​เก่ง​พอ แต่​เพราะ:

  1. ความ​ซับซ้อน​ของ​ระบบ​เติบโต​แบบ combinatorial จำนวน​ปฏิสัมพันธ์​ที่​เป็น​ไป​ได้​ระหว่าง​ส่วนประกอบ​เพิ่ม​ขึ้น​เร็ว​กว่า​จำนวน​ส่วนประกอบ​มาก การ​คาด​การณ์​ทุก​กรณี​ล่วงหน้า​จึง​เป็น​ไป​ไม่​ได้​ใน​ทาง​ปฏิบัติ แม้​จะ​มี​สเปค​ที่​ละเอียด​เพียง​ใด
  2. ข้อ​กำหนด (requirement) ที่แท้​จริง​เผย​ตัว​ออก​มา​ก็​ต่อ​เมื่อ​มี​คนใช้งาน​ระบบ​จริง ไม่​ว่า​จะ​ระดม​สมอง​หรือ​ทำ requirement-gathering ละเอียด​แค่​ไหน ก็​ไม่มี​ทาง​รู้​ทุก​อย่าง​ก่อน​ที่​ผู้​ใช้​จริง​จะ​ได้​สัมผัส​ระบบ
  3. ระบบ​ง่าย​ที่​ทำงาน​ได้​ทำ​หน้าที่​เป็น “หลักฐาน​เชิง​ประจักษ์” ที​ละ​ชั้น ทุก​ครั้ง​ที่​เพิ่ม​ความ​ซับซ้อน​แล้ว​ยัง​คง​ทำงาน​ได้ นั่น​คือ​การ​พิสูจน์​สมมติฐาน​ของ​ชั้น​ก่อนหน้า สะสม​ความ​มั่นใจ​ไป​ที​ละ​ขั้น ต่าง​จาก​การ​ออกแบบ​ทั้ง​ระบบ​ที​เดียว​ที่​ไม่มี​จุด​ตรวจสอบ​ระหว่าง​ทาง​เลย

Monolith First ใน​สถาปัตยกรรม microservices — Martin Fowler เสนอ​แนวคิด “Monolith First” ไว้​ใน​บทความ​ชื่อ​เดียวกัน โดย​ชี้​ว่า​เรื่องราว​ความ​สำเร็จ​ของ microservices เกือบ​ทั้งหมด​เริ่ม​จาก monolith ที่​ใช้งาน​ได้​จริง​แล้ว​โต​จน​ต้อง​แตก​ออก​เป็น​บริการ​ย่อย ใน​ขณะ​ที่​ระบบ​ซึ่ง​ถูก​ออกแบบ​เป็น microservices ตั้งแต่​วัน​แรก​มัก​จบ​ลง​ด้วย​ปัญหา​ใหญ่ เหตุผล​หลัก​สอง​ข้อ​คือ (1) ทีม​งาน​ยุค​เริ่มต้น​ต้องการ​วงจร feedback ที่​เร็ว การ​จัดการ​ความ​ซับซ้อน​เชิง operation ของ microservices จะ​ถ่วง​ความเร็ว​นั้น และ (2) การ​กำหนด​ขอบเขต​บริการ (service boundary) ที่​ถูกต้อง​เป็น​เรื่อง​ยาก​แม้แต่​สำหรับ​สถาปนิก​ผู้เชี่ยวชาญ — การ​มี monolith ที่​ทำงาน​ได้​ก่อน​ช่วย​ให้​ทีม​ค้น​พบ​ขอบเขต​ที่แท้​จริง​จาก​การ​ใช้งาน​จริง ก่อน​จะ​ผูกมัด​กับ​การ​แยก​บริการ

World Wide Web เทียบ​กับ CORBA — เว็บ​เริ่มต้น​จาก​โพรโทคอล HTTP และ HTML ที่​เรียบ​ง่าย​มาก​ใน​ปี 1991 แล้ว​ค่อย​เติบโต​ที​ละ​องค์​ประกอบ (CSS, JavaScript, REST APIs, ฯลฯ) จน​กลาย​เป็น​โครงสร้าง​พื้นฐาน​ที่​ซับซ้อน​ที่สุด​ระบบ​หนึ่ง​ของ​โลก ใน​ทาง​ตรง​ข้าม CORBA (Common Object Request Broker Architecture) ถูก​ออกแบบ​ด้วย​ข้อ​กำหนด​ที่​ซับซ้อน​ตั้งแต่​เริ่มต้น และ​แม้​จะ​ได้​รับ​การ​สนับสนุน​จาก​อุตสาหกรรม​อย่าง​มาก ก็​ไม่​เคย​ได้​รับ​ความ​นิยม​ใน​วง​กว้าง​เท่า​เว็บ

MVP (Minimum Viable Product) ใน​การ​พัฒนา​ผลิตภัณฑ์ — การ​เริ่มต้น​ด้วย​ผลิตภัณฑ์​ขั้น​ต่ำ​ที่​ใช้งาน​ได้​จริง แล้ว​ปล่อย​ให้​ผู้​ใช้​จริง​เป็น​ตัว​ขับ​เคลื่อน​การ​เพิ่ม feature เป็นการ​ประยุกต์​ใช้ Gall’s Law โดยตรง​ใน​บริบท​ของ product development — ตัวอย่าง​ที่​มัก​ถูก​ยก​มา​คือ Facebook ที่​เริ่ม​จาก​ระบบ​โปร file นัก​ศึกษาฮาร์​วาร์ด​เรียบ​ง่าย ก่อน​ขยาย​เป็น platform ระดับ​โลก

การ​เติบโต​ของ domain model ใน code — ลอง​เทียบ​สอง​แนวทางใน​การ​เริ่ม​สร้าง​ระบบ order processing แนวทาง​แรก​พยายาม​ออกแบบ​โครงสร้าง​ที่ “รองรับ​ทุก​อนาคต” ตั้งแต่​วัน​แรก (BDUF): มี interface, abstract factory, strategy pattern สำหรับ discount, tax, shipping ที่​ยัง​ไม่มี​ลูกค้า​จริง​มา​ทดสอบ​เลย​สัก​ราย ผล​คือ code ซับซ้อน​แต่​ไม่มี​ส่วน​ไหน​ผ่าน​การ​พิสูจน์​กับ​ความ​ต้องการ​จริง เมื่อ​ความ​ต้องการ​จริง​มา​ถึง​กลับ​ไม่​ตรง​กับ​โครงสร้าง​ที่​เดา​ไว้ และ​ต้อง​รื้อ​ทิ้ง​เกือบ​ทั้งหมด

// เริ่มจากระบบง่ายที่ทำงานได้จริงก่อน — ยังไม่มี abstraction ที่ยังไม่ถูกพิสูจน์
public class Order
{
public decimal Total { get; private set; }
public void AddItem(decimal price)
{
Total += price;
}
}
// เมื่อมีความต้องการจริงเรื่องส่วนลด ค่อยเพิ่มความซับซ้อนทีละชั้น
// โดยยังคงมีระบบที่ทำงานได้ในทุกขั้นตอน แทนที่จะออกแบบ IDiscountStrategy
// ล่วงหน้าตั้งแต่วันแรกโดยยังไม่รู้ว่ากฎส่วนลดจริงเป็นอย่างไร
public class Order
{
private readonly List<decimal> _itemPrices = new();
public void AddItem(decimal price) => _itemPrices.Add(price);
public decimal Total => _itemPrices.Sum() * (1 - DiscountRateFor(_itemPrices.Count));
// กฎส่วนลดถูกเพิ่มเข้ามาหลังจากเห็นความต้องการจริงจากผู้ใช้ ไม่ใช่การเดาล่วงหน้า
private static decimal DiscountRateFor(int itemCount) => itemCount >= 10 ? 0.1m : 0m;
}
  • อย่า​พยายาม​สร้าง​ระบบ​ใหญ่​ที่​เชื่อม​โยง​กัน​ทั้งหมด​ตั้งแต่​วัน​แรก (หลีก​เลี่ยง Big Design Up Front) แต่​จง​เริ่ม​จาก​สิ่ง​ที่​เรียบ​ง่าย​และ​ใช้งาน​ได้​จริง แล้ว​ค่อย ๆ เติบโต​อย่าง​มี​การ​ทดสอบ​กับ​ความ​จริง​ใน​ทุก​ขั้น
  • ส่ง​มอบ​เป็น Vertical Slices แต่ละ slice ควร​เป็น​ระบบ​เล็ก ๆ ที่ “ทำงาน​ได้” ครบ​วงจร​ตั้งแต่​ต้น​จน​จบ (end-to-end) แม้​จะ​บาง​และ​มี feature น้อย ดี​กว่า​การ​สร้าง​ทุก layer ให้​หนา​แต่​ไม่มี slice ไหน​ใช้งาน​ได้​จริง​เลย
  • ยึด KISS และ YAGNI เป็น​แนว​ปฏิบัติ​คู่​กัน — ความ​ซับซ้อน​ที่​คุณ คาด​ว่า จะ​ต้อง​ใช้​ใน​อนาคต มัก​ผิด​จาก​สิ่ง​ที่​ระบบ​จริง​ต้องการ การ​เพิ่ม​ความ​ซับซ้อน​ควร​มา​จาก​ความ​ต้องการ​ที่​พิสูจน์​แล้ว​เท่านั้น ไม่ใช่​จาก​การ​เดา
  • ใน​การ​ออกแบบ​สถาปัตยกรรม ให้​พิจารณา monolith-first ก่อน​กระโดด​ไป microservices เว้น​แต่​ทีม​มี​ประสบการณ์​ตรง​กับ domain นั้น​มา​ก่อน​แล้ว​จริง ๆ
  • ยอมรับ​ว่าการ refactor (refactoring) เป็น​ส่วน​หนึ่ง​ของ​เส้นทาง​ที่​หลีก​เลี่ยง​ไม่​ได้ เพราะ​ระบบ​ง่าย​ที่​ทำงาน​ได้​จะ​ต้อง​ถูก​ปรับ​โครงสร้าง​ซ้ำ​แล้ว​ซ้ำ​เล่า​เพื่อ​รองรับ​ความ​ซับซ้อน​ที่​เพิ่ม​ขึ้น — นี่​ไม่ใช่​ความ​ล้มเหลว​ของ​การ​ออกแบบ​เริ่มต้น แต่​เป็น​กลไก​วิวัฒนาการ​ตาม​ธรรมชาติ​ของ​ระบบ​ที่​ใช้งาน​ได้
  • ระวัง​ไม่​ตีความ​กฎ​นี้​ผิด​เป็น​ข้อ​อ้าง​ละเลย​การ​ออกแบบ​เลย Gall’s Law ไม่​ได้​ต่อต้าน​การ​วางแผน​สถาปัตยกรรม แต่​เตือน​ว่าการ​วางแผน​ต้อง​พิสูจน์​ผ่าน​การ​ทำงาน​จริง​เป็น​ระยะ ๆ ไม่ใช่​ปล่อย​ให้​แผน​ใหญ่​ลอย​อยู่​บน​กระดาษ​จนกว่า​จะ “เสร็จ​สมบูรณ์” ก่อน​เริ่ม​สร้าง
flowchart LR
    Start[ระบบง่ายที่ทำงานได้] --> Grow[เพิ่มความซับซ้อนทีละส่วน]
    Grow --> Verify[พิสูจน์ด้วยการใช้งานจริง]
    Verify --> Grow
    Verify --> Complex[ระบบซับซ้อนที่ทำงานได้]
    Scratch[ออกแบบระบบซับซ้อนจากศูนย์] --> Fail[ไม่ทำงาน แก้ไม่ได้]