เขียนโค้ดที่ลบได้ง่าย
- โค้ดทุกบรรทัดมีต้นทุนในการบำรุงรักษา การนำโค้ดกลับมาใช้ซ้ำทำให้การเปลี่ยนแปลงทำได้ยากขึ้น
- ยิ่งมีผู้ใช้ API มากเท่าไร ก็ยิ่งมีโค้ดที่ต้องเขียนใหม่เมื่อต้องเปลี่ยนแปลงมากขึ้นเท่านั้น
- การจัดการการพึ่งพากันของโค้ดเป็นปัญหาสำคัญในระบบขนาดใหญ่
ขั้นที่ 0: ไม่เขียนโค้ด
- จำนวนบรรทัดของโค้ดไม่ได้ให้ข้อมูลที่มีความหมายมากนักในตัวมันเอง
- โค้ดที่ไม่ได้เขียนคือโค้ดที่ลบได้ง่ายที่สุด
ขั้นที่ 1: คัดลอก-วางโค้ด
- โค้ดที่นำกลับมาใช้ซ้ำได้สามารถเขียนให้ดีขึ้นได้ง่ายกว่าในภายหลังผ่านตัวอย่าง
- การคัดลอก-วางโค้ดเป็นวิธีหนึ่งในการหลีกเลี่ยงการพึ่งพาและเพิ่มความยืดหยุ่น
ขั้นที่ 2: อย่าคัดลอก-วางโค้ด
- หากโค้ดถูกคัดลอก-วางมากพอแล้ว ก็ถึงเวลาสกัดออกมาเป็นฟังก์ชัน
- การสร้างไดเรกทอรี
util เพื่อเก็บยูทิลิตีต่าง ๆ ไว้ในไฟล์อื่นเป็นแนวทางที่ดี
ขั้นที่ 3: เขียน boilerplate ให้มากขึ้น
- boilerplate คล้ายกับการคัดลอก-วางโค้ด แต่เป็นการเปลี่ยนโค้ดในตำแหน่งที่ต่างกัน
- boilerplate ช่วยลดการพึ่งพาและให้ความยืดหยุ่น
ขั้นที่ 4: อย่าเขียน boilerplate
- หากมี boilerplate มากเกินไป ควรห่อมันด้วยไลบรารีที่มีมุมมองต่อ policy, workflow และ state
- ความสัมพันธ์ระหว่าง
requests กับ urllib3 เป็นตัวอย่างที่ดี
ขั้นที่ 5: เขียนโค้ดเป็นก้อนใหญ่
- business logic มักเต็มไปด้วยกรณียกเว้นไม่รู้จบและการแฮ็กแบบเร็ว ๆ พอใช้
- การลบความผิดพลาดครั้งใหญ่เพียงครั้งเดียวทำได้ง่ายกว่าการลบความผิดพลาดเล็ก ๆ หลายครั้ง
ขั้นที่ 6: แยกโค้ดออกเป็นชิ้น ๆ
- โค้ดก้อนใหญ่มีต้นทุนในการบำรุงรักษาสูง
- ควรแยกความรับผิดชอบของโค้ดและออกแบบโมดูลโดยคำนึงถึงความเป็นไปได้ในการเปลี่ยนแปลง
ขั้นที่ 7: เขียนโค้ดต่อไปเรื่อย ๆ
- ควรสามารถเขียนโค้ดใหม่โดยแยกจากโค้ดเดิม เพื่อให้ทดลองไอเดียใหม่ ๆ ได้
- ฟีเจอร์แฟล็กคือวิธีที่ช่วยให้เปลี่ยนใจภายหลังได้
สรุปโดย GN⁺
- บทความนี้อธิบายวิธีเขียนโค้ดให้ลบออกได้ง่าย
- แก่นสำคัญคือการลดการพึ่งพากันของโค้ด เพิ่มความยืดหยุ่น และลดต้นทุนการบำรุงรักษา
- โปรเจ็กต์ที่มีลักษณะใกล้เคียงกัน ได้แก่
requests และ urllib3
- บทความนี้ช่วยย้ำเตือนนักพัฒนาซอฟต์แวร์ถึงความสำคัญของการจัดการโค้ดและการบำรุงรักษา
1 ความคิดเห็น
ความคิดเห็นบน Hacker News
ชอบวลีที่ว่า "Simple is robust" ยิ่งระบบมีความซับซ้อนน้อย ก็ยิ่งเปลี่ยนแปลงได้ง่าย การวางแผนเพื่ออนาคตควรยึดโค้ดที่เข้าใจได้ตรงไปตรงมามากกว่าโค้ดที่ออกแบบมาเพื่อขยายได้ เช่น ทำ abstraction เมื่อสถานการณ์เรียกร้องเท่านั้น สนับสนุนการทำซ้ำแบบง่าย ๆ ใช้ monolith ในช่วงแรก และให้ความสำคัญกับการขยายแนวตั้งก่อนการขยายแนวนอน เมื่อสร้างระบบแบบ 0-1 หลายระบบก็พบรูปแบบร่วมเหล่านี้
น่าแปลกใจที่ไม่มีการพูดถึงการทดสอบหรือ observability เลย การทดสอบมีต้นทุนในการดูแลรักษา แต่ช่วยลดความเสี่ยงที่จะเกิดปัญหาเมื่อเอาโค้ดออก เมื่อต้องเปิดเผย service ให้กับ caller ภายนอก จำเป็นต้องมีวิธีที่ทรงพลังในการทำเครื่องหมายบางการเรียกว่าเตรียมเลิกใช้ และสังเกตว่ายังมีการเรียกอยู่หรือไม่ เมื่อไม่นานมานี้ได้ลบ GraphQL resolver แบบกึ่งอัตโนมัติ และใช้ metric ของความถี่การใช้งานเพื่อระบุ resolver ที่ลบไม่ได้ GraphQL มี deprecation annotation อยู่แล้ว แต่ใน service ไม่ได้จัดการอะไรเป็นพิเศษ สามารถเพิ่ม observability เพื่อตั้ง flag ว่ามีการเรียกใช้ฟังก์ชันที่เตรียมเลิกใช้หรือไม่ แล้วรันใน production เป็นระยะเวลานานพอ ก่อนจะลบโค้ดที่เปิดให้ภายนอกใช้อย่างปลอดภัย
กลายเป็นคนที่สนับสนุน "การออกแบบเพื่อการลบ" ในอดีตเคยคิดว่าสามารถวางแผนทุกสถานการณ์และสร้างผลงานที่ตอบโจทย์ทุกความต้องการได้ แต่การคาดเดาความต้องการในอนาคตนั้นยาก วันหนึ่งสิ่งที่เราสร้างจะกลายเป็นสิ่งไร้ประโยชน์สำหรับใครบางคน และการรื้อทิ้งของพวกเขาก็ย่อมสมเหตุสมผล ดังนั้นจึงควรทุ่มเทให้กับการทำให้มันเอาออกได้ง่าย ซึ่งมักนำไปสู่การลด coupling แต่ไม่ใช่แนวเดียวกับนักพัฒนารุ่นใหม่ที่พยายามแยกทุกอย่างออกเป็น framework แบบ meta-configurable บางครั้ง coupling ที่แน่นแฟ้นกลับดีกว่า หากมันทำให้เข้าใจตรรกะได้ง่ายกว่า
หากต้องการเขียนโค้ดให้ลบออกได้ง่าย ควรทำซ้ำเพื่อหลีกเลี่ยง dependency ไม่ใช่ทำซ้ำเพื่อให้ง่ายต่อการจัดการ ควรแบ่งชั้นของโค้ด และสร้าง API ที่เรียบง่ายบนส่วนที่แม้จะเขียนง่าย แต่ใช้งานไม่สะดวก ควรแยกโค้ดออก และทำให้ส่วนที่เขียนยากกับส่วนที่มีแนวโน้มจะเปลี่ยนบ่อย แยกจากโค้ดส่วนที่เหลือและแยกจากกันเอง ไม่ควร hardcode ทุกทางเลือก แต่ควรเปิดให้เปลี่ยนบางอย่างได้ใน runtime จากประสบการณ์ส่วนตัว โค้ดที่ลบได้ง่ายมักเป็นโค้ดที่มีการแบ่งชั้นและทำเป็นโมดูล จึงขยายได้ง่ายด้วย
มักบอกนักศึกษาด้านฟิสิกส์เชิงคำนวณว่า การคำนวณที่ดีที่สุดคือการคำนวณที่ไม่ต้องใส่ใจมันเลย
โดยส่วนตัวจะแบ่งโค้ดเป็น business logic กับ implementation จริง business logic โดยธรรมชาติแล้วอาจซ้ำกันได้ แต่รายละเอียดทางเทคนิคไม่ควรซ้ำมากเกินไป ตราบใดที่ยังคง business logic ให้เป็นอิสระจากแอปพลิเคชันโดยไม่ไปจัดการมันโดยตรง business logic จะเละเทะแค่ไหนก็ได้ หากเกิดปัญหาและสิ่งต่าง ๆ ทำงานไม่ดี ก็ยังมีทางเลือกที่จะลบ implementation ทั้งหมดทิ้ง และไม่ถูกบังคับให้แก้โดยพยายามหา spec ที่แท้จริงจากตัว implementation
ข้อผิดพลาดที่เห็นได้ชัดในย่อหน้าแรก: ปัญหาของการนำโค้ดกลับมาใช้ซ้ำคือมันขัดขวางการเปลี่ยนใจในภายหลัง ซึ่งโดยทั่วไปเป็นข้ออ้างที่ผิด ถ้าคุณเปลี่ยนใจแล้วโค้ดถูกคัดลอกวางไว้สิบแห่ง คุณต้องแก้ทั้งสิบแห่ง ในทางกลับกัน ถ้าโค้ดอยู่ในฟังก์ชัน คุณแก้แค่ครั้งเดียว หากมีเพียงหนึ่งจากสิบการเรียกที่ไม่ควรถูกเปลี่ยน คุณก็ยังคัดลอกวางได้อยู่ หรือจะทำให้ฟังก์ชันมีความทั่วไปมากขึ้นก็ได้ เช่นเดียวกับการไม่มองซ้ายขวาก่อนข้ามถนน การคัดลอกวางแทบจะเป็นความคิดที่แย่เสมอ
มีความสัมพันธ์ที่ชัดเจนมากว่า โค้ดแย่ ๆ มักอยู่มานาน เพราะมันลบออกได้ยาก
สงสัยว่านี่หมายถึงการใช้ซอฟต์แวร์ในสภาพพื้นฐานให้มากที่สุด และอย่าปรับแต่งลึกเกินไปใช่ไหม