'นักพัฒนาสมองกรัก' (2022)
(grugbrain.dev)- บทเรียนสำคัญที่สุดที่พบซ้ำ ๆ จากประสบการณ์พัฒนามายาวนานคือ ความซับซ้อน คือ “ศัตรูตลอดกาล” ของ codebase และทำให้การเปลี่ยนแปลงเล็ก ๆ ไปทำให้ส่วนที่ไม่คาดคิดพังได้
- เครื่องมือที่ทรงพลังที่สุดในการลดความซับซ้อนคือการพูดว่า “no” กับฟีเจอร์และ abstraction แต่เมื่อจำเป็นต้องประนีประนอมกับความเป็นจริง ให้ใช้ วิธี 80/20 เพื่อส่งมอบคุณค่าส่วนใหญ่ด้วยโค้ดจำนวนน้อย
- การออกแบบและการทดสอบควรให้น้ำหนักกับการหา จุดขอบเขตที่ดี หลังจากรูปทรงของระบบเริ่มปรากฏ มากกว่าการทำ abstraction เร็วเกินไป และให้ความสำคัญกับ integration test ที่คงอยู่ได้นานกว่า unit test
- เครื่องมือ, type system, logging, debugger และการออกแบบ API ควรลดภาระทางความคิดของนักพัฒนา และคุณค่าเชิงปฏิบัติที่ใหญ่ของ type system อยู่ที่ IDE autocomplete
- เทคนิคที่มีชื่อดี ๆ อย่าง DRY, separation of concerns, microservices, SPA, generics และการ refactor มากเกินไป ก็อาจกลายเป็นทางผ่านของความซับซ้อนได้ หากทำให้การเข้าใจและบำรุงรักษาโค้ดยากขึ้น
ความซับซ้อนคือความเสี่ยงอันดับต้น ๆ ของ codebase
- ความซับซ้อน ทำให้ระบบที่ตอนแรกยังเข้าใจได้ กลายเป็นสิ่งที่จัดการยากอย่างฉับพลัน และการแก้ไขจุดหนึ่งทำให้จุดอื่นที่ดูไม่เกี่ยวข้องพัง
- ความซับซ้อนอาจเข้ามาผ่านนักพัฒนาหรือ project manager ที่มีเจตนาดี และบางครั้งความซับซ้อนที่เรานำเข้ามาเองก็เป็นต้นเหตุของปัญหา
- ไม่มี “กระบอง” สารพัดประโยชน์สำหรับแก้ความซับซ้อน การรับมือที่ดีที่สุดคือไม่ปล่อยให้มันเข้ามาตั้งแต่แรก
“no” และ “ok”: การปฏิเสธกับการประนีประนอมตามความจริง
- อาวุธที่ดีที่สุดในการกันความซับซ้อนคือ “no”
- “ไม่ทำฟีเจอร์นั้น”
- “ไม่สร้าง abstraction นั้น”
- อย่างไรก็ตาม แม้ “no” จะเป็นคำแนะนำด้านวิศวกรรมที่ดี แต่อาจไม่เป็นผลดีต่อค่าตอบแทนที่มากขึ้นหรือการเลื่อนตำแหน่ง
- เมื่อในความเป็นจริงต้องประนีประนอม ให้ตอบว่า “ok” แล้วหา 80/20 solution
- วิธีที่สร้าง 80% ของคุณค่าที่ต้องการด้วยโค้ด 20%
- อาจไม่มีฟีเจอร์ตกแต่งครบทุกอย่างและดูไม่สวยนัก แต่ส่งมอบคุณค่าส่วนใหญ่พร้อมรักษาความซับซ้อนไว้ในระดับต่ำ
- บางครั้ง project manager อาจลืมรายละเอียด requirement ย้ายงาน หรือออกจากบริษัท ดังนั้นวิธี 80/20 จึงอาจเป็นทางเลือกที่ดีกว่า
การออกแบบและการแยกโค้ดเร็วเกินไปเป็นอันตราย
- ช่วงต้นโปรเจกต์ รูปทรงของระบบยังไม่ชัดเจนเหมือน “น้ำ” จึงควรหลีกเลี่ยง การแยกส่วนเร็วเกินไป
- เมื่อเวลาผ่านไป จุดขอบเขตที่ดีจะเริ่มปรากฏ
- interface กับส่วนที่เหลือของระบบแคบ
- ซ่อนความซับซ้อนภายในด้วยฟังก์ชันหรือ abstraction จำนวนน้อย
- หากสร้าง abstraction เร็วเกินไป จะเกิด abstraction ที่ผิดได้ง่าย จึงให้น้ำหนักกับการรอมากกว่า
- สำหรับนักพัฒนาที่อยากสร้าง abstraction ใหญ่ ๆ ตั้งแต่ต้น การให้ผลลัพธ์ที่ทิ้งได้อย่าง UML หรือให้ทำ demo ที่ใช้งานได้ในวันพรุ่งนี้ อาจช่วยได้
- prototype ช่วงต้นจะแสดงโค้ดที่ทำงานได้จริงและข้อจำกัดตามความเป็นจริงได้เร็วกว่า
การทดสอบไม่ใช่ “ค่อยทำทีเดียวทีหลัง” แต่คือ “ทำไปพร้อมกับที่รูปทรงเริ่มชัด”
- การทดสอบช่วยป้องกันปัญหามากมาย แต่ไม่เห็นด้วยกับการบังคับ test first ตั้งแต่ยังไม่เข้าใจ domain
- หลัง prototype เมื่อโค้ดเริ่มแข็งตัว มักชอบเขียน test ส่วนใหญ่ในช่วงนั้น
- ทัศนคติที่ข้ามการทดสอบโดยอ้างว่า “บนเครื่องผมใช้ได้” เป็นสิ่งที่แย่มาก
- ความชอบต่อประเภทการทดสอบชัดเจน
- unit tests: มีประโยชน์ในช่วงต้นโปรเจกต์ แต่พังง่ายเมื่อ implementation เปลี่ยน และอาจทำให้การ refactor ยากขึ้น
- end to end: แสดงพฤติกรรมทั้งระบบได้ แต่เมื่อพังแล้วเข้าใจสาเหตุยาก และหากพังบ่อยก็ถูกมองข้ามได้ง่าย
- integration tests: สูงพอให้เห็นความถูกต้องของระบบ และต่ำพอให้ใช้ debugger หาสาเหตุได้ จึงใกล้กับจุดที่ดีที่สุด
- เมื่อ API ที่เป็นจุดขอบเขตเริ่มเสถียร ให้ลงทุนกับ integration test รอบ ๆ จุดนั้นอย่างจริงจัง
- end-to-end test ควรรักษาให้เป็นชุดเล็ก ๆ ที่จัดการได้ดี โดยเน้นฟังก์ชัน UI ที่ใช้บ่อยที่สุดและ edge case สำคัญบางส่วน
- ใช้ mocking เฉพาะเมื่อจำเป็นจริง ๆ และถ้าเป็นไปได้ให้ใช้เฉพาะในระดับจุดขอบเขตใหญ่ ๆ
- เมื่อพบบั๊ก ชอบแนวทางยกเว้นคือทำให้เกิดซ้ำด้วย regression test ก่อน แล้วค่อยแก้
Agile, การ refactor และ Chesterton's Fence
- Agile เองไม่ใช่สิ่งเลวร้ายที่สุด และสามารถใช้เป็นหนึ่งในวิธีจัดองค์กรพัฒนาได้ แต่ควรระวัง “agile shaman” ที่ใช้มันเหมือนเครื่องมือ免责สำหรับความล้มเหลว
- prototype, เครื่องมือที่ดี และการจ้างนักพัฒนาที่ดี สำคัญต่อความสำเร็จของซอฟต์แวร์มากกว่า และไม่มี process ใดเป็น silver bullet ที่แก้ได้ทุกปัญหา
- การ refactor มีประโยชน์โดยเฉพาะช่วงท้ายโปรเจกต์เมื่อโค้ดเริ่มแข็งตัวแล้ว แต่ยิ่ง refactor ใหญ่เท่าไร โอกาสล้มเหลวยิ่งสูง
- แบ่งให้เล็กแล้วค่อยทำ
- รักษาให้ระบบยังทำงานต่อไปได้มากที่สุด
- จบแต่ละขั้นแล้วจึงไปขั้นถัดไป
- abstraction ที่มากเกินไปอาจนำไปสู่ความล้มเหลวของการ refactor และความล้มเหลวของระบบ
- Chesterton's Fence เชื่อมโยงกับท่าทีว่า อย่าลบโค้ดที่ดูไม่ชอบออกก่อนจะเข้าใจว่าทำไมมันจึงมีอยู่
- โค้ดที่กำลังทำงานอยู่ แม้ไม่สมบูรณ์แบบก็ควรได้รับความเคารพ และยิ่งระบบใหญ่ ยิ่งต้องใช้เวลาเข้าใจก่อน
Microservices และเครื่องมือ
- Microservices เพิ่ม network call เข้าไปในปัญหายากของการแบ่งระบบให้ถูกต้อง
- เครื่องมือช่วยเพิ่ม productivity และความเข้าใจของนักพัฒนาได้อย่างมาก
- ในสภาพแวดล้อมใหม่ ควรใช้เวลาเรียนรู้เครื่องมือ
- การใช้เวลา 2 สัปดาห์เรียนรู้เครื่องมือ อาจทำให้ความเร็วในการพัฒนาเร็วขึ้น 2 เท่า
- หากไม่มีเอกสาร ต้องถามนักพัฒนาคนอื่นและขุดลงไปเอง
- code completion ของ IDE ช่วยให้ไม่ต้องจำ API ทั้งหมด และแทบจะจำเป็นสำหรับการพัฒนา Java
- debugger ที่ดีสำคัญมาก
- conditional breakpoint
- expression evaluation
- การไล่ดู stack
- นักพัฒนาใหม่ควรเรียนรู้ debugger ที่ใช้ได้อย่างลึกซึ้ง และสิ่งนี้อาจสอนเรื่องคอมพิวเตอร์ได้มากกว่าชั้นเรียนมหาวิทยาลัย
Type system, expression และ DRY
- คุณค่าที่ใหญ่ที่สุดของ type system อยู่ที่ การสนับสนุนจากเครื่องมือ ซึ่งแสดงรายการการกระทำที่เป็นไปได้เมื่อพิมพ์จุด
- ความถูกต้องของ type ก็ดี แต่ code completion และการนำทางในโค้ดมีคุณค่าเชิงปฏิบัติมากกว่า
- ควรระวังนักพัฒนาที่ใช้ type system ในเชิง abstract มากเกินไป
- generics อาจอันตรายเป็นพิเศษ
- ในกรณีส่วนใหญ่ ชอบจำกัดไว้ประมาณ container class
- แทนที่จะยัด conditional expression สั้น ๆ ไว้บรรทัดเดียว การแยกด้วยตัวแปรกลางที่มีชื่อมีความหมายจะดีกว่าสำหรับการ debug และการทำความเข้าใจ
- DRY เป็นคำแนะนำที่ดี แต่ต้องมีสมดุล
- โค้ดซ้ำที่เรียบง่ายและชัดเจน บางครั้งดีกว่า callback, closure หรือ object model ที่ซับซ้อน
- หากการกำจัดความซ้ำซ้อนเองเพิ่มความซับซ้อน ก็อาจไม่คุ้ม
Separation of concerns, closure และ logging
- Separation of Concern เป็นแนวคิดที่ทรงพลัง แต่ในทางปฏิบัติอาจทำให้ต้องสลับไปมาระหว่างหลายไฟล์เพื่อเข้าใจพฤติกรรม และเสียเวลา
- ชอบทางเลือกอย่าง locality of behavior มากกว่า
- หากวางโค้ดไว้ใกล้กับ “สิ่งที่ทำงาน” เมื่อดูสิ่งนั้นก็จะรู้ทันทีว่ามันทำอะไร
- closure เหมาะสำหรับ abstract การทำงานกับ collection แต่ใช้เพียงเล็กน้อยเหมือนเกลือก็พอ
- “callback hell” ใน JavaScript เป็นตัวอย่างของความซับซ้อนจากการใช้ closure มากเกินไป
- logging สำคัญมาก โดยเฉพาะในสภาพแวดล้อม deployment บนคลาวด์
- บันทึก log ทุกจุดแยกของ logic สำคัญ
- สำหรับ request ที่ผ่านหลายเครื่อง ให้ใส่ request ID เพื่อเชื่อม log เข้าด้วยกันได้
- หากเป็นไปได้ ให้ปรับ log level ได้แบบ dynamic
- หากเป็นไปได้ ให้ปรับ log level รายผู้ใช้เพื่อ debug ปัญหาของผู้ใช้เฉพาะราย
- library สำหรับ logging อาจซับซ้อน แต่การลงทุนสร้าง logging infrastructure ให้ดีจะให้ผลตอบแทนมากในภายหลัง
Concurrency, optimization และ API
- Concurrency เป็นพื้นที่ที่ควรเกรงกลัว และควรเลือก model ที่เรียบง่ายที่สุดเท่าที่ทำได้
- web request handler ที่ไม่มี state
- remote work queue ที่ไม่พึ่งพากัน
- API ที่เรียบง่าย
- บนเว็บ optimistic concurrency อาจเหมาะสมดี
- โครงสร้างข้อมูลสำหรับ concurrency อย่าง ConcurrentHashMap ของ Java ก็ต้องใช้อย่างระมัดระวัง
- Optimization ควรเริ่มหลังจาก profile ประสิทธิภาพจริงยืนยันปัญหาที่ชัดเจนแล้ว
- อย่าดูแค่ CPU
- การเข้าถึง network เทียบได้กับ CPU cycle จำนวนมาก จึงควรลดลงเมื่อทำได้
- หากเห็น nested loop แล้วรีบกำจัด
O(n^2)ทันที อาจเพิ่มความซับซ้อนขึ้น
- API ที่ดีควรทำให้นักพัฒนาไม่ต้องคิดมาก
- หากผู้เขียน API ติดอยู่กับมุมมอง implementation หรือมุมมอง domain จะทำให้ใช้งานยาก
- ควรมี การแบ่งชั้น โดยให้ API ที่เรียบง่ายสำหรับกรณีเรียบง่าย และมี API ที่ซับซ้อนกว่าแยกต่างหากสำหรับกรณีซับซ้อน
- หากเป็น object-oriented พฤติกรรมควรอยู่บนวัตถุนั้นให้มากที่สุดเท่าที่ทำได้
- ตัวอย่าง stream/collector ของ Java ถูกวิจารณ์ว่าเป็น API ที่ทำให้งานทั่วไปต้องอ้อมโดยไม่จำเป็น
Parsing, Visitor Pattern และ frontend
- มองว่า parser แบบ recursive descent นั้นสนุกและสวยงาม
- parser generator ทำให้เข้าใจและ debug โค้ดที่สร้างขึ้นได้ยาก และซ่อนธรรมชาติแบบ recursive ของ grammar
- parser สำหรับ production จริงส่วนใหญ่เป็น recursive descent และแนะนำ Crafting Interpreters ของ Bob Nystrom
- Visitor pattern สรุปสั้น ๆ ว่า “bad”
- วิธีแยก frontend กับ backend แล้วเชื่อม SPA library และ GraphQL JSON API ผ่าน HTTP นั้นใกล้เคียงกับการสร้างรังความซับซ้อนสองรัง
- ควรระวังกระแสที่ใช้ frontend library ขนาดใหญ่ แม้กับเว็บไซต์ง่าย ๆ หรือการนำ form ใส่ฐานข้อมูล
- เพื่อทำให้ความซับซ้อนลดลง จึงสร้าง htmx และ hyperscript
- รักษา HTML ให้เรียบง่าย
- หลีกเลี่ยง JavaScript จำนวนมาก
- React ดีกว่าสำหรับงานและแอปพลิเคชันบางประเภท แต่มองว่าเป็นทางเลือกที่ก้าวเข้าสู่เส้นทางของความซับซ้อนฝั่ง frontend
กระแส, FOLD และ impostor syndrome
- การพัฒนามีกระแสมากมาย และเด่นชัดเป็นพิเศษใน frontend
- ควรสงสัยแนวทางใหม่ที่อ้างว่าเป็นการปฏิวัติ
- ในวงการคอมพิวเตอร์ มีไอเดียมากมายที่เคยถูกลองมาแล้วอย่างน้อยหนึ่งครั้ง
- ไอเดียแย่ ๆ อาจกลับมาพร้อมชื่อใหม่
- การที่ senior developer พูดต่อหน้าคนอื่นว่า “อันนี้ซับซ้อนเกินไปจนเข้าใจยาก” เป็นเรื่องดี
- FOLD ย่อมาจาก Fear Of Looking Dumb คือภาวะที่ไม่กล้ายอมรับความซับซ้อนเพราะกลัวดูโง่
- เมื่อ senior บอกว่าซับซ้อน junior ก็ยอมรับได้ง่ายขึ้นว่าตนไม่เข้าใจ
- FOLD เป็นแหล่งพลังสำคัญที่ทำให้ความซับซ้อนเติบโต โดยเฉพาะในหมู่นักพัฒนารุ่นใหม่
- นักพัฒนามักแกว่งไปมาระหว่างความรู้สึกว่าควบคุมทุกอย่างได้ กับความรู้สึกว่าไม่รู้อะไรเลย และ impostor syndrome เป็นเรื่องพบได้ทั่วไป
- หากทุกคนรู้สึกเหมือน impostor ก็ไม่มีใครเป็น impostor และนักพัฒนารุ่นใหม่สามารถเดินหน้าสายอาชีพต่อได้ แม้จะมีความท้อแท้และความกังวล
สิ่งที่แนะนำให้อ่านและบทสรุป
- สิ่งที่แนะนำให้อ่าน
- บทสรุปสุดท้ายเหมือนกับตอนเริ่ม: ความซับซ้อนนั้นแย่มาก ๆ
1 ความคิดเห็น
ความคิดเห็นบน Hacker News
แปลกที่คนฉลาด ๆ มักถูกดึงดูดเข้าหา ความซับซ้อน เหมือนแมลงเม่าบินเข้ากองไฟ
ต้องใช้เวลาหลายปีกว่าจะเรียนรู้วิธีต่อสู้กับแรงกระตุ้นที่อยากออกแบบเกินจำเป็น
พอเริ่มมองเห็นแล้วก็ยากที่จะมองข้าม และตอนนี้ผมรู้ได้ทันทีว่าโค้ดถูกออกแบบเกินจำเป็นหรือไม่
น่าเสียดายที่โค้ดราว 99% ดูเหมือนจะถูกออกแบบเกินจำเป็น และมีแรงจูงใจอย่างมากให้เดเวลลอปเปอร์เพิ่ม ความเป็นคนที่ทดแทนไม่ได้ ของตัวเองและชั่วโมงที่เรียกเก็บเงินได้ให้สูงสุด
แม้แต่เดเวลลอปเปอร์ที่ดูทำงานแบบกลไกล้วน ๆ และไร้อัตตา ก็ยังออกแบบเกินจำเป็นอยู่บ่อยครั้ง ซึ่งเกิดขึ้นโดยไม่รู้ตัว
เดเวลลอปเปอร์จำนวนมากไม่ได้คิดอย่างมีสติกับโค้ดทุกบรรทัดที่เขียน แต่แค่ตั้งเป้าหมายแล้วหยิบโค้ดแรกที่ทำให้เข้าใกล้เป้าหมายนั้นขึ้นมา จึงไม่ตระหนักว่าในทุกขั้นตอนมีทางเลือกที่ดีกว่าอยู่
อย่างแรก ผมชอบปริศนาที่สนุก โดยเฉพาะโค้ดเชิงพาณิชย์ ถ้าทำให้ถูกต้องก็มักจะค่อนข้างน่าเบื่อ ทำให้ผมเผลอผลักดันฟีเจอร์ที่สนุกต่อการ implement โดยไม่รู้ตัว และคำว่าสนุกในที่นี้สุดท้ายก็หมายถึงฟีเจอร์ที่ทำให้ทุกอย่างซับซ้อนเกินไป
อย่างที่สอง ระหว่างที่กำลังเขียนโปรแกรมอะไรสักอย่าง ทุกการตัดสินใจของผมดูสมเหตุสมผลและจำเป็นไปหมด ต้องรอจนภายหลังเมื่อถอยออกจากโค้ดแล้วกลับมาพยายามทำความเข้าใจด้วยสายตาใหม่ ถึงจะเห็นว่าผมทำมันเละเทะแค่ไหน
สำหรับปัญหาส่วนใหญ่ วิธีแก้ที่เห็นเด่นชัดที่สุดมักค่อนข้างซับซ้อน และการจะรู้ว่าควรไปหาวิธีแก้ที่เรียบง่ายจากตรงไหนต้องอาศัย ปัญญา·ทักษะ·ความรู้โดเมน วิธีแก้ที่เรียบง่ายและสะอาดแทบไม่เคยชัดเจนเลย
“เรากำลังค่อย ๆ implement message queue ที่ทำไปครึ่ง ๆ กลาง ๆ ของเราเองอยู่นี่นา ใช้ message queue มาตรฐานเถอะ”, “เรากำลังสร้างโปรโตคอล binary framing แบบกำหนดเองที่บั๊กเยอะอยู่นี่นา ใช้ protobuf/msgpack เถอะ”, “อย่าสร้างโปรโตคอล RPC แบบกำหนดเองเพื่อดึงข้อมูลเลย ใช้ REST บน HTTP เถอะ แบบนั้นเราสามารถใส่ nginx ตรงกลางเพื่อ cache response จาก backend และทิ้ง cache แบบกำหนดเองได้ด้วย”
สุดท้ายแล้ว หากมีเวลาและได้รับรางวัลให้ทำเช่นนั้น เราก็สามารถเขียนโค้ดที่สมบูรณ์แบบ เรียบง่าย และไม่ซ้ำซ้อนได้ แต่ส่วนใหญ่แล้วสิ่งที่ได้รับรางวัลคือการปล่อยของออกมา และการแสดง ความสามารถทางเทคนิค ผ่านปริมาณงาน สติปัญญา และความสามารถในการออกแบบที่ใช้เพื่อปล่อยสิ่งนั้น
ข้อสรุปตามธรรมชาติคือทุกอย่างจะบวมพอง กลายเป็นกองวิธีแก้ชั่วคราวที่ออกแบบเกินจำเป็น และถูกเขียนใหม่ทุก ๆ 3–7 ปี
ผมไม่เคยดูข้อมูล แต่คิดว่า “ครึ่งชีวิตของการเขียนใหม่” น่าจะสัมพันธ์กับอัตราการลาออกหรือระยะเวลาการทำงานเฉลี่ย ต่อให้ผู้คนพยายามมากขึ้นเพื่อไม่ออกแบบเกินจำเป็น ยังไงมันก็มีแนวโน้มจะถูกเขียนใหม่อยู่ดี
ในฐานะคนรักความสมบูรณ์แบบ เรื่องนี้ขัดใจมาก แต่บางครั้งการสร้างสิ่งที่ซับซ้อนและออกแบบเกินจำเป็นกลับได้รับรางวัลดีกว่าการใช้เวลาคิดให้นานขึ้นเพื่อหาวิธีแก้ที่เรียบง่ายที่สุด อาจมีองค์กรที่ดีกว่านี้อยู่ก็ได้ แต่ตลอด 9 ปีที่ทำงานเป็น software engineer ผมยังไม่เจอ
โปรเจกต์โอเพนซอร์สเป็นตัวอย่างโต้แย้งที่ดีที่สุดที่นึกออก แต่แม้แต่ไลบรารีที่ดีก็มักถูกเขียนใหม่หรือออกเวอร์ชันใหม่เมื่อผู้ดูแลเปลี่ยนไป แรงจูงใจทางการเงิน ระหว่างโอเพนซอร์สกับกองของกระจัดกระจายสำหรับองค์กรที่ software engineer เต็มเวลาจำนวนมากบน HN พบเห็นนั้นแตกต่างกันมาก คล้ายกับการเปรียบเทียบบทความวิชาการที่ขัดเกลาอย่างดี กับอีเมลงานที่เขียนลวก ๆ
ผมจำได้ว่า Casey จาก Handmade Hero เรียกสไตล์การเขียนโปรแกรมของตัวเองว่า “compression-oriented” คือเขียนโค้ดไปก่อน แล้วค่อย ๆ แยกสิ่งที่อยู่ด้วยกันออกมา
ทำ abstraction เมื่อมันเกิดการซ้ำ ไม่ใช่ตั้งใจออกแบบ abstraction ขึ้นมา ผมกำลังใช้วิธีนี้มากขึ้นเรื่อย ๆ
นั่นไม่ได้หมายความว่ามันผิดโดยตัวมันเองเสมอไป แต่ถ้าเป็นการประเมินคุณภาพงานของคนอื่น ก็ดูมีโอกาสสูงที่จะมีอคติรุนแรงปนอยู่
ผมชอบไซต์นี้มากจริง ๆ และมันทำให้หัวเราะได้เสมอ ส่วนที่ชอบที่สุดเป็นพิเศษคือ microservices
“grug สงสัยว่าทำไมสมองใหญ่ถึงเอาปัญหาที่ยากที่สุดอย่างการแบ่งระบบให้ถูกต้อง แล้วใส่ network call เข้าไปด้วย สำหรับ grug มันดูสับสนมาก”
“ความซับซ้อนนั้นแย่มาก ๆ”
“ถ้าต้องเลือกระหว่างความซับซ้อนกับการสู้ตัวต่อตัวกับไทแรนโนซอรัส grug จะเลือกไทแรนโนซอรัส อย่างน้อย grug ก็เห็นไทแรนโนซอรัสได้”
มีประเด็นที่ไม่ค่อยพอใจเกี่ยวกับเรื่องไทป์
grug บอกว่าคุณค่าของระบบไทป์มากกว่า 90% อยู่ที่การมีรายการสิ่งที่ทำได้โผล่ขึ้นมาอย่างกับเวทมนตร์เมื่อกดจุดบนคีย์บอร์ด แต่จูเนียร์ในบริษัทเรามักปล่อยโค้ดขึ้นโปรดักชันที่พังเพราะการเข้าถึงค่า null แล้ว Sentry ก็แจ้งให้รู้
ในช่วงพัฒนาแบบเร่งเข้มข้น โดยประมาณจะมี บั๊กการเข้าถึง null ที่ตรวจพบวันละหนึ่งรายการต่อจูเนียร์ดีเวลลอปเปอร์ 1 คน
ถ้าใช้ระบบไทป์ที่ดีพร้อมการตรวจสอบแบบสแตติก IDE จะบอกว่า “อันนี้อาจเป็น null นะ แน่ใจหรือ?” ซึ่งน่าจะช่วยได้มาก
และถึงไม่มี static type ก็ยังทำ autocomplete ได้
ส่วนที่ว่า “พวกหมอผีระบบไทป์สมองโตบอกว่าความถูกต้องของไทป์คือแก่นของระบบไทป์ แต่ grug เห็นว่าพวกหมอผีเหล่านั้นไม่ได้ deploy โค้ดบ่อยนัก โค้ดที่ไม่ได้ deploy อาจถูกต้องในความหมายหนึ่ง แต่ไม่ใช่ความถูกต้องแบบที่ grug พูดถึง” นั้นหยาบคายและไม่จำเป็น
ผม deploy โค้ดมาเป็นหลักด้วย C, PHP, Python, Haskell และ Python ที่มีไทป์กำกับ ในภาษาแบบมีไทป์ อัตราบั๊กที่หลุดไปถึงโปรดักชันต่ำกว่ามาก นั่นเป็นหนึ่งในเหตุผลที่ชอบไทป์
อีกอย่างคือ การรีแฟกเตอร์ ง่ายขึ้นมาก เมื่อเปลี่ยนสิ่งที่ถูกใช้อย่างแพร่หลาย จะตรวจจุดเรียกที่พังทั่วทั้ง codebase ได้อย่างน่าเชื่อถือภายในไม่กี่วินาที และช่วยได้มากในการปรับปรุง codebase ที่โตขึ้นอย่างต่อเนื่อง
แต่ยอมรับระบบไทป์แบบประนีประนอมที่จับปัญหาบางส่วนได้ และเหนือสิ่งอื่นใดคือให้ เวทมนตร์ autocomplete โดยไม่ต้องถูกฉุดรั้งด้วยรายละเอียดทั้งหมดที่จำเป็นต่อการพิสูจน์ความถูกต้องอย่างสมบูรณ์
ดูเหมือนเขามองว่า static type ระดับ C หรือ Java นั้นโอเคหรือดี ส่วนของที่หวือหวากว่าใน Rust หรือ Scala เป็นการเสียเวลา ผมคิดว่าเขาผิดเต็ม ๆ แต่ชีวิตก็เป็นแบบนี้แหละ
ผมเข้าใจว่า “หมอผีระบบไทป์สมองโต” ไม่จำเป็นต้องเป็นคนที่เขียนโค้ด แต่หมายถึงคนที่ขายคอร์สเรื่องนั้นและทำให้ผู้คนตื่นเต้น เป็นต้น
ตอนต้นบทความก็ใช้คำว่า “หมอผี” ตอนพูดถึง Agile ด้วย ซึ่งมีคนไม่น้อยที่ทำงานใกล้เคียงกับการขายคอร์สมากกว่าการพัฒนาซอฟต์แวร์จริง
การทำงานกับคนที่หมกมุ่นกับ ลำดับชั้นของไทป์ นั้นทนได้ยาก และคำอธิบายความชอบธรรมมักค้างอยู่ในโลกของ “ถ้าเกิดว่า” และ “แต่นั่นไม่ sound”
การเสื่อมลงของคุณภาพจำนวนมากที่ระบบไทป์ก่อขึ้น มาจากคนที่เชื่อว่าถ้าใส่ไทป์ให้มากพอจะป้องกันบั๊กได้ทั้งหมด ซึ่งไม่เป็นความจริง
คิดว่าในอีกไม่กี่เดือนข้างหน้าทุกคนจะกระโดดขึ้นรถไฟ HTMX กันหมด และมันจะเขย่าหัวนักพัฒนาหนุ่มสาวจำนวนมาก ช่วยประหยัดพลังงานทั่วโลกได้มาก และทำให้หลายคนมีความสุข
จากนั้นเมื่อคนหนุ่มสาวขี้สงสัยเหล่านี้กดลิงก์ใน htmx.org ไปเรื่อย ๆ จนเจอ hyperscript มันจะเหมือนฉากที่แวมไพร์โผล่ออกมาใน Dusk till Dawn
มีใครสักคนเบื่อความซับซ้อนของ JavaScript แล้วสร้างไลบรารี JavaScript แบบเรียบง่ายขึ้นมา ผู้คนแห่กันไปเพราะความเรียบง่ายนั้น ไลบรารีต้องรองรับฟีเจอร์เว็บทั้งหมดเลยซับซ้อนขึ้น แล้วก็มีใครสักคนที่เบื่อความซับซ้อนนั้นสร้างไลบรารี JavaScript แบบเรียบง่ายขึ้นมาอีก
เริ่มจาก JavaScript ล้วน ๆ ต่อสู้กับ IE5/6 อยู่หลายปี แล้ว jQuery ก็ช่วยทุกคนไว้ AngularJS ไม่ค่อยดีเลยข้ามไป พอ React beta ออกมาก็ชอบเพราะอุดมคติแบบ functional programming ตอนนี้กำลังฝ่ากอง transpiling, hot reload, Typescript และไลบรารี React หนัก ๆ อยู่
เคยทำโปรเจกต์ด้วย Intercooler และ HTMX มาบ้าง HTMX โดยรวมแข็งแรงดี แต่ไม่ใช่รถไฟขบวนแรกที่ผ่านเมืองนี้
อยากเห็นคำวิจารณ์ htmx แบบจริงจัง
ส่วนตัวคิดว่าความท้าทายใหญ่ที่ต่อเนื่องของเว็บคือการหาวิธีอัปเดตหน้าให้ดี และเราก็ลองสารพัดวิธีกันมาตลอด ดังนั้นจึงเกิดความเหนื่อยล้า และบางไอเดียก็สุดโต่งหรือกลายเป็นของรก ๆ เมื่อเวลาผ่านไป
Htmx ดูเหมือนเป็น ปฏิกิริยาตอบโต้ แบบ grug ที่ยึดแนวทำน้อยลงและ YAGNI
อนาคตยังไม่แน่นอน แต่งานอย่าง signals ก็ยังดูเหมือนสิ่งศักดิ์สิทธิ์อยู่ มีความพยายามมากมายจากหลายทิศทาง และเรายังอยู่ระหว่างบุกเบิกแนวรบนี้รวมถึงแนวรบอื่น ๆ
MobX และ Svelte เป็นต้นก็เดินทางมายาวไกลแล้ว เส้นทางยังไม่จบ และการที่ยังต้องพันอยู่กับสัตว์ประหลาดแห่งความซับซ้อน ผมอยากคิดว่าไม่ใช่สัญญาณของความอ่อนแอ แม้ว่าความคิดแบบอนุรักษนิยม หรือแบบ grug อาจมองเช่นนั้น แต่เป็นความพยายามที่สมควรเพื่อไปให้ถึงสิ่งที่ เรียบง่ายและดี ไม่ใช่แนวทางที่เรียบง่ายเฉย ๆ
_="on load wait 5s then transition opacity to 0 then remove me"โอ้ ไม่นะ
_="on htmx:error(errorInfo) fetch /errors {method:'POST', body:{errorInfo:errorInfo} as JSON} "โอ้ ไม่นะ!
ทำงานเป็นนักพัฒนามา 30 ปีแล้ว และยอมรับว่าในช่วงแรก ๆ ผมหยิ่งและคิดว่าตัวเองฉลาดกว่าคนอื่น ตอนนั้นผมอยู่ฝั่ง “สมองโต” ที่ชอบ ปีศาจแห่งความซับซ้อน ทั้งหมด
สิบปีต่อมาผมใกล้เคียงกับนักพัฒนาแบบ “Grug brain” มากขึ้น และตอนนี้แม้รู้ว่ามันอาจไม่สมบูรณ์แบบ แต่ก็โฟกัสกับวิธีแก้ที่เรียบง่ายที่สุดเท่าที่ใช้งานได้ เหตุผลที่มันโอเคคือมันทำให้เข้าใกล้สิ่งที่ถูกต้องมากขึ้น และทำให้ปรับปรุงซ้ำได้
สิ่งที่ดีที่สุดที่ทำได้ในฐานะนักพัฒนาคือ การลบโค้ด ตอนนี้ข้อกำหนดที่เราดูแลมาสองปีจู่ ๆ ก็ไม่ใช่ข้อกำหนดอีกต่อไปแล้ว และผมตื่นเต้นมากที่จะได้รื้อโค้ดจำนวนมากออก เพราะทุกอย่างจะเรียบง่ายขึ้น
เหมือนร่างกายรับรู้ได้ว่าพื้นที่กำลังถูกปลดปล่อย
Grug เคยถูกพูดถึงในคอมเมนต์ของบทความ Philosophy of Software Design เมื่อสัปดาห์ก่อนด้วย และผมว่าการถกเถียงตอนนั้นค่อนข้างดี https://news.ycombinator.com/item?id=38011938
“ผมก็รู้สึกกับ grug แบบเดียวกันเป๊ะ ๆ ผมไม่คิดว่าผู้คนจะเห็นพ้องกันจริง ๆ ว่าอะไรคือความเรียบง่าย ดังนั้นการทำเหมือนว่า ‘ความเรียบง่าย’ ของตัวเองเป็นสิ่งชัดแจ้งที่แม้แต่มนุษย์ถ้ำก็ต้องเห็นด้วย จึงให้ความรู้สึกเหมือนเป็นการวางท่า”
ความเรียบง่ายมักเป็นหนึ่งในสิ่งที่ซับซ้อนที่สุดในการค้นพบ ผมเห็นคนจำนวนมากที่ปักใจเชื่อว่าทุกอย่างรอบตัว ยกเว้นงานที่ตัวเองทำ ล้วนซับซ้อนและต้องถูกดัดให้เรียบง่าย มันให้ความรู้สึกเหมือนท่าทีที่อ่อนแออย่างอันตราย ซึ่งยึดติดกับอำนาจอ้างอิง
ปรัชญาแบบ grug brain คือเลือกความเรียบง่ายไม่ว่าจะต้องแลกด้วยอะไรก็ตาม ยกเว้นเมื่อหลีกเลี่ยงไม่ได้จริง ๆ
ปรัชญาสมองใหญ่ในสายตา grug คือเลือกการนำกลับมาใช้ซ้ำไม่ว่าจะต้องแลกด้วยอะไรก็ตาม ยกเว้นเมื่อหลีกเลี่ยงไม่ได้จริง ๆ
ปัญหาของปรัชญานี้คือมันทำให้ตัวเลือกการออกแบบรุ่นแรกแข็งตัว และทำให้การปรับปรุงแบบวนซ้ำยากขึ้น
จริงอยู่ที่ความเรียบง่ายหาได้ยาก แต่กุญแจในการค้นหาความเรียบง่ายคือ การปรับปรุงแบบวนซ้ำ
ค่าต่ำสุดเฉพาะจุดจะขยับไปตามความซับซ้อนของสิ่งที่กำลังสร้าง ดังนั้นมุมมองที่ค่อนข้างฉลาดหลายแบบจึงมีโอกาสถูกต้องพร้อมกันได้
ไม่ใช่ตัวชี้วัดที่สมบูรณ์แบบ แต่ทิศทางถูกต้อง และทำงานได้ดีมากบนสเกลลอการิทึม
ผมใช้และอธิบายแนวคิด รั้วของเชสเตอร์ตัน มานับครั้งไม่ถ้วน ดีใจที่ได้รู้ชื่อของมัน
เรื่องนี้เกิดขึ้นบ่อยมากเวลาทำงานกับนักพัฒนามือใหม่ พอพวกเขาเห็น “ขยะ legacy เก่า ๆ” ปฏิกิริยาแรกคืออยากรื้อทิ้ง หรือโยนทิ้งทั้งหมดแล้วสร้างใหม่ตั้งแต่ต้น
ในบางกรณี การปล่อยให้ลองทำเองก็อาจเป็นบทเรียนได้ แต่ควรจำไว้ว่าคนที่มาก่อนเราไม่ได้โง่สนิทกันทุกคน และโดยมากแล้วเขามีเหตุผลที่ทำแบบนั้น
บางครั้งมันก็เป็นโค้ดเก่าเลอะเทอะที่ควรถูกแทนที่จริง ๆ แต่แม้ในกรณีนั้น มันก็มักบรรจุบันทึกที่ได้มาอย่างยากลำบากเกี่ยวกับทุกซอกทุกมุมและ edge case ที่ต้องเข้าใจและจัดการเพื่อสร้างบางอย่างในโดเมนนั้น
ส่วนเรื่องการทดสอบดีมาก ตรงกับสิ่งที่ผมเรียนรู้มาตลอดหลายปีพอดี
integration test เป็นจุดที่เหมาะสำหรับการหาบั๊ก
mock object มีแนวโน้มทำให้เรื่องต่าง ๆ ซับซ้อนเกินไป และถึงแม้ผมยังใช้อยู่บ้างเป็นครั้งคราว แต่ก็หลีกเลี่ยงการใช้แบบเป็นระบบ
unit test เปราะบางเกินไปเมื่อเจอการรีแฟกเตอร์ ขณะที่ integration test ช่วยในการรีแฟกเตอร์
unit test เขียนและดูแลยากกว่า แต่เมื่อมันล้มเหลว จะเข้าใจและดีบักได้ง่ายกว่ามาก จึงยืนระยะได้ดีกว่ามากในระยะยาว
การทดสอบที่แย่ที่สุดคือ integration test ที่แอบพึ่งพาข้อเท็จจริงว่า integration test อื่นถูกรันมาก่อน มันถูกต้อง 99% ของเวลา แต่พอการเปลี่ยนแปลงบางอย่างทำให้ลำดับเปลี่ยน ก็พังทันที
ถ้าทีมมี โค้ดที่เรียบง่าย การทดสอบจะช่วยได้มาก แต่ถ้าทีมไม่มีโค้ดที่เรียบง่าย ซึ่งโดยปกติก็มักไม่มี การเอาเวลาไปทำให้โค้ดเรียบง่ายดีกว่าเอาเวลาไปเขียนเทสต์
ถ้าเอาฟังก์ชันปลายใบหนึ่งตัวเป็นหน่วย ก็มักจะอยู่ในระดับต่ำเกินไป แต่โมดูลที่ใหญ่กว่าซึ่งมีอินเทอร์เฟซค่อนข้างเสถียร อาจเป็น หน่วย ที่ดีและคุ้มค่าต่อการทดสอบได้
“ความซับซ้อนนั้นเลวร้ายมาก ๆ”
“อาวุธที่ดีที่สุดในการต่อกรกับปีศาจวิญญาณแห่งความซับซ้อน คือคำวิเศษว่า ‘ไม่’”
“น่าเศร้าแต่จริง: หลังจากเรียนรู้คำว่า ‘ใช่’ แล้วล้มเหลว การเรียนรู้ที่จะโทษ grug คนอื่นคือคำแนะนำด้านอาชีพในอุดมคติ”
นี่คือภูมิปัญญาอันซับซ้อนของ grug
ตอนนั้นก็มีการถกเถียงกันแล้ว
The Grug Brained Developer - https://news.ycombinator.com/item?id=31840331 - มิถุนายน 2022, 374 คอมเมนต์