Tesler’s Law
ความซับซ้อนย้ายที่ได้ แต่กำจัดไม่ได้ — คำถามคือใครจะเป็นผู้แบกรับ
ใจความสำคัญ
หัวข้อที่มีชื่อว่า “ใจความสำคัญ”Tesler’s Law หรือชื่อทางการว่า Law of Conservation of Complexity เป็นข้อสังเกตจากวงการ human-computer interaction (HCI) ที่ระบุข้อจำกัดพื้นฐานของการออกแบบซอฟต์แวร์: ทุก application มีความซับซ้อนแก่นแท้จำนวนหนึ่งที่ ลดทอนไม่ได้ (irreducible) คุณย้ายมันจากที่หนึ่งไปอีกที่หนึ่งได้ แต่ทำให้มันหายไปเฉย ๆ ไม่ได้ — เหมือนกฎอนุรักษ์พลังงานในฟิสิกส์ที่พลังงานเปลี่ยนรูปได้แต่ไม่สูญหาย
“ทุก application มีความซับซ้อนที่ลดทอนไม่ได้ในตัว คำถามเดียวคือใครจะต้องรับมือกับมัน — ผู้ใช้ นักพัฒนา app หรือนักพัฒนา platform”
— Larry Tesler
Larry Tesler เป็นนักวิทยาการคอมพิวเตอร์ที่ Xerox PARC ในช่วงกลางทศวรรษ 1980s ก่อนจะย้ายไปมีบทบาทสำคัญในการพัฒนา Lisa และ Macintosh ที่ Apple เขาเป็นผู้คิดค้นแนวคิด cut/copy/paste ที่เราใช้กันทุกวันนี้ Tesler เริ่มพูดถึงกฎนี้ตอนช่วยพัฒนาภาษาออกแบบเชิงโต้ตอบ (interactive design language) ที่ Xerox PARC โดยตั้งใจใช้เป็นข้อโต้แย้งเชิงกลยุทธ์เพื่อโน้มน้าวผู้บริหารบริษัทและผู้พัฒนาซอฟต์แวร์อิสระ ให้ยอมลงทุนแรงงานวิศวกรเพิ่มขึ้นเพื่อลดภาระของผู้ใช้ปลายทาง เรื่องราวนี้ถูกบันทึกไว้ในบทสัมภาษณ์ Tesler ในหนังสือ Designing for Interaction ของ Dan Saffer ซึ่งเป็นที่มาหลักที่ทำให้กฎนี้แพร่หลายในวงการ UX
ที่มาของชื่อ “Law of Conservation” ตั้งใจล้อกับกฎอนุรักษ์พลังงานในวิชาฟิสิกส์โดยตรง — พลังงานไม่ถูกสร้างขึ้นใหม่หรือทำลายทิ้งได้ เปลี่ยนรูปได้เท่านั้น ในทำนองเดียวกัน ความซับซ้อนของปัญหาหนึ่ง ๆ ก็มี “ปริมาณคงตัว” อยู่ระดับหนึ่งที่การออกแบบใด ๆ ก็ตัดทิ้งไปเฉย ๆ ไม่ได้ ทำได้แค่ย้ายมันไปยังจุดอื่นในระบบ
ความหมายและนัยยะ
หัวข้อที่มีชื่อว่า “ความหมายและนัยยะ”หัวใจของกฎนี้ไม่ใช่การบอกให้ “ลดความซับซ้อน” แบบเลื่อนลอย แต่คือการยอมรับความจริงที่ว่า ความซับซ้อนแก่นแท้ (irreducible complexity) ของปัญหาหนึ่ง ๆ มีปริมาณคงที่อยู่ปริมาณหนึ่ง เช่น การจัดตารางนัดหมายข้ามเขตเวลาที่ต่างกันย่อมมีตรรกะซับซ้อนบางอย่างที่หนีไม่พ้น สิ่งที่ทีมออกแบบทำได้จริง ๆ คือ ตัดสินใจอย่างจงใจว่าใครจะแบกมันไว้ ระหว่างผู้เล่นสามฝ่าย:
- ผู้ใช้ (user) — ต้องเรียนรู้ ตั้งค่า หรือทำขั้นตอนเพิ่มเองด้วยมือ
- นักพัฒนา application (application developer) — เขียน code เพิ่มเพื่อซ่อนความซับซ้อนไว้เบื้องหลัง UI ที่เรียบง่าย
- นักพัฒนา platform (platform developer) — ผลักภาระไปไว้ที่ framework, library หรือระบบปฏิบัติการชั้นล่างสุด
Tesler เชื่อมั่นอย่างแรงกล้าว่าภาระควรตกอยู่ที่วิศวกรมากกว่าผู้ใช้ปลายทาง เขาให้เหตุผลเชิงเศรษฐศาสตร์ไว้อย่างคมคายว่า
“ถ้าผู้ใช้นับล้านคนต้องเสียเวลาคนละหนึ่งนาทีต่อวันไปกับความซับซ้อนที่วิศวกรคนหนึ่งสามารถกำจัดได้ด้วยการทำงานเพิ่มแค่หนึ่งสัปดาห์ โดยทำให้ซอฟต์แวร์ซับซ้อนขึ้นอีกนิด นั่นแปลว่าคุณกำลังลงโทษผู้ใช้เพื่อให้งานของวิศวกรง่ายขึ้น”
นี่คือการเทียบ “ต้นทุนรวม” (aggregate cost) ไม่ใช่ต้นทุนต่อหน่วย — เวลาของผู้ใช้หนึ่งล้านคนคูณกันแล้วมีค่ามากกว่าเวลาของวิศวกรหนึ่งคนเสมอ แนวคิดนี้เป็นรากฐานของหลักการ Progressive Disclosure และปรัชญาการออกแบบ smart default ที่ซอฟต์แวร์ยุคหลังใช้กันแพร่หลาย
อย่างไรก็ตาม Bruce Tognazzini นักออกแบบ UX ผู้มีชื่อเสียงอีกคนหนึ่งเสนอมุมมองแย้งที่น่าสนใจ: มนุษย์มักไม่ยอมให้ความซับซ้อนในชีวิตลดลงจริง ๆ เมื่อเครื่องมือถูกทำให้ง่ายขึ้น ผู้ใช้กลับพยายามทำงานที่ซับซ้อนขึ้นไปอีกขั้นด้วยเครื่องมือเดิม (เช่น Excel ที่เรียบง่ายขึ้นเรื่อย ๆ กลับถูกใช้สร้าง model การเงินที่ซับซ้อนกว่าเดิม) ผลคือ “ความซับซ้อนที่ถูกอนุรักษ์ไว้” อาจไม่ได้อยู่นิ่งเฉย แต่ขยายตัวไปพร้อมกับความทะเยอทะยานของงานที่ผู้ใช้พยายามทำ
ควรแยกกฎนี้ออกจากแนวคิด essential vs. accidental complexity ของ Fred Brooks ใน “No Silver Bullet” ให้ชัดเจน: Brooks พูดถึงความซับซ้อนของ “ปัญหา domain” เทียบกับความซับซ้อนที่เกิดจาก “เครื่องมือ/กระบวนการ” ที่เราเลือกใช้ ส่วน Tesler’s Law พูดถึง “ตำแหน่ง” ที่ความซับซ้อนแก่นแท้จะไปตกอยู่ — คนละมิติกัน แต่เสริมกันได้ดี: essential complexity ตาม Brooks คือปริมาณที่ Tesler บอกว่า “ลดทอนไม่ได้” นั่นเอง
ตัวอย่างในโลกจริง
หัวข้อที่มีชื่อว่า “ตัวอย่างในโลกจริง”- เครื่องมือจัดตารางนัดหมาย (เช่น Calendly) — การหาช่วงเวลาว่างที่ตรงกันข้ามเขตเวลา เทียบ availability ของหลายฝ่าย และจัดการการยกเลิก/เลื่อนนัด เป็นความซับซ้อนที่หนีไม่พ้น เครื่องมือเหล่านี้เลือกให้ทีมวิศวกรแบกตรรกะซับซ้อนไว้เบื้องหลัง เพื่อให้ผู้ใช้เห็นแค่ปุ่ม “เลือกเวลา” ง่าย ๆ ตรงข้ามกับการส่งอีเมลไปมาต่อรองเวลานัดหมายที่ผลักภาระทั้งหมดกลับไปที่มนุษย์
- Autocomplete และ autocorrect — ความซับซ้อนของภาษาธรรมชาติ การสะกดคำ และบริบท ถูกย้ายจากผู้ใช้ (ต้องพิมพ์ถูกทุกตัวอักษรเอง) ไปไว้ที่นักพัฒนา platform keyboard/เบราว์เซอร์
- การตั้งค่า network ของอุปกรณ์บ้าน (router, smart TV) — ผู้ผลิตบางรายเลือกซ่อนความซับซ้อนของ DHCP, DNS, port forwarding ไว้หลัง auto-configuration ขณะที่บางรายเปิดเมนูตั้งค่าเต็มรูปแบบให้ผู้ใช้ขั้นสูงจัดการเอง — เป็นตัวอย่างชัดของ “เลือกว่าใครแบก”
- Checkout ของ e-commerce — การคำนวณภาษี ค่าส่ง ส่วนลด และวิธีชำระเงินที่รองรับหลายประเทศเป็นความซับซ้อนแก่นแท้ที่หนีไม่พ้น ผู้ให้บริการอย่าง payment gateway สมัยใหม่เลือกแบกความซับซ้อนนี้ไว้ในบริการของตนเอง (เช่น คำนวณภาษีตามที่อยู่อัตโนมัติ) เพื่อให้ทั้งผู้ใช้ปลายทางและนักพัฒนาร้านค้าเห็นแค่หน้าจอ checkout ที่เรียบง่าย ตรงข้ามกับระบบเก่าที่ให้ผู้ดูแลร้านค้าต้องตั้งค่ากฎภาษีเองทีละประเทศ
- Library และ framework ที่ซ่อน retry/backoff ไว้ให้ — ตัวอย่างเชิง code: ถ้าปล่อยให้ผู้เรียกใช้ (caller) ต้องเขียน retry loop เองทุกจุดที่เรียก API ภายนอก ความซับซ้อนของ transient failure จะตกอยู่ที่ผู้ใช้ library (นักพัฒนา app) แต่ถ้า library platform ซ่อนมันไว้ ความซับซ้อนก็ย้ายไปอยู่ที่นักพัฒนา library แทน:
// แบบที่ 1: ผู้เรียกใช้ (นักพัฒนา app) ต้องแบกความซับซ้อนของ retry เองvar attempt = 0;var response = await httpClient.GetAsync(url);while (!response.IsSuccessStatusCode && attempt < maxRetries){ await Task.Delay(ComputeBackoff(attempt)); response = await httpClient.GetAsync(url); attempt++;}
// แบบที่ 2: ย้ายความซับซ้อนไปไว้ที่นักพัฒนา platform (คนเขียน ResilientHttpClient)// ผู้เรียกใช้เห็นแค่ interface ง่าย ๆ ตรรกะ retry/backoff ถูกซ่อนไว้ข้างในvar response = await resilientClient.GetAsync(url);code ทั้งสองแบบแก้ปัญหาเดียวกัน ความซับซ้อนของ transient failure ไม่ได้หายไปไหน มันแค่ถูกย้ายที่ — คำถามคือใครควรเขียนและดูแล ComputeBackoff และ retry loop นี้ครั้งเดียวในที่เดียว (library) หรือให้ทุกจุดเรียกต้องเขียนซ้ำเอง
flowchart LR
Problem[ปัญหาที่มีความซับซ้อนแก่นแท้] --> Choice{ใครจะแบกรับ}
Choice --> User[ผู้ใช้ทำเองด้วยมือ]
Choice --> AppDev[นักพัฒนา app เขียน code ซ่อนไว้]
Choice --> PlatformDev[นักพัฒนา platform ซ่อนไว้ใน library]
AppDev --> SimpleUI[UI ที่ผู้ใช้เห็นเรียบง่ายขึ้น]
PlatformDev --> SimpleAPI[API ที่นักพัฒนา app เรียกใช้ง่ายขึ้น]
บทเรียนที่นำไปใช้
หัวข้อที่มีชื่อว่า “บทเรียนที่นำไปใช้”- ตั้งคำถามอย่างจงใจ ไม่ใช่ปล่อยให้เกิดโดยบังเอิญ — ทุกครั้งที่พบความซับซ้อนที่หนีไม่พ้น ให้ถามตรง ๆ ว่า “ใครควรแบกสิ่งนี้” แทนที่จะปล่อยให้มันตกไปอยู่กับผู้ใช้โดยไม่มีใครตัดสินใจ
- เปรียบเทียบต้นทุนรวม ไม่ใช่ต้นทุนต่อครั้ง — เวลาของวิศวกรหนึ่งสัปดาห์เทียบกับเวลาผู้ใช้นับล้านคนสะสมกันไปเรื่อย ๆ มักจะคุ้มเสมอที่จะลงทุนฝั่งวิศวกรรม
- อย่าลืมว่าความซับซ้อนที่ซ่อนไว้ยังต้องมีคนดูแล — เมื่อย้ายภาระไปที่นักพัฒนา platform หรือ app code ที่ซ่อนความซับซ้อนนั้นเองก็ต้องทดสอบ ดูแล และแก้ bug ในระยะยาว การซ่อนไม่ใช่การกำจัด
- ใช้ Progressive Disclosure เป็นเครื่องมือ — เปิดเผยความซับซ้อนแบบเป็นชั้น ๆ ให้ผู้ใช้ขั้นสูงเข้าถึงได้เมื่อจำเป็น โดยไม่บังคับผู้ใช้ทั่วไปให้เห็นทุกอย่างตั้งแต่แรก
- ระวังการตีความผิดว่า “ซับซ้อนขึ้นเสมอดีกว่า” — กฎนี้ไม่ได้บอกให้เพิ่มความซับซ้อนของ code พร่ำเพรื่อ หลัก YAGNI และ Keep It Simple ยังใช้คู่กันได้: ลงทุนแก้ความซับซ้อน แก่นแท้ ที่มีอยู่จริง อย่าสร้างความซับซ้อน เทียม ขึ้นมาใหม่โดยอ้างกฎนี้
- จำมุมมองของ Tognazzini ไว้ด้วย — การลดความซับซ้อนของเครื่องมือหนึ่งอาจถูกผู้ใช้ผลักดันให้ทำงานที่ทะเยอทะยานขึ้นจนความซับซ้อนกลับมาใหม่ในรูปแบบอื่น การออกแบบจึงควรมองเป็นวงจรต่อเนื่อง ไม่ใช่แก้ครั้งเดียวจบ