Anderson’s Law
ทุกปัญหา เมื่อมองให้ลึก มักซับซ้อนยิ่งกว่าเดิม
Anderson’s Law (โดยนักเขียนไซไฟ Poul Anderson) กล่าวว่า:
“ผมยังไม่เคยเห็นปัญหาใด ไม่ว่าจะซับซ้อนแค่ไหน ที่เมื่อมองในมุมที่ถูกต้องแล้ว จะไม่กลับยิ่งซับซ้อนขึ้นไปอีก”
— Poul Anderson
หลายแง่มุมของการออกแบบซอฟต์แวร์ดูเรียบง่าย… สำหรับ “happy path” แต่เมื่อพิจารณาสภาวะล้มเหลวและสถานะที่ไม่สอดคล้องต่าง ๆ ความซับซ้อนที่แท้จริงของการทำให้ระบบทำงานถูกต้องภายใต้เงื่อนไขโลกจริงก็เผยหน้าออกมา บทเรียนคือให้เผื่อความซับซ้อนที่ซ่อนอยู่ไว้ในการประเมินเสมอ และอย่าเชื่อว่างานจะง่ายจนกว่าจะได้ลงลึก
ใจความสำคัญ
หัวข้อที่มีชื่อว่า “ใจความสำคัญ”ประโยคนี้มีที่มาชัดเจนกว่าที่หลายคนคิด: Poul Anderson เขียนมันไว้ในปากตัวละคร Arne Viken จากเรื่องสั้นแนวไซไฟ “Call Me Joe” ซึ่งตีพิมพ์ในนิตยสาร Astounding Science Fiction ฉบับเดือนเมษายน ค.ศ. 1957 ถ้อยคำต้นฉบับคือ “I’ve yet to see any problem, however complicated, which when you looked at it the right way didn’t become still more complicated”
ต่อมาในปี 1967 Arthur Koestler นำประโยคนี้ไปอ้างเป็นบทนำของบทที่ 4 ในหนังสือ The Ghost in the Machine โดยให้เครดิต Anderson อย่างถูกต้อง แต่เพราะ Koestler เป็นคนดัง คำพูดจึงมักถูกเข้าใจผิดว่าเป็นของเขาเอง (บางครั้งเรียกกันว่า “Koestler’s motto” ทั้งที่ Koestler เองไม่เคยอ้างเครดิตให้ตัวเอง) จากนั้นในปี 1969 บทความ “Reductionism v. Organicism” ของ William Thorpe ในนิตยสาร New Scientist ได้ปรับถ้อยคำให้เป็นรูปแบบที่แพร่หลายที่สุดในทุกวันนี้ ก่อนที่ Paul Dickson จะรวบรวมมันไว้ในหนังสือ The Official Rules ฉบับขยาย (2013) พร้อมตั้งชื่อให้เป็นทางการว่า “Anderson’s Law”
ข้อสังเกตที่น่าสนใจคือบริบทดั้งเดิมของประโยคนี้ไม่ใช่วิศวกรรมซอฟต์แวร์เลย แต่เป็นเรื่องสั้นไซไฟ และถูกยืมมาใช้ในบทความปรัชญาวิทยาศาสตร์เรื่อง reductionism กับ organicism — ว่าการแยกส่วนระบบเพื่อทำความเข้าใจ (reduction) จะช่วยให้เห็นความจริงง่ายขึ้นหรือกลับซ่อนความซับซ้อนไว้มากขึ้น DevIQ หยิบยืมนัยยะนี้มาใช้กับการออกแบบซอฟต์แวร์: สิ่งที่ดูง่ายบน happy path จะซับซ้อนขึ้นทันทีที่เริ่มถามคำถาม “แล้วถ้า…ล่ะ” — ถ้าเครือข่ายขาด ถ้าผู้ใช้กดซ้ำ ถ้าข้อมูลไม่สมบูรณ์ ถ้าสองคนแก้ไขพร้อมกัน
ความหมายและนัยยะ
หัวข้อที่มีชื่อว่า “ความหมายและนัยยะ”Anderson’s Law ไม่ได้บอกว่า “การวิเคราะห์ให้ลึกเป็นเรื่องไร้ประโยชน์” แต่บอกว่า การวิเคราะห์ที่ดีมักเผย ไม่ใช่ซ่อน ความซับซ้อนที่มีอยู่แล้ว ความเรียบง่ายที่เราเห็นตอนแรกมักเป็นภาพลวงตาที่เกิดจากการมองในระดับสูงเกินไป (abstraction ที่สูงเกินจำเป็น) หรือมองแค่กรณีทั่วไปโดยละเลยกรณีชายขอบ
ในงานซอฟต์แวร์ นัยยะที่สำคัญมีอย่างน้อยสามด้าน:
- การประมาณงาน (estimation) — ทีมมักประเมิน “เวลาที่ต้องใช้” จากภาพความเข้าใจตอนต้น ซึ่งยังไม่ผ่านการซักถามเชิงลึก พอเริ่มลงมือจริงจึงเจอ edge case, ข้อจำกัดของระบบเดิม, requirement ที่ไม่มีใครพูดถึง ทำให้งานที่ “น่าจะเสร็จใน 2 วัน” กลายเป็น 2 สัปดาห์ ความสัมพันธ์นี้ใกล้เคียงกับ Hofstadter’s Law ที่บอกว่างานใช้เวลานานกว่าที่คาดเสมอ แม้จะเผื่อ Hofstadter’s Law ไว้แล้วก็ตาม — Anderson’s Law อธิบายว่า ทำไม: เพราะทุกครั้งที่มองลึกขึ้นอีกชั้น ความซับซ้อนใหม่จะโผล่ออกมาเสมอ ไม่ใช่น้อยลง
- สถาปัตยกรรมซอฟต์แวร์ — module ที่ดูเหมือนแยกส่วนกันชัดเจนตอนออกแบบ (เช่น “แค่ผูก service A กับ service B”) มักซ่อนความซับซ้อนของ consistency, การจัดการ failure บางส่วน (partial failure), และ contract ที่เปลี่ยนแปลงได้ เมื่อมองลึกลงไปในรายละเอียดจริง
- domain ธุรกิจ — subdomain ที่ผู้เชี่ยวชาญบอกว่า “ง่ายนะ กฎแค่ข้อเดียว” มักมีข้อยกเว้นซ้อนข้อยกเว้นเมื่อคุยกันลึกพอ นี่คือเหตุผลที่การทำ Event Storming หรือ Domain Storytelling ถึงมักเผยความซับซ้อนที่ไม่มีใครเห็นมาก่อน แม้จะทำงานกับระบบนี้มาหลายปีแล้ว
ผังด้านล่างแสดงวงจรของการ “ลอกชั้น” ความซับซ้อนออกทีละชั้น — แต่ละชั้นที่ลอกออกไม่ได้ทำให้เหลือแค่ความเรียบง่าย มันมักเผยชั้นใหม่ที่ซับซ้อนกว่าเดิมแทน
flowchart TD
A[ออกแบบตาม happy path] --> B[ดูเหมือนง่าย]
B --> C[เพิ่มสภาวะล้มเหลว]
C --> D[เพิ่ม concurrency และ edge case]
D --> E[เพิ่มเงื่อนไขข้อมูลจริง]
E --> F[ความซับซ้อนที่แท้จริงเผยออกมา]
F --> G[ต้องออกแบบใหม่ให้ลึกขึ้นอีกชั้น]
G --> B
วงวนนี้ไม่มีจุดจบตายตัว — สิ่งที่ทีมทำได้คือตัดสินใจว่าจะ “ลงลึกอีกกี่ชั้น” ให้พอดีกับความเสี่ยงและมูลค่าของงาน ไม่ใช่พยายามไล่ตามความสมบูรณ์แบบไปเรื่อย ๆ (ดู Tolerance for Imperfection)
ตัวอย่างในโลกจริง
หัวข้อที่มีชื่อว่า “ตัวอย่างในโลกจริง”feature “แค่ระบบ login” เป็นตัวอย่างคลาสสิกที่ทุกทีมเคยเจอ ตอนแรกดูเหมือนงานเล็ก ๆ: รับ username/password แล้วเช็คกับฐานข้อมูล แต่พอมองลึกขึ้นจะพบว่าต้องมี: การรีเซ็ตรหัสผ่าน, การล็อกบัญชีชั่วคราวเมื่อพยายามผิดหลายครั้ง (rate limiting), ข้อความ error ที่ไม่รั่วข้อมูลผู้ใช้ (ดู Fail Fast ในมุมความปลอดภัย), การรองรับหลายภาษา, การ invalidate session เมื่อเปลี่ยนรหัสผ่าน, การเชื่อมกับ OAuth ของผู้ให้บริการภายนอกที่มีเงื่อนไขต่างกัน และการลบข้อมูลผู้ใช้ตามกฎหมายคุ้มครองข้อมูลส่วนบุคคล — แต่ละอย่างเป็นชั้นความซับซ้อนที่ไม่มีใครพูดถึงตอนประเมินงานครั้งแรก
code ตัวอย่างด้านล่างแสดงพัฒนาการเดียวกันในระดับ function: version แรกดูสมบูรณ์ในตัวเอง
// ดูเหมือนง่าย: แค่บันทึกคำสั่งซื้อแล้วเรียกเก็บเงินpublic void ProcessOrder(Order order){ _repository.Save(order); _paymentGateway.Charge(order.Total);}แต่เมื่อถามคำถาม “แล้วถ้า…ล่ะ” ไปทีละข้อ (สต็อกหมดพอดีตอนสั่งพร้อมกันสองคน, การชาร์จเงิน timeout แล้วถูก retry ซ้ำ, การชำระเงินถูกปฏิเสธหลังจองสต็อกไปแล้ว) function เดิมจะขยายตัวออกเป็นแบบนี้:
public async Task ProcessOrderAsync(Order order, CancellationToken ct){ // กัน race condition เมื่อสต็อกเหลือน้อยและมีคำสั่งซื้อพร้อมกัน using var stockLock = await _stockLocks.AcquireAsync(order.ProductId, ct);
if (!await _inventory.TryReserveAsync(order.ProductId, order.Quantity, ct)) throw new OutOfStockException(order.ProductId);
try { // ต้องทำให้ idempotent เพราะ payment gateway อาจ timeout แล้วถูกเรียกซ้ำ var charge = await _paymentGateway.ChargeAsync( order.Total, idempotencyKey: order.Id, ct);
if (charge.Status == ChargeStatus.Declined) { await _inventory.ReleaseAsync(order.ProductId, order.Quantity, ct); throw new PaymentDeclinedException(order.Id); }
await _repository.SaveAsync(order, ct); await _events.PublishAsync(new OrderPlaced(order.Id), ct); } catch { // ต้องคืนสต็อกที่จองไว้เสมอ ไม่ว่าจะพังตรงขั้นตอนไหน await _inventory.ReleaseAsync(order.ProductId, order.Quantity, ct); throw; }}งานที่ “น่าจะเสร็จใน 1 ชั่วโมง” กลายเป็นงานที่ต้องคิดเรื่อง locking, idempotency, compensating action และ event publishing — ทั้งหมดนี้มีอยู่จริงในระบบตั้งแต่แรก เพียงแต่ยังไม่ถูกมองเห็น
ในโลกของการประมาณงาน บทความ “Hidden Complexities Of Software Estimation” ของ Mike McQuaid ชี้ให้เห็นปรากฏการณ์เดียวกันโดยไม่ได้อ้างถึง Anderson’s Law โดยตรง: การประมาณเวลาต้องคิดถึง code review, การ deploy, ขวัญกำลังใจของทีม, ต้นทุนการสลับงาน (context switching), และการปรับโครงสร้างองค์กรระหว่างทาง — ปัจจัยเหล่านี้ไม่ปรากฏในแผนงานตอนต้น แต่ปรากฏขึ้นเสมอเมื่อมองงานให้ลึกพอ นอกจากวงการซอฟต์แวร์แล้ว ประโยคนี้ยังถูกอ้างถึงในหนังสือการบริหารโครงการอย่าง Project Management: A Systems Approach ของ Harold Kerzner และหนังสือ Thinking in Systems: A Primer ของ Donella Meadows ซึ่งสะท้อนว่าปรากฏการณ์นี้ไม่ได้จำกัดอยู่แค่วงการไอที แต่เป็นธรรมชาติของระบบซับซ้อนโดยทั่วไป
บทเรียนที่นำไปใช้
หัวข้อที่มีชื่อว่า “บทเรียนที่นำไปใช้”- ประมาณงานเป็นช่วง (range) ไม่ใช่ตัวเลขเดียว เพราะความซับซ้อนที่ยังไม่ถูกค้นพบมีอยู่จริงเสมอ การให้ตัวเลขเดียวเป็นการซ่อนความไม่แน่นอนไว้ ไม่ใช่การขจัดมันออกไป
- ทำ spike หรือ timebox สำรวจความไม่รู้ก่อนตัดสินใจ commit วันส่งงาน การขุดลึกเพียงเล็กน้อยก่อนประมาณมักคุ้มค่ากว่าการเผื่อ buffer แบบเดา
- ทำ pre-mortem หรือ threat modeling แบบสั้น ๆ ก่อนเริ่มงาน โดยถามว่า “อะไรจะพังได้บ้าง” แทนที่จะรอให้มันพังจริงระหว่างทำงาน
- อย่าใช้กฎนี้เป็นข้ออ้างให้วิเคราะห์ไม่จบสิ้น การไล่ตามความซับซ้อนทุกชั้นจนกว่าจะ “สมบูรณ์แบบ” คือการตกหลุมพราง Analysis Paralysis — เป้าหมายคือรู้ว่ามีความไม่แน่นอนอยู่ตรงไหน ไม่ใช่ต้องแก้มันให้หมดก่อนเริ่มงาน
- ออกแบบให้ระบบเรียบง่ายที่สุดที่ยังทำงานได้ก่อน แล้วค่อยให้ความซับซ้อนที่จำเป็นค่อย ๆ ปรากฏผ่านการทำงานจริง แนวคิดนี้สอดคล้องกับ Gall’s Law ที่ว่าระบบซับซ้อนที่ใช้งานได้ มักวิวัฒนาการมาจากระบบง่าย ๆ ที่ใช้งานได้ก่อน ไม่ใช่ถูกออกแบบให้ซับซ้อนตั้งแต่วันแรก
- บันทึกสมมติฐานและสิ่งที่ยังไม่รู้ (known unknowns) ไว้อย่างชัดเจน เพื่อให้ทีมและผู้มีส่วนได้ส่วนเสียเห็นภาพเดียวกันว่าตัวเลขประมาณการมีความไม่แน่นอนแฝงอยู่มากแค่ไหน
ที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “ที่เกี่ยวข้อง”- Tolerance for Imperfection
- Tesler’s Law
- Hofstadter’s Law
- Gall’s Law
- Conditional Complexity
- Analysis Paralysis