ภาระทางความคิดมีความสำคัญ
(github.com/zakirullin)- โปรเจกต์โอเพนซอร์สนี้รวบรวมวิธีการและกรณีศึกษาต่าง ๆ อย่างเป็นระบบเพื่อลด ภาระทางความคิดในการพัฒนาซอฟต์แวร์
- โค้ด โครงสร้าง และการนามธรรมที่ซับซ้อนโดยไม่จำเป็น บั่นทอนประสิทธิภาพการทำงานของนักพัฒนาและเพิ่มต้นทุนการบำรุงรักษา
- แทนที่จะเป็นโมดูลที่ “เล็กและตื้น” โมดูลที่ดีควรมี โครงสร้างที่ลึกพร้อมอินเทอร์เฟซที่เรียบง่ายและความสามารถที่ทรงพลัง
- การนามธรรมมากเกินไป การพึ่งพาเฟรมเวิร์ก และการใช้หลัก DRY เกินจำเป็น กลับเป็นสาเหตุที่ทำให้ภาระทางความคิดเพิ่มขึ้น
- สถาปัตยกรรมที่ดีที่สุดคือ โค้ดเบสที่เรียบง่ายและทำให้นักพัฒนาใหม่เข้าใจได้อย่างรวดเร็ว
สรุปโปรเจกต์และความสำคัญ
เอกสารโอเพนซอร์สบน GitHub นี้มุ่งเน้นที่ ‘ภาระทางความคิด (cognitive load)’ ซึ่งเป็นหนึ่งในหลักการสำคัญของการพัฒนาซอฟต์แวร์ จุดเด่นที่สุดของรีโพซิทอรีนี้คือการจัดระเบียบต้นตอของความซับซ้อนที่นักพัฒนาต้องเผชิญจริง โดยไม่ขึ้นกับขนาดทีมหรือกระแสเทคโนโลยี พร้อมยกตัวอย่างและวิธีแก้ไขที่หลากหลาย ต่างจากเอกสารแนว best practice ทั่วไปตรงที่คำนึงถึงภาระทางจิตใจและการรับรู้ด้วย จึงช่วยเรื่องการบำรุงรักษาและการ onboarding สมาชิกใหม่ได้อย่างเป็นรูปธรรม
บทนำ
- แนวคิดที่กำลังเป็นกระแสหรือ best practice ที่เราได้ยินในงานพัฒนานั้น มักล้มเหลวในการใช้งานจริงอยู่บ่อยครั้ง
- สาเหตุสำคัญของความสับสนและการสูญเสียเวลา/ต้นทุนในงานจริง คือ ภาระทางความคิดที่สูง
- นักพัฒนาใช้เวลาไปกับ การทำความเข้าใจและอ่านโค้ดมากกว่าการเขียนโค้ด
- เนื้อหานี้มุ่งเน้นวิธีปฏิบัติเพื่อลด ภาระทางความคิดที่ไม่จำเป็น (Extraneous Cognitive Load)
ภาระทางความคิดคืออะไร
- ภาระทางความคิดหมายถึง ปริมาณข้อมูลที่นักพัฒนาต้องเก็บไว้ในหัวเพื่อให้งานเสร็จสมบูรณ์
- โดยเฉลี่ยแล้ว ความจำระยะสั้นสามารถเก็บ ‘ข้อมูลเป็นก้อน’ ได้พร้อมกันเพียงราว 4 ก้อน (เช่น เงื่อนไข ค่าตัวแปร เป็นต้น)
- เมื่อภาระทางความคิดเข้าใกล้จุดวิกฤต ความสามารถในการเข้าใจและความเร็วในการพัฒนาจะลดลงอย่างมาก
ประเภทของภาระทางความคิด
- ภาระทางความคิดโดยเนื้อแท้ (Intrinsic) : เกิดจากความยากโดยธรรมชาติของงานเอง ลดไม่ได้
- ภาระทางความคิดที่ไม่จำเป็น (Extraneous) : เกิดจากความซับซ้อนที่มนุษย์สร้างขึ้น เช่น วิธีนำเสนอข้อมูล การออกแบบโครงสร้าง หรือแพตเทิร์นที่ไม่จำเป็น ซึ่งสามารถลดได้อย่างจริงจัง
ตัวอย่างเชิงปฏิบัติและแนวทางปรับปรุง
เงื่อนไขที่ซับซ้อน
- โค้ดที่มีหลายเงื่อนไขซ้อนกันทำให้ต้องจดจำรายละเอียดในแต่ละขั้นมากขึ้น ส่งผลให้ ภาระทางความคิดเพิ่มขึ้น
- วิธีแก้: เพิ่มตัวแปรกลางที่มีความหมายเพื่อแยกจุดประสงค์ของแต่ละเงื่อนไขให้ชัดเจน
if ซ้อนกัน vs Early Return
- แพตเทิร์น Early Return ช่วยลดภาระจากการต้องจำเงื่อนไขล่วงหน้าได้ดีกว่า if ที่ซ้อนกันหลายชั้น
- ทำให้จำเฉพาะ ‘เส้นทางปกติ’ (happy path) ก็พอ จึงช่วยปลดปล่อย พื้นที่ในหน่วยความจำขณะทำงาน
ผลข้างเคียงของโครงสร้างการสืบทอด
- ระบบการสืบทอดที่ลึก (เช่น คลาสA → คลาสB → คลาสC …) ทำให้การเข้าใจโค้ดต้องจำข้อมูลหลายชั้นพร้อมกัน ส่งผลให้ ภาระทางความคิดพุ่งสูง
- วิธีแก้: ให้ความสำคัญกับ composition ก่อน ใช้การประกอบแทนการสืบทอดที่ไม่จำเป็น
โมดูล/ฟังก์ชันตื้น ๆ ที่มีมากเกินไป
- แทนที่จะมีคลาสเล็กและตื้น 80 คลาส การมี deep module ไม่กี่ตัวที่มีอินเทอร์เฟซเรียบง่ายแต่ทรงพลัง จะดูแลง่ายกว่า
- มีการยกตัวอย่างอินเทอร์เฟซที่เรียบง่ายของ UNIX I/O ได้แก่
open,read,write,lseek,close
ความเข้าใจผิดเกี่ยวกับ 'หลักการความรับผิดชอบเดียว'
- การตีความอย่างคลุมเครือของคำว่า ‘ทำแค่อย่างเดียว’ กลับทำให้เกิดการนามธรรมแบบตื้นและไม่ชัดเจนมากขึ้น
- ในทางปฏิบัติ ควรตีความว่า ‘รับผิดชอบต่อผู้มีส่วนได้ส่วนเสียหนึ่งกลุ่ม’ เพื่อช่วยให้เข้าใจความเชื่อมโยงทางธุรกิจและลดภาระทางความคิด
การใช้ไมโครเซอร์วิสมากเกินไป
- การมี ไมโครเซอร์วิสตื้น ๆ จำนวนมาก ทำให้ต้องจำความสัมพันธ์และการเชื่อมต่อของแต่ละบริการตลอดเวลา ส่งผลให้ ภาระทางความคิดและต้นทุนในการดีบัก/ปล่อยระบบเพิ่มขึ้น
- ในระยะแรก โครงสร้างแบบ monolith ที่สมบูรณ์และออกแบบดี อาจเอื้อต่อการบำรุงรักษามากกว่า
ฟีเจอร์/ตัวเลือกของภาษาที่มากเกินไป
- ฟีเจอร์ใหม่จำนวนมากของภาษาเอง (โดยเฉพาะ C++ เป็นต้น) กลับทำให้ต้องไล่ตามว่า ‘ทำไมถึงเขียนแบบนี้’ จน ภาระในการจดจำสะสมมากขึ้น
- เป็นการเพิ่มภาระทางความคิดรองที่ไม่เกี่ยวกับธุรกิจ
การแมป HTTP status code กับ business logic
- การแมปความหมายทางธุรกิจภายในกับ HTTP status code (401, 403, 418 เป็นต้น) แบบตามใจ ทำให้ สมาชิกทีมทุกคนต้องท่องจำ
- วิธีปรับปรุง: ส่งต่อข้อมูลอย่างสม่ำเสมอด้วย string code ที่อธิบายตัวเองได้ (เช่น
"jwt_has_expired")
การใช้หลัก DRY (Do not repeat yourself) เกินความจำเป็น
- ในโค้ดเบสที่ซับซ้อน หากพยายาม กำจัดความซ้ำซ้อน มากเกินไป จะเกิด dependency ที่แน่นเกินและทำให้ภาระทางความคิดกับต้นทุนการเปลี่ยนแปลงเพิ่มขึ้นแทน
- มีการอ้างคำกล่าวของ Rob Pike ว่าบางครั้ง ‘การคัดลอกเล็กน้อยในระดับ local’ ดีกว่า
การพึ่งพาเฟรมเวิร์กและผลเสียของ Layered Architecture
- เมื่อพึ่งพาโครงสร้าง ‘เวทมนตร์’ ของเฟรมเวิร์กมากเกินไป นักพัฒนาใหม่จะต้องใช้เวลา นานมาก เพื่อทำความเข้าใจตรรกะภายใน
- การสะสมของชั้นการนามธรรมทำให้ ภาระทางความคิดพุ่งสูงในเวลาที่ต้องตามรอยปัญหาจริง
- ควรมุ่งที่หลักการพื้นฐาน เช่น Dependency Inversion, Info Hiding และการควบคุม Cognitive Load
ความเข้าใจผิดเกี่ยวกับ Domain-Driven Design (DDD)
- แก่นของ DDD คือการวิเคราะห์โดเมนปัญหา แต่การยึดติดกับโครงสร้างโฟลเดอร์/แพตเทิร์นกลับทำให้เกิด การตีความตามอัตวิสัยและเพิ่มภาระทางความคิด
- เฟรมเวิร์กอย่าง ‘Team Topologies’ อาจมีประสิทธิภาพมากกว่าในการแบ่งภาระทางความคิด
ความคุ้นเคย vs ความเรียบง่าย
- ความคุ้นเคยไม่เท่ากับความเรียบง่าย สิ่งที่คุ้นอาจรู้สึกเบาเพราะชิน ไม่ได้หมายความว่าโครงสร้างนั้นง่ายจริง
- หากพนักงานใหม่ยังสับสนเกิน 40 นาที นั่นคือ สัญญาณว่าควรปรับปรุงโค้ด
กรณีศึกษาความสำเร็จ/ความล้มเหลวจริง
- อย่าง Instagram ก็สามารถขยายระบบและบำรุงรักษาได้ดีด้วย สถาปัตยกรรม monolith ที่เรียบง่าย
- บริษัทที่มี “นักพัฒนาที่ฉลาดมาก” สร้างโครงสร้างซับซ้อนขึ้นมา กลับพบประสบการณ์ล้มเหลวบ่อยครั้ง
- โครงสร้างที่นักพัฒนาทุกคนอ่านได้ง่ายและ onboarding ได้เร็ว มีบทบาทสำคัญต่อการเพิ่มประสิทธิภาพการทำงาน
บทสรุป
- ภาระทางความคิดที่ไม่จำเป็นซึ่งเกินไปจากตัวงานโดยเนื้อแท้ ส่งผลเสียต่อทุกฝ่ายในการพัฒนา
- โค้ดที่ดีที่สุดคือ โค้ดที่ทั้งคนอื่นในอนาคตและตัวเราเองสามารถเข้าใจได้เร็วที่สุด
- เมื่อเทียบกับโครงสร้างที่ “ดูฉลาด” แล้ว วิธีแก้แบบธรรมดาและตรงไปตรงมา มักเอื้อต่อการบำรุงรักษาและประสิทธิภาพของทีมมากกว่าในระยะยาว
อ้างอิง/ความเห็นเพิ่มเติม
- มีการอ้างถึงความคิดเห็นของ Rob Pike, Andrej Karpathy, Elon Musk, Addy Osmani, antirez (ผู้พัฒนา Redis) และนักพัฒนาชื่อดังคนอื่น ๆ
- ชี้ให้เห็นประเด็นที่สอดคล้องกับกรณีจริงของระบบขนาดใหญ่อย่าง Chromium, Redis, Instagram เป็นต้น
- ความเรียบง่าย ความชัดเจน และการลดภาระทางความคิด คือหัวใจของความยั่งยืนของซอฟต์แวร์
คุณค่าของโปรเจกต์โอเพนซอร์สนี้
- เป็นเอกสารที่เน้น ประสบการณ์จริงของนักพัฒนาและกรณีใช้งานเชิงปฏิบัติ ซึ่งหนังสือออกแบบซอฟต์แวร์หรือแพตเทิร์นจำนวนมากมักมองข้าม
- ช่วยทีมต่าง ๆ ได้ทันทีอย่างเป็นรูปธรรมในเรื่อง การ onboarding นักพัฒนาใหม่ การรีวิวสถาปัตยกรรม และการบำรุงรักษาระยะยาว
- ทำหน้าที่เป็นเช็กลิสต์ให้ย้อนกลับมาทบทวนโค้ดได้ ผ่านกรอบแนวคิดที่ชัดเจนของ ‘ภาระทางความคิด’
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
หนังสือ A Philosophy Of Software Design ของ John Ousterhout เป็นสิ่งที่ทำให้ฉันตาสว่างที่สุด หนังสือเล่มนี้ดีที่สุดในหัวข้อนี้และอยากแนะนำให้นักพัฒนาทุกคนอ่าน แก่นสำคัญคือ เป้าหมายของการออกแบบซอฟต์แวร์ควรเป็นการลดความซับซ้อนให้ต่ำที่สุด โดยนิยามของความซับซ้อนในที่นี้คือ “การเปลี่ยนแปลงมันยากแค่ไหน” และ “ความยาก” นี้ถูกกำหนดโดยภาระทางการรับรู้ที่ต้องใช้ในการทำความเข้าใจโค้ด
ปัญหาคือ ไม่มีกฎข้อไหนที่จะแทนที่วิจารณญาณ ประสบการณ์ และสัญชาตญาณได้ในท้ายที่สุด กฎทุกข้อสามารถกลายเป็นเครื่องมือในการโต้เถียงได้ และคุณไม่มีวันชนะในการถกเถียงเรื่องสถาปัตยกรรม เหตุผลที่บทความนี้ดีคือ คนที่ไม่จำเป็นต้องอ่านก็รู้อยู่แล้ว ส่วนคนที่จำเป็นต้องเข้าใจจริง ๆ ก็อาจจะไม่เข้าใจมันอยู่ดี สุดท้ายแล้วนี่ไม่ใช่ปัญหาทางเทคนิค แต่เป็นปัญหาเรื่องคนและวัฒนธรรม สถาปัตยกรรมเป็นสิ่งที่ตามมาจากคนและวัฒนธรรม เหมือนกับที่มี Rob Pike และ Google จึงมี Go เกิดขึ้น ไม่ใช่อ่านหนังสือเล่มเดียวแล้วจะสร้างภาษาแบบ Go ได้
ฉันคิดว่าหลักการ DRY (อย่าทำซ้ำ) เป็นสิ่งที่ควรค่อยพิจารณาหลังจากที่เราเข้าใจแอปพลิเคชันดีพอและผ่านการทำมาหลายเวอร์ชันแล้ว ช่วงแรกกลับควรยอมทำซ้ำเพื่อทำความเข้าใจพื้นที่ปัญหาก่อน และค่อยคิดเรื่องการดูแลรักษาในเวอร์ชันที่สอง พอถึงเวอร์ชันที่สามค่อยเริ่มใช้ DRY ก็ยังไม่สาย
หาหนังสือเล่มนี้โดยไม่ต้องจ่ายเงินให้ Jeff Bezos ยากมาก ถ้าใครรู้จักผู้เขียน John อยากให้ช่วยบอกเรื่องนี้ที ฉันหาไม่ได้ทั้งที่ร้านหนังสือในมหาวิทยาลัย ร้านหนังสือท้องถิ่น หรือแม้แต่ร้าน Powell’s
ฉันเลิกตามหาคำตอบซอฟต์แวร์ที่สมบูรณ์แบบมานานแล้ว ดูเหมือนจะไม่มีใครจัดระเบียบเรื่องนี้ได้อย่างสมบูรณ์ อาวุธที่ดีที่สุดที่เรามีจึงยังเป็นภูมิปัญญาและประสบการณ์ของผู้คน บริบท อุตสาหกรรม และทีมแตกต่างกันมากเกินกว่าจะนิยามเป็นตัวเลขหรือกฎตายตัวได้ สำหรับฉัน เป้าหมายในการออกแบบคือการหาสมดุลระหว่าง “ความเละเทะ” กับ “ความงาม” สิ่งที่ยากที่สุดคือ ธุรกิจเต็มไปด้วยความไม่แน่นอน แต่ซอฟต์แวร์เป็นสิ่งที่กำหนดชัดเจน ทำให้ความต้องการทางธุรกิจเปลี่ยนตลอดและยากจะบีบให้เข้ากับความแข็งตัวของระบบคอมพิวเตอร์ ทุกวันนี้ฉันจะลองรีแฟกเตอร์ก็ต่อเมื่อเริ่มรู้สึกอึดอัดตอนจะแก้โค้ดจริง ๆ และถึงอย่างนั้นก็จะจัดระเบียบให้น้อยที่สุด พอทำรีแฟกเตอร์แบบค่อยเป็นค่อยไปหลายรอบ ก็จะเริ่มเห็นแพตเทิร์นใหม่ ๆ และดึงมันออกมาเป็น abstraction ได้
หนังสือเล่มนี้แหละดีที่สุดในเรื่องนั้น บทความที่กำลังพูดถึงก็ได้แรงบันดาลใจมาจากหนังสือเล่มนี้ ฉันเองก็เคยคุยกับผู้เขียน John เรื่องเนื้อหาในนั้นอยู่บ้างหนึ่งสองครั้ง
ความสามารถในการเขียนโค้ดที่ช่วยลดภาระทางการรับรู้ของคนอื่นเป็นทักษะที่ทั้งหายากและยากมาก ต่อให้มีพรสวรรค์ก็ยังต้องใช้ความพยายามอย่างสม่ำเสมอ หน้าที่ของนักพัฒนาคือบีบอัดแนวคิดหลักให้เหลือแต่แก่นแท้ และเปิดเผยเฉพาะความซับซ้อนที่จำเป็นจริง ๆ ซึ่งในทางปฏิบัติฉันแทบไม่ค่อยเห็นคนทำได้ดี
โค้ดที่ออกแบบมาดีจริง ๆ จะทำให้คนเผลอคิดว่า “หรือปัญหานี้มันง่ายมาตั้งแต่แรก?” ในทางกลับกัน โค้ดประเภท “บ้านไพ่” ที่มองเห็นความซับซ้อนเด่นชัดกลับมักได้รับการยอมรับว่าเป็นร่องรอยของความทุ่มเท และบางทียังพาไปสู่การเลื่อนตำแหน่งด้วย
เรื่องนี้ใช้ได้เหมือนกันกับการออกแบบอินเทอร์เฟซหรือ UX/interaction นักพัฒนารับมือกับภาระทางการรับรู้ได้ดีกว่าคนทั่วไปมาก แต่ถ้าจะทำให้คนที่ไม่ใช่สายเทคนิคใช้งานได้ดี ก็ต้องก้าวออกจากสัญชาตญาณแบบนักพัฒนาของตัวเอง ซึ่งยากมาก การออกแบบเครื่องมือให้ผู้ใช้ทั่วไปแก้ปัญหาที่ซับซ้อนได้อย่างเป็นธรรมชาตินั้นยากจริง ๆ
abstraction ส่วนใหญ่ไม่ค่อยอยู่รอดเมื่อความต้องการเปลี่ยนไป เฟรมเวิร์กที่ฉันชอบคือเฟรมเวิร์กที่รู้ว่าไม่มีทางสร้างชั้น abstraction ที่สมบูรณ์แบบตลอดกาลได้ จึงตั้งใจเปิด “ทางหนีทีไล่” ไว้ให้ (เช่น ในเฟรมเวิร์ก Web UI ให้ direct reference กับ html element ได้) ความฉลาดคือการรู้ว่าเราไม่มีวันทำนายอนาคตได้สมบูรณ์
ในหลายบริษัท ทักษะลักษณะนี้ไม่ใช่สิ่งจำเป็นเท่าไรนัก แต่เป็นเหมือนโบนัสมากกว่า แค่ดู codebase หลัก ๆ ก็พอจะเห็นได้
ฉันคิดว่าตัวเองเป็นหนึ่งในพวก “นักพัฒนาฉลาดแบบเนิร์ด” ที่ชอบสร้าง abstraction ช่วงนี้ทั้งแปลกใจและกังวลที่อุตสาหกรรมกำลังวนกลับไปสู่วงจร “สถาปัตยกรรมกอง if-statement” ฉันเข้าใจว่าทำไมหลายคนถึงชอบ เพราะมันดูเรียบง่าย เข้าใจง่าย และแค่ปิด ticket ที่ได้รับมาก็พอ กระบวนการพัฒนาส่วนใหญ่พึ่งพากอง if อยู่แล้ว และวิธีนี้ก็ใช้ได้ดีจริงกับเคสบางระดับ แถมต่อให้เกิดเหตุข้อมูลส่วนตัวรั่วครั้งใหญ่ ก็แทบไม่มีใครต้องรับผิดชอบ แต่ปัญหาคือฉันไม่แน่ใจว่ามีทางเลือกที่ดีกว่านี้ไหม abstraction หรู ๆ หรือสถาปัตยกรรมสวย ๆ ก็ดูเหมือนไม่ได้ช่วยเพิ่มความสอดคล้องของโค้ดจริง โดยเฉพาะในสภาพแวดล้อมองค์กรที่เจ้าของ business logic แทบไม่ใส่ใจ logic เลย ทำให้สร้าง abstraction สวย ๆ อย่างระมัดระวังไม่ได้ สุดท้ายข้อกำหนดแบบ “คำสั่งซื้อส่งได้แค่ที่อยู่เดียว” ก็อาจกลายเป็น “ลูกค้ารายใหญ่ขอให้ส่งได้หลายที่อยู่” แบบฉับพลัน และในความสับสนแบบนี้ abstraction ที่ไร้บั๊กแทบเป็นไปไม่ได้ จนบางทีฉันก็คิดว่า สำหรับซอฟต์แวร์องค์กร กอง if-statement อาจเป็นสิ่งที่ดีที่สุดแล้วก็ได้
สำหรับคำถามว่า “กอง if-statement ดีที่สุดไหม” ขอแนะนำ บทความ Big Ball of Mud และ บทความสรุป ว่าด้วยสถาปัตยกรรมซอฟต์แวร์ขนาดใหญ่ในโลกจริง ระบบจริงนั้นพัฒนาเปลี่ยนแปลงอยู่เสมอ เริ่มต้นจาก “ก้อนโคลน” แล้วค่อย ๆ ปรับปรุงเป็นส่วน ๆ จากนั้นเมื่อความเปลี่ยนแปลงมาอีก abstraction ที่เคยงดงามก็พังลงอีก แก่นสำคัญคือทำให้ระบบเติบโตด้วยการผสมผสานระหว่าง domain modeling, abstraction ที่พอเหมาะ, และการเปลี่ยนแปลงแบบรื้อทำใหม่เมื่อจำเป็น เหมือนหมู่บ้านเล็ก ๆ ที่ค่อย ๆ โตเป็นเมือง
ซอฟต์แวร์แบบนี้ก็ยังสร้าง abstraction สวย ๆ ได้ แต่พอ business logic เข้ามาเกี่ยวข้อง การคงสภาพมันไว้จะยากมาก ส่วนที่ abstraction ยังอยู่ได้มักเป็นองค์ประกอบเชิงผลิตภัณฑ์ เช่น การยืนยันตัวตน สิทธิ์การเข้าถึง log ฐานข้อมูล middleware และโครงสร้างพื้นฐาน พอ logic ภายในธุรกิจเริ่มมีผลต่อ abstraction เมื่อไร ทุกอย่างก็มักจะกลับไปเละอีก บางที่ถึงขั้นให้คนฝั่งธุรกิจมาจัดการ logic เอง ซึ่งทำให้ทั้งทดสอบยาก เข้าใจไม่ได้ และแม้แต่จำลองสถานการณ์ก็ยังแทบทำไม่ได้ สุดท้ายก็ต้องมี operator และเกิดสถานการณ์ที่ junior developer เขียนโค้ดผ่านเครื่องมือกราฟิก หลังจาก rewrite ไปหลายรอบ ผ่านไป 2–3 ปีก็กลับมายุ่งเหยิงเหมือนเดิมทุกที
เป็นไปไม่ได้เลยที่คนฝั่งธุรกิจจะอธิบาย business logic ให้คน implement เข้าใจได้อย่างสมบูรณ์ ต่อให้พวกเขาเข้าใจเอง ก็ไม่ได้แปลว่าจะถ่ายทอดออกมาในมุมของการเขียนโค้ดได้ กลับกัน อย่างน้อยต้องมีคน implement สักคนที่ซึมซับประสบการณ์ของผู้ใช้จริงอย่างลึกซึ้งจึงจะเข้าใจได้ถูกต้อง แต่ในโลกจริง องค์กรมักบังคับให้แยกฝ่ายกัน ทำให้ “business logic” กลายเป็นพื้นที่ที่ไม่มีใครใส่ใจจริงจัง และสุดท้ายก็เหลือแค่การขยับ if-statement ไปมา
แก่นแท้คือ ความเป็นจริงหรือธุรกิจเองก็คือกอง if-statement อยู่แล้ว ถ้าปัญหาหรือโดเมนมีความเป็นเทคนิคชัดเจนหรือทำให้เป็นนามธรรมได้ ก็อาจลด if ด้วย abstraction ได้ แต่ถ้าตัวโดเมนเองเต็มไปด้วยความโกลาหล abstraction ก็จำเป็นต้องใส่ “ความยืดหยุ่น” เข้าไปเป็นค่าตั้งต้นอยู่ดี บางครั้งความขัดแย้งเองก็อาจกลายเป็นฟีเจอร์ได้
ถ้าลองเล่นกับเครื่องมืออย่าง Codex CLI จะเห็นว่า เมื่อมันเจอบั๊ก มันมักจะสร้างแพตช์ if-statement สำหรับเคสนั้นโดยเฉพาะเสมอ ถ้าคุณไม่บอกแพตเทิร์นให้ชัดและไม่ขอ abstraction ใหม่ มันก็จะเติมสิ่งที่มันเรียกว่า “heuristic” ต่อไปเรื่อย ๆ บั๊ก 10 แบบก็ได้แพตช์ 10 ชิ้น และพอมีบั๊กคล้ายกันตัวที่ 11 มันก็แน่นอนว่าใช้ไม่ได้ วิธีแก้ที่ Codex เสนอจึงเป็นเพียงชุดของการเพิ่ม if ซ้ำ ๆ
ฉันสงสัยว่าสถานการณ์ที่ถูกขอให้ไปแก้โปรเจกต์ที่ไม่รู้อะไรเลยเกิดขึ้นบ่อยแค่ไหนในโลกจริง ถ้าไม่ได้ย้ายงานบ่อยมาก ก็คงราว ๆ ทุกสองปีครั้ง ฉันคิดว่าควรโฟกัสกับปัญหาที่ยากจริง ไม่ใช่ปัญหาที่แค่เกิดจากขาดประสบการณ์
ตอนทำงานในองค์กรนักพัฒนาของ Microsoft อยู่ 8 ปี โมเดลประเภทวิศวกรยุคแรกมีประโยชน์มาก มี persona อยู่สามแบบ ก่อนจะถูกแทนด้วย framework ที่ซับซ้อนกว่าในภายหลัง ถึงอย่างนั้น persona ดั้งเดิมก็ยังช่วยฉันสื่อสารกับวิศวกรคนอื่นได้ดีมากตลอดอาชีพ
ฉันคิดว่าควรมี persona แบบ Amanda เพิ่มเข้ามาด้วย หลังจากรีวิวโค้ดเละ ๆ ของตัวเองและของคนอื่นมา 20 ปี ก็ซึมซับได้แล้วว่าเหนือสิ่งอื่นใด โค้ดมีไว้ให้ “คนอ่าน” ฉันอยากสร้างทีมที่มีแต่ Amanda
Mort คือสายปฏิบัตินิยม, Einstein คือสายสมบูรณ์แบบ, ส่วน Elvis... เอาจริง ๆ ฉันมองว่าแทบจะเป็นโทษต่อโปรเจกต์ อาจมีประโยชน์ด้านแรงกระตุ้นบ้างเล็กน้อย แต่ทีมในอุดมคติคือทีมที่ผสม Mort กับ Einstein ได้ดี ทำให้เรียบง่ายเท่าที่จำเป็นและถูกต้องพอที่จะไม่ทรมานเวลาบำรุงรักษา ที่จริงถ้าปล่อยให้ Mort ดูแลโปรเจกต์ระยะยาว สุดท้ายเขาก็มักจะเริ่มใส่ใจแบบ Einstein เอง พูดนอกเรื่องนิด แต่ agent เขียนโค้ดอัตโนมัติช่วงนี้มีความเป็น Mort สูงมาก จนฉันต้องกลายเป็น Mort มาคอยคุมพวกมันอีกที
persona เป็นเครื่องมือที่ดี แต่จากประสบการณ์ของฉัน เมื่อเวลาผ่านไปมันมักเสื่อมสภาพกลายเป็นทางลัดที่ผิด Elvis ในความเป็นจริงเคยเป็นคำใช้ในการเมืองภายในองค์กร และเหมือนกฎของ Goodhart พอมันดูมีตรรกะถูกต้อง ทุกคนก็พยายามหยิบไปใช้ในการถกเถียง จนประโยชน์ใช้สอยลดลง Alan Cooper เคยนำแนวคิด persona เข้ามาพร้อมกับการพัฒนา Visual Basic ประเด็นสำคัญคือการเข้าใจว่ามุมมองที่ต่างจากเรา เช่น นักวิทยาศาสตร์กับนักพัฒนาเฟิร์มแวร์ มีระบบคุณค่าคนละแบบ หนังสือที่เกี่ยวข้อง
ฉันมองว่าวิศวกรที่ดีที่สุดคือคนที่ปรับสัดส่วนของทั้งสามลักษณะให้เหมาะกับโปรเจกต์ สถานการณ์ และเป้าหมายส่วนตัวได้ บทบาทต่างกันต้องการสัดส่วนไม่เท่ากัน เช่น งาน optimization ของ compiler จะต้องการ Einstein กับ Mort มากกว่า ส่วนโค้ดแบบ game engine ก็มีสัดส่วนอีกแบบ แม้จะถกเถียงกันได้ว่าคุณลักษณะแต่ละอย่างแม่นแค่ไหน แต่ประเด็นคือทุกคนควรขยับเปลี่ยนได้ตามกาลเวลา
ฉันคิดว่าวิธีนี้ต้องมีขอบเขต เพราะถ้าจัดคนเข้าหมวดหมู่ง่ายเกินไป ก็อาจกลายเป็นการตีตราที่ไม่ยุติธรรมและติดตัวถาวร จากประสบการณ์ของฉัน ผู้บริหารกลับชอบ Mort มากกว่า Elvis เสียอีก ทางออกจริงอยู่ที่ภาวะผู้นำและการจัดการ ถ้ากำหนดมาตรฐานคุณภาพโค้ดเป็นส่วนหนึ่งของ requirement ให้ Mort เขาก็สามารถส่งงานที่ดีพอได้แม้จะช้าลงบ้าง ส่วน Elvis ต้องมีข้อจำกัด และ Einsteins ต้องได้รับกรอบเวลาแบบปฏิบัติได้จริงอย่างชัดเจน วิธีนี้ก็ยังมีข้อจำกัดตรงที่มองข้ามความซับซ้อนของมนุษย์อยู่ดี
ฉันกังวลว่า AI จะกลายเป็นผลเสียต่ออุตสาหกรรมซอฟต์แวร์ เพราะ AI ไม่มีข้อจำกัดแบบมนุษย์ มันจึงสามารถเขียนโค้ดที่ซับซ้อนมาก อ่านยาก แต่ยังทำงานได้ และเมื่อถึงวันที่มันพังขึ้นมา สุดท้ายอาจไม่มีใครแก้ได้เลย เพราะงั้นฉันจึงแนะนำวิศวกร junior ว่าอย่าพึ่งพา AI อย่างเดียว ควรทำความเข้าใจลักษณะของ codebase ด้วย ไม่อย่างนั้นพวกเขาจะสูญเสียความสามารถในการเขียนโค้ดด้วยตัวเอง
ฉันคิดว่าข้อโต้แย้งนี้ไม่น่าโน้มน้าวนัก เพราะถึง AI จะหยุดทำงานไป มันก็ยังสามารถถูกใช้ให้กลับมาแก้ใหม่ได้อยู่ดี
จากประสบการณ์ของฉัน ฉันจะให้ AI ลดความซับซ้อนของโค้ดไปเรื่อย ๆ จนกว่าฉันจะเข้าใจมันได้จริง ถ้าวิธีมันซับซ้อนเกินไปก็ให้ทำให้ง่ายขึ้น ถ้าใช้ไลบรารีภายนอกที่ไม่จำเป็นก็ให้ตัดออก ใช้ stdlib หรือใช้เฉพาะ dependency ที่มีอยู่แล้ว AI เขียนโค้ดแทนฉัน ไม่ได้เขียนให้ตัวมันเอง
บางที AI อาจช่วยเพิ่มคุณภาพซอฟต์แวร์ได้ด้วยซ้ำ เพราะมันลดภาระทางการรับรู้ในระดับ low-level design ทำให้เราเอาเวลาไปคิดเรื่องสถาปัตยกรรมระดับสูงได้มากขึ้น
สำหรับ “นิสัยเฉพาะของนักพัฒนาฉลาด” ที่กล่าวถึงในบทความ ฉันรู้สึกว่าผู้เขียนเหมือนจะหมายถึงคนที่เข้าใจได้เฉพาะโค้ดที่ตัวเองเขียนหรือโค้ดที่เขียนในสไตล์ของตัวเองเท่านั้น แต่ความลับที่แท้จริงของการลดภาระทางการรับรู้คือการลดปริมาณโค้ดที่ “ไม่จำเป็นต้องอ่าน” ให้เหลือน้อยที่สุด ขอบเขตที่แข็งแรงและ API ที่ชัดเจนช่วยให้การแก้ไขเปลี่ยนแปลงง่ายขึ้น ประเด็นคือไม่ต้องสนใจทั้งระบบ แต่สนใจเพียงความซับซ้อนที่เกิดจากอินเทอร์เฟซที่นิยามไว้อย่างดี ความเป็น “นักพัฒนาฉลาด” จึงอาจเป็นแค่ปัญหาที่ผิวหน้า
นี่แหละคือข้อดีสูงสุดของแนวคิด microservices สองทีมสื่อสารกันผ่าน API เท่านั้น และถ้าใช้เครื่องมืออย่าง schema มากำหนดขอบเขตให้เคร่งครัด ก็จะทำให้การเปลี่ยนแปลงเกิดขึ้นอย่างระมัดระวังมากและต้องคิดล่วงหน้าอย่างลึกซึ้ง เหมือนเป็นการใส่แรงเสียดทานแบบตั้งใจด้วยเทคโนโลยีไว้ในจุดที่เราไม่อยากให้มันเปลี่ยน
ตอนดีบักฉันรู้สึกบ่อยมากว่า ถ้ามีโค้ดบางส่วนที่ optimize มาเพื่อให้ debug ตรงบน call stack มากเกินไป มันจะเหนื่อยมาก ดังนั้นการแยกเฉพาะบางส่วนออกเป็นฟังก์ชันและแบ่ง commit ให้ดี จะช่วยให้ rollback การเปลี่ยนแปลงโค้ดที่ไม่ต้องการได้ง่ายขึ้น และทำให้โฟกัสกับโค้ดได้ดีขึ้น ถึงใน PR จะขึ้นมาพร้อมกันทีเดียว แต่ถ้าขอบเขตชัดเจนก็ยังดีกว่ามาก
โดยทั่วไปแล้วนักพัฒนามักคิดเป็น “สัญญาและอินเทอร์เฟซ” ไม่เก่งนัก ควรทำให้คนมองเห็นเฉพาะคำมั่นสัญญาภายนอก ไม่ใช่ implementation ภายใน เป้าหมายคือให้เข้าใจได้โดยไม่ต้องอ่านโค้ด ถ้าจะต้องเปิดโค้ดของฉันเข้าไปดูเพื่อทำความเข้าใจ แปลว่าฉันล้มเหลวแล้ว
ต่อให้ API ชัดเจนแค่ไหน โค้ดภายในก็อาจไม่ได้เข้าใจง่ายเสมอไป ภายนอกอาจดูเหมือนทำงานดี แต่ในความเป็นจริง หลายครั้งก็ยังต้องขุดลงไปดูข้างในอยู่ดี
ฉันชอบมากที่บทความนี้สรุปประสบการณ์ของฉันได้ตรงเหลือเกิน วิธีวิทยาการเขียนโปรแกรมที่เรียนมาอย่างเป็นทางการส่วนใหญ่ พอถึงเวลาที่คนอื่นต้องมาอ่านโค้ดของฉัน กลับให้ผลตรงกันข้าม โค้ดที่ซ่อนความซับซ้อนแบบ Rust หรือ C++ นั้นหนักมาก แต่พอเห็นโครงสร้างแบบ C ที่ซ่อนโค้ดไว้หลังเทมเพลต 6 ชั้นไม่ได้ ฉันกลับรู้สึกโล่งใจ
ประเด็นที่ว่าต้องปฏิบัติต่อการเข้าถึงโค้ดเหมือนเป็นพลเมืองชั้นหนึ่งนั้นโดนใจมาก ใช้กฎเป็นแนวทาง แต่คนที่เก่งจริงคือคนที่ตัดสินใจได้ตามบริบทว่าจะทำให้โค้ดอ่านง่ายขึ้นด้วยการฝ่าฝืนกฎหรือเสริมกฎเมื่อไร สุดท้ายแล้วความสามารถในการรับรู้ภาระทางการรับรู้ของโค้ดและ trade-off ต่าง ๆ แบบเป็นธรรมชาติต่างหากที่สำคัญ ไม่ว่าจะเป็นการทำซ้ำหรือ abstraction ก็ตาม ต้องคิดถึงคนถัดไปเสมอ รวมถึงตัวเราเองในอีก 6 เดือนข้างหน้า เวลามีคนขอ “เพิ่มกฎอีกข้อ” ฉันรู้สึกว่ามันก็คือการทำซ้ำปัญหาเดิมอีกครั้ง หลังจาก guideline พื้นฐานแล้ว สิ่งที่เหลือคือ tacit knowledge หรือความรู้โดยนัยจากประสบการณ์ เมื่อเวลาผ่านไป เราจะเริ่ม “รู้สึกได้เอง” ว่าโค้ดแบบไหนดีหรือไม่ดี สุดท้ายมันต้องสัมผัสด้วยตัวเองจริง ๆ
มีคนบอกให้เขียนเอกสารว่า “ทำไม” แต่สำหรับฉัน การแยก “ทำไม” ออกจาก “อะไร” นั้นยากมาก เลยบันทึกทั้งสองอย่างไว้ ความเห็นที่แย่ที่สุดคือ
แบบนี้ การผสมโค้ดกับคอมเมนต์ทำให้อ่านยากและบังคับให้สลับบริบทบ่อยเกินไป ฉันจึงชอบแบบนี้มากกว่า
คือแยกคอมเมนต์กับโค้ดให้ชัดเจน ถึงอย่างนั้น ต่อให้เขียนแค่ “อะไร” ก็ยังดีกว่าไม่มีอะไรเลย และช่วยลดภาระทางการรับรู้ได้
คอมเมนต์รูปแบบนี้ส่วนใหญ่ดูแลให้ทันสมัยได้ยาก และเพราะ implementation เปลี่ยนปีละสัก 5 ครั้ง มันจึงล้าสมัยและสร้างความสับสนอย่างรวดเร็ว ในโปรเจกต์ล่าสุด ฉันลบคอมเมนต์ส่วนใหญ่ออก เหลือไว้แค่ logic ที่ยากจริง ๆ กับเอกสารระดับบนสุด
ฉันคิดว่ามีบางครั้งที่ต้องเขียนทั้งสองอย่าง (why/what) บางครั้งต้องอธิบายเหตุผล บางครั้งต้องทำให้พฤติกรรมชัดเจน ทุกวันนี้ฉันรู้สึกว่าคุณค่าของคอมเมนต์ถูกประเมินต่ำเกินไป ไม่จำเป็นต้องใส่คอมเมนต์ทุกบรรทัด แต่โค้ดแบบ “self-documenting” ส่วนใหญ่ก็ไม่ได้อ่านง่ายขนาดนั้น
ทุกวันนี้ฉันรู้สึกว่าสิ่งที่ยากที่สุดคือการตัดสินใจว่าอะไรควรอยู่ในโค้ด และอะไรควรอยู่ใน design doc/technical doc ข้อสรุปของฉันคือ คอมเมนต์ในโค้ดควรใช้เพื่ออธิบายเจตนาที่ไม่เป็นธรรมชาติหรือซ่อนอยู่ เช่น “เราทำ memoize ตรงนี้เพราะ X”
ฉันขำกับคอมเมนต์แบบ x=4 สไตล์ ChatGPT มาก แต่ข้อเสียของรูปแบบที่เสนอมาก็คือ คอมเมนต์มีความเสี่ยงจะล้าสมัยจนไม่ตรงกับโค้ด ดังนั้นเวลาปรับโค้ดก็ต้องอัปเดตคอมเมนต์เสมอ
ฉันไม่ได้เป็นโปรแกรมเมอร์โดยการศึกษา แต่เป็นนักฟิสิกส์ ดังนั้นทุกครั้งที่เขียนคอมเมนต์ก็พยายามปรับให้เป็นแบบนี้ สมัยเป็นนักศึกษา ธรรมเนียมคือยิ่งใส่คอมเมนต์เยอะยิ่งได้คะแนนดี