Calendar Coder
ทำตามคำแนะนำอย่างงมงายโดยไม่เข้าใจว่าทำไม (Cargo Cult)
ปัญหาคืออะไร
หัวข้อที่มีชื่อว่า “ปัญหาคืออะไร”Calendar Coder เป็นการเล่นคำกับแนวคิด cargo cult programming — ชื่อนี้มาจากปฏิทิน Software Craftsmanship AntiPatterns ปี 2012 และล้อว่านักพัฒนาบางคน “เขียน code ตามที่ปฏิทินบอก” คือทำตามคำแนะนำที่เห็นมาโดยไม่เข้าใจเหตุผลเบื้องหลัง แก่นของปัญหาคือ การขาดความคิดและความเข้าใจ ของนักพัฒนาที่ทำตามคำแนะนำจากแหล่งที่ตนเชื่อว่าน่าเชื่อถืออย่างงมงาย
คำว่า cargo cult มีรากมาจาก “cargo cult science” ที่ Richard Feynman พูดถึง ซึ่งอ้างอิงถึงลัทธิบูชาสินค้าบนเกาะแถบเมลานีเซียหลังสงครามโลกครั้งที่สอง ชาวเกาะเห็นว่าเครื่องบินทหารเคยนำสิ่งของมาลงเมื่อมีรันเวย์ หอบังคับการ และคนใส่หูฟัง พวกเขาจึงสร้างรันเวย์จำลอง หอไม้ไผ่ และหูฟังไม้ขึ้นมา แล้วรอให้เครื่องบินกลับมา — แต่มันไม่มาอีกเลย Feynman สรุปไว้ว่า “รูปแบบสมบูรณ์แบบ มันดูเหมือนของจริงทุกอย่าง แต่มันไม่ทำงาน” นี่คือหัวใจของปัญหา: ลอกรูปแบบภายนอกของสิ่งที่ได้ผล โดยไม่เข้าใจหลักการที่ทำให้มันได้ผลจริง
ในโลก code อาการนี้คือการใส่ code หรือโครงสร้างเข้ามาแบบเป็นพิธีกรรม ทั้งที่ไม่ได้ทำหน้าที่อะไรจริง ๆ มักเกิดกับคนที่ไม่เข้าใจทั้ง bug ที่ตัวเองพยายามแก้ และไม่เข้าใจทางแก้ที่ตัวเองคัดลอกมา คำว่า cargo cult programming ปรากฏครั้งแรกใน Jargon File version 2.5.1 ตั้งแต่เดือนมกราคม 1991 และยังใช้อธิบายการหยิบ design pattern หรือสไตล์การเขียนมาใช้อย่างมืดบอดโดยไม่เข้าใจว่าหลักการนั้นมีไว้แก้ปัญหาอะไร
ทำไมถึงดูน่าใช้
หัวข้อที่มีชื่อว่า “ทำไมถึงดูน่าใช้”- มันมักได้ผลในตอนแรก — code ที่คัดลอกมาจากคำตอบใน Stack Overflow หรือ block ที่ดูน่าเชื่อถือ มักรันผ่านทันทีในเดโม ความสำเร็จที่มองเห็นได้เร็วนี้เองที่ตอกย้ำพฤติกรรมให้ทำซ้ำ
- ดูเหมือน “best practice” — เมื่อทีมอื่นหรือซีเนียร์ทำแบบนี้ การทำตามย่อมรู้สึกปลอดภัยกว่าการตั้งคำถาม การเลียนแบบรูปแบบที่คนเก่ง ๆ ใช้ ทำให้รู้สึกว่าตัวเองกำลังเขียน code อย่างมืออาชีพ
- ประหยัดแรงคิดในระยะสั้น — การเข้าใจว่า ทำไม บางอย่างถึงได้ผลนั้นใช้พลังงานมากกว่าการก็อปมาวางเฉย ๆ ภายใต้เดดไลน์ ทางลัดทางความคิดนี้จึงน่าดึงดูดเสมอ
- AI ยิ่งเร่งให้เกิดง่ายขึ้น — เครื่องมือช่วยเขียน code ด้วย LLM เลียนแบบโครงสร้าง code ที่ “ดูเป็นมืออาชีพ” ได้อย่างคล่องแคล่ว เพราะมันเคยเห็นรูปแบบเหล่านั้นซ้ำ ๆ ในข้อมูลฝึก ผลคือความซับซ้อนที่ไม่จำเป็นกลับดู “ถูกต้อง” และถูกรับมาโดยไม่ตั้งคำถาม
ทำไมถึงเป็นปัญหา
หัวข้อที่มีชื่อว่า “ทำไมถึงเป็นปัญหา”ปัญหาที่ลึกที่สุดคือ ถ้าคุณไม่รู้ว่าทำไม code ถึงทำงาน คุณก็จะไม่รู้ว่าทำไมมันถึงพัง แนวคิดนี้ตรงกับสิ่งที่ The Pragmatic Programmer เรียกว่า “programming by coincidence” หรือการเขียนโปรแกรมด้วยความบังเอิญ — code ใช้ได้ แต่ไม่มีเหตุผลรองรับว่าทำไม เมื่อบริบทเปลี่ยนไปเพียงเล็กน้อย มันก็พังโดยที่เจ้าของ code ไล่หาสาเหตุไม่เจอ หนังสือเล่มนั้นแยกความบังเอิญออกเป็นสองแบบ:
- Accident of implementation — พึ่งพาพฤติกรรมที่ไม่ได้เอกสารระบุ เช่น side effect ที่บังเอิญเกิดเมื่อเรียก method ตามลำดับหนึ่ง หรือ parameter ที่ไม่ได้ตั้งค่าเริ่มต้นแต่บังเอิญเป็นค่าที่ต้องการ เมื่อ library อัปเดต code ก็พังทันที
- Accident of context — เขียน code โดยตั้งสมมติฐานที่ไม่ได้ตรวจสอบ เช่น สมมติว่าจะมี GUI อยู่เสมอ หรือคัดลอก code ข้ามบริบทโดยไม่เข้าใจทั้งต้นทางและปลายทาง
ผลเสียที่ตามมาเป็นรูปธรรม:
- code บวมโดยไม่มีประโยชน์ — ริทัวลที่ก็อปมาสะสมเป็นชั้น ๆ เช่น ห่อ webhook receiver ธรรมดา ๆ ด้วย repository, factory และ message queue ทั้งที่โจทย์จริงต้องการแค่ตรวจ signature กับบันทึกข้อมูล ความซับซ้อนที่ไม่จำเป็นทำให้อ่านยาก เทสยาก และแก้ยากขึ้นทุกครั้ง
- bug ที่อันตรายกว่าเดิม — การก็อป pattern มาใช้ผิดที่ทำให้เกิดข้อผิดพลาดร้ายแรง เช่น เอา retry wrapper ไปครอบ operation ที่ไม่ idempotent จนเกิดการเรียกเก็บเงินซ้ำในโปรดักชัน
- ความเข้าใจผิดเชิงองค์กร — Steve McConnell เรียกอาการระดับทีมว่า “cargo cult software engineering” คือองค์กรเลียนแบบวิธีทำงานของทีมที่ประสบความสำเร็จ โดยสับสนระหว่างเหตุกับผล เช่น สั่งให้ทุกคนทำงานดึกเพราะเห็นว่าทีมเก่ง ๆ ทำงานดึก ทั้งที่การทำงานดึกเป็น ผล ของแรงจูงใจ ไม่ใช่ เหตุ ของความสำเร็จ
- สะสมเป็นธรรมเนียมปลอม — เมื่อริทัวลถูกทิ้งไว้ใน codebase คนรุ่นถัดไป (รวมถึง AI agent) จะถือว่ามันเป็น “แบบแผนที่ทีมยึดถือ” แล้วทำตาม ทำให้ความเข้าใจผิดขยายตัวและฝังลึกลงเรื่อย ๆ
ตัวอย่าง
หัวข้อที่มีชื่อว่า “ตัวอย่าง”code ต่อไปนี้เต็มไปด้วยริทัวลที่คัดลอกมาโดยไม่เข้าใจ ทุกบรรทัด “ดูเหมือน code ที่ดี” แต่แต่ละบรรทัดกลับสร้างปัญหาแทนที่จะแก้
// ก่อนแก้: ริทัวลที่ก็อปมาโดยไม่เข้าใจว่าแต่ละบรรทัดแก้ปัญหาอะไรpublic class UserService{ private readonly IUserRepository _repo;
public User GetUser(int id) { // "เห็นใน template เขาห่อ Task.Run กันหมด เลยห่อตาม" // จริง ๆ คือ block thread เปล่า ๆ และเสี่ยง deadlock var user = Task.Run(() => _repo.Find(id)).Result;
// "อ่าน block มาว่าเรียก GC แล้ว app จะเบา" // จริง ๆ คือฝืนให้ garbage collector ทำงานหนักขึ้นโดยไม่จำเป็น GC.Collect(); GC.WaitForPendingFinalizers();
if (user == null) { try { throw new Exception("not found"); } catch (Exception ex) { // "ที่อื่นเห็น throw ex กัน เลยทำตาม" // จริง ๆ คือรีเซ็ต stack trace เดิมทิ้ง ทำให้ debug ยากขึ้น throw ex; } }
return user; }}เมื่อถามว่า “ทำไมต้องมีบรรทัดนี้” คำตอบคือ “เพราะเห็นคนอื่นทำ” — นั่นคือสัญญาณของ cargo cult ที่ชัดเจนที่สุด version ที่แก้แล้วเก็บเฉพาะสิ่งที่มีเหตุผลรองรับ และตัดพิธีกรรมที่ไม่มีจุดประสงค์ทิ้งทั้งหมด
// หลังแก้: เก็บเฉพาะสิ่งที่อธิบายได้ว่าทำไม ตัดริทัวลที่ไร้จุดประสงค์public class UserService{ private readonly IUserRepository _repo;
public UserService(IUserRepository repo) => _repo = repo;
public async Task<User> GetUserAsync(int id) { // async ตลอดสาย ไม่ block thread และไม่ต้องพึ่ง Task.Run var user = await _repo.FindAsync(id);
// ไม่ยุ่งกับ GC — ปล่อยให้ runtime จัดการหน่วยความจำตามที่มันถูกออกแบบมา if (user is null) throw new UserNotFoundException(id); // exception เจาะจง รักษา stack trace เดิมไว้
return user; }}หลักที่ใช้ตัดสินไม่ใช่ “code สั้นลง” แต่คือ ทุกบรรทัดที่เหลืออยู่ตอบได้ว่ามันแก้ปัญหาอะไร ถ้าตอบไม่ได้ แปลว่ามันคือริทัวลที่ควรลบ
ทางแก้และการ refactor
หัวข้อที่มีชื่อว่า “ทางแก้และการ refactor”แผนภาพด้านล่างแสดงจุดตัดสินใจที่แยกระหว่าง Calendar Coder กับนักพัฒนาที่เขียน code อย่างตั้งใจ (program deliberately)
flowchart TD
A[เจอ code หรือคำแนะนำที่ดูน่าเชื่อถือ] --> B[คัดลอกมาใช้ตามรูปแบบ]
B --> C{เข้าใจไหมว่าทำไมมันได้ผล}
C -->|ไม่เข้าใจ| D[ใส่ริทัวลที่ไม่มีจุดประสงค์]
D --> E[code บวมและเปราะ]
E --> F[พังเมื่อบริบทเปลี่ยนแต่ไล่หาสาเหตุไม่เจอ]
C -->|เข้าใจ| G[เก็บเฉพาะส่วนที่แก้ปัญหาจริง]
G --> H[code เรียบง่ายและอธิบายได้]
แนวทางแก้ไม่ใช่การห้ามลอก code หรือห้ามฟังคำแนะนำ แต่คือการใส่ วิจารณญาณ เข้าไปทุกครั้ง:
- ใช้วิจารณญาณกับทุกแหล่ง — ไม่ว่าคำแนะนำจะมาจาก block ดัง ซีเนียร์ในทีม หรือ AI ให้ถามเสมอว่า ทำไม และ อย่างไร จึงควร (หรือไม่ควร) ทำสิ่งนั้น นี่คือหัวใจที่ DevIQ เน้น และเป็นเส้นแบ่งเดียวกับ Found on Internet
- ตั้งชื่อแรงที่ pattern นั้นแก้ — ก่อนรับ pattern หรือโครงสร้างใด ให้ระบุให้ได้ว่ามันแก้ปัญหาอะไร ใน codebase ของเรา ไม่ใช่แก้ปัญหาทั่ว ๆ ไปในทฤษฎี ถ้าตอบไม่ได้ ก็ยังไม่ควรใส่มันเข้ามา
- ทำ deletion test — ลองลบ pattern หรือบรรทัดที่สงสัยออก แล้วรันเทส ถ้าเทสยังผ่านและ code เรียบง่ายขึ้น แสดงว่าสิ่งนั้นคือริทัวลที่ไม่จำเป็น
- พิสูจน์ invariant ด้วยเทส — ถ้า pattern จำเป็นจริง ให้เขียนเทสที่พิสูจน์ว่าทำไมมันถึงต้องอยู่ตรงนั้น เทสจะกลายเป็นเอกสารที่อธิบายเหตุผลให้คนรุ่นถัดไป
- เขียนโปรแกรมอย่างตั้งใจ — ตามคำแนะนำของ The Pragmatic Programmer: จงอธิบาย code ของตัวเองให้จูเนียร์ฟังได้อย่างละเอียด ถ้าอธิบายไม่ได้ แปลว่าคุณกำลังพึ่งความบังเอิญ ให้ทดสอบสมมติฐานแทนที่จะเดา
- ยึดความเรียบง่ายเป็นค่าตั้งต้น — เมื่อไม่แน่ใจ ให้เลือกทางที่เรียบง่ายที่สุดที่ยังแก้ปัญหาได้ (KISS) แล้วค่อย refactor เพิ่มความซับซ้อนเมื่อมีเหตุผลรองรับจริง ไม่ใช่เพราะ “เห็นคนอื่นทำ”