- เนื้อหาที่นักพัฒนามากประสบการณ์แบ่งปันปรัชญาการพัฒนาซอฟต์แวร์กับนักพัฒนารุ่นใหม่
- มีคำแนะนำในหลายหัวข้อ เช่น การหลีกเลี่ยงการเขียนใหม่ทั้งหมด (ground-up rewrite), การบริหารตารางเวลา, คุณภาพโค้ด, ระบบอัตโนมัติ, และการรับมือกับ edge case ต่าง ๆ
จงหลีกเลี่ยงสถานการณ์ที่การเขียนใหม่ทั้งหมด (ground-up rewrite) ดูน่าดึงดูด ไม่ว่าจะต้องแลกด้วยอะไรก็ตาม
- ช่วงเวลาที่เกิดแรงดึงดูดให้เขียนใหม่ทั้งหมด มักเป็นตอนที่โค้ดเดิมดูแลรักษาได้ยากแล้วจากหนี้ทางเทคนิคที่สะสมมา
- ควรจับสัญญาณเตือนล่วงหน้าเมื่อความซับซ้อนของโค้ดเริ่มสะสม เช่น แม้แต่การแก้ไขง่าย ๆ ก็ทำได้ยาก, การเขียนคอมเมนต์/เอกสารทำได้ลำบาก, หรือจำนวนคนที่เข้าใจโค้ดส่วนสำคัญลดลง และต้องมองหาวิธีแก้อย่างจริงจัง
- หลังจากช่วงขยายฟีเจอร์จบลง ต้องมีช่วงบูรณาการเพื่อลดความซับซ้อนและจัดระเบียบคุณภาพให้ดีขึ้นเสมอ
- หากการเขียนใหม่ทั้งหมดกลายเป็นสิ่งที่เลี่ยงไม่ได้ นั่นหมายความว่าโครงการได้เข้าสู่ระยะอันตรายแล้ว
- เพื่อลดความเสี่ยง ควรจัดการหนี้ทางเทคนิคอย่างต่อเนื่องและติดตามคุณภาพโค้ดอย่างใกล้ชิด
ทำให้งานทั้งหมดเสร็จ 90% ภายในครึ่งหนึ่งของเวลาที่มี
- การเขียนโค้ดรอบแรกให้พอทำงานได้ คิดเป็นเพียงครึ่งหนึ่งของงานทั้งหมด
- หลังจากนั้นยังต้องใช้เวลามากกว่าที่คิดเพื่อทำส่วนที่เหลือให้เสร็จอย่างเหมาะสม เช่น การจัดการ edge case, การทดสอบ, การ deploy, เอกสารประกอบ, ประสิทธิภาพ, และความสามารถในการบำรุงรักษา
- ควรเผื่อ buffer ไว้มากพอเพื่อรับมือกับปัญหาที่ไม่คาดคิดและงานเก็บรายละเอียดช่วงท้าย
- สุดท้ายแล้วต้องตระหนักถึงช่องว่างระหว่าง “ทำให้โค้ดพอรันได้แบบหยาบ ๆ ในครั้งแรก” กับ “ทำให้มันเป็นฟีเจอร์ที่เสร็จสมบูรณ์” และสะท้อนสิ่งนี้ลงในแผนงาน
ทำให้แนวปฏิบัติที่ดีเป็นระบบอัตโนมัติ
- หากเพียงย้ำกับนักพัฒนาซ้ำ ๆ ด้วยคำพูดหรือเอกสารว่า “ต้องทำแบบนี้” ก็มีโอกาสผิดพลาดได้ง่าย
- หากทำได้ การบังคับใช้ด้วย automated test หรือสคริปต์ในลักษณะ “หากผิดกฎแล้ว build ล้มเหลว” จะมีประสิทธิภาพมากกว่า
- แม้แต่กฎที่เพิ่งนำมาใช้ใหม่ เช่น API ที่ห้ามใช้ หรือคอมเมนต์ที่จำเป็นต้องใส่ ก็สามารถค่อย ๆ ทำให้เป็นอัตโนมัติเพื่อลดข้อผิดพลาดและการตกหล่นได้
- อย่างไรก็ตาม ไม่ใช่ทุกอย่างที่จะทำให้เป็นอัตโนมัติได้ และกฎที่เข้มงวดเกินไปอาจรบกวน workflow การพัฒนา จึงต้องมีความสมดุล
จงคำนึงถึงข้อมูลแบบสุดโต่ง (pathological) ด้วย
- การตัดสินโค้ดจากเพียง input ปกติ (golden path) นั้นอันตราย
- ต้องสมมติสถานการณ์ปัญหาไว้ด้วย เช่น request ล่าช้าหรือถูกยกเลิก, ข้อมูลขนาดมหาศาล (หลายล้านถึงหลายร้อยล้านแถว), หรือสตริงประหลาด (ยาวเกินไป, มี slash หรือช่องว่าง)
- การเตรียมพร้อมรับมือ edge case อย่างรอบคอบเป็นตัวตัดสินคุณภาพโค้ดขั้นสุดท้าย
โดยทั่วไปมักมีวิธีที่เขียนได้ง่ายกว่านี้
- หลังจากทำให้โค้ดพอทำงานได้ในตอนแรกแล้ว ควรเว้นเวลาไว้กลับมาปรับปรุงให้เรียบง่ายและชัดเจนยิ่งขึ้น
- แม้จะพบ “วิธีแก้ที่ดูดี” แล้ว ก็ยังควรมีท่าทีที่เปิดโอกาสให้กลับมาทบทวนว่ามีทางออกที่ดีกว่านี้หรือไม่
- กระบวนการ refactor โค้ดที่ยาวและซับซ้อนให้กระชับขึ้นจะช่วยยกระดับคุณภาพสุดท้าย
เขียนโค้ดให้ทดสอบได้
- หากลดจำนวน interface และ side effect ให้น้อยที่สุด การเขียน automated test จะง่ายขึ้นมาก
- หากโครงสร้างใดทดสอบได้ยาก ก็มีโอกาสสูงว่าการ encapsulation ยังทำได้ไม่ดี
- หากออกแบบโครงสร้างโค้ดให้อยู่ในรูปแบบที่ทดสอบได้ดีอย่างเป็นรูปธรรม ก็จะช่วยค้นพบบั๊กได้ตั้งแต่เนิ่น ๆ
แค่โค้ด “พิสูจน์ได้” ว่าไม่มีปัญหา ก็ยังไม่ถือว่าจบ
- ต่อให้โค้ดดูปลอดภัยในเชิงโครงสร้าง แต่หากสภาพแวดล้อมรอบข้างเปลี่ยนไปหรือวิธีเรียกใช้บางส่วนเปลี่ยน ก็อาจเกิดปัญหาได้
- หากเป็นโค้ดด้านความปลอดภัย ต้องออกแบบโดยคำนึงถึงความเป็นไปได้ที่จุดเรียกใช้งานจะเปลี่ยนในอนาคต แม้ตอนนี้จะปลอดภัยอยู่แล้วก็ตาม
- โค้ดควรถูกเขียนให้ “ปลอดภัยอย่างชัดเจน และไม่เกิดปัญหาแม้จะมีการเปลี่ยนแปลง”
ความคิดเห็น
- ที่มาของประโยค “เหตุผลที่เขียนจดหมายฉบับนี้ให้สั้นไม่ได้ เพราะไม่มีเวลาพอจะเขียนให้สั้น” คือ Pascal
- จงระวัง off-by-one error อยู่เสมอ
- เพิ่มแนวทางใหม่ลงในวิกิ
- หากระบบเอกสาร/การแบ่งปันความรู้ในบริษัทไม่มีการจัดการที่ดี ข้อมูลจะกระจัดกระจายและทำให้เกิดความสับสน
- อาจเกิดปัญหาที่เอกสารทางการล้าสมัยจากกระบวนการอนุมัติ หรือมีหลายวิกิอยู่พร้อมกันจนไม่รู้ว่าอันไหนคือข้อมูลทางการ
- การกำหนดวิกิเพียงแห่งเดียวให้เป็นศูนย์รวมข้อมูลทั้งหมด และหากมีส่วนที่ขาดก็ให้นักพัฒนาเขียนเพิ่มเองหรือเติมเต็มผ่าน reverse-engineering เป็นแนวทางที่ใช้ได้ผล
- หากเอกสารถูกเชื่อมโยงกับแหล่งอื่นได้ดี เช่น source control หรือคอมเมนต์ในโค้ด ก็จะรักษาข้อมูลให้ทันสมัยได้ง่ายขึ้น
- หาก automated test และสภาพแวดล้อม build มีความซับซ้อนหรือไม่ได้เปิดให้นักพัฒนาเข้าถึง ก็อาจเกิดสถานการณ์ที่ไม่มีใครเคยรันการทดสอบทั้งหมดจริง ๆ เลย
- ควรรักษา Jenkins build script ให้เรียบง่ายในรูปแบบ “cd project; ./build-it” และควรเก็บสคริปต์นี้ไว้ใน source control ด้วย
- หากทั้งทีมสามารถ build และทดสอบได้ในสภาพแวดล้อมเดียวกัน (เช่น image ของ virtual machine หรือการตั้งค่า build) ก็จะช่วยลดปัญหาได้ล่วงหน้า
- ควรคำนึงถึง edge case ตั้งแต่ขั้นพัฒนา เช่น ทำให้
destroy_object(foo) ทำงานได้อย่างปลอดภัยแม้ foo จะเป็น NULL หรือให้ create_object() เรียก destroy_object() ภายในเมื่อเกิดความล้มเหลว ก็เป็นแนวทางที่มีประโยชน์
- ท้ายที่สุด สิ่งสำคัญคือทำให้นักพัฒนาทุกคนเข้าถึงและมีส่วนร่วมกับเอกสารและสภาพแวดล้อม build/test ได้อย่างง่ายดาย
- เอกสารประกอบและ automated test: มีการกล่าวถึงข้อเสนอเชิงปฏิบัติว่า เอกสารหรือวิกิสำหรับการแบ่งปันความรู้มีความสำคัญ และการตั้งค่าสภาพแวดล้อม CI/CD อย่าง Jenkins ก็ควรเป็นสิ่งที่แบ่งปันร่วมกันได้
- ไม่มีเครื่องมือเปลี่ยนพฤติกรรมใดได้ผลดีไปกว่าการ “ทำให้ไม่สะดวก” จนกว่าผู้คนจะจดจำพฤติกรรมที่ต้องการได้
- เช่นเดียวกับที่มีมุมมองคัดค้านสุภาษิตหมากรุกที่ว่า “ถ้าเห็นตาที่ดูดี ก็จงเล่นมันทันที” การเขียนโปรแกรมก็เช่นกัน มีความเป็นสองด้านในตัวเอง
6 ความคิดเห็น
> โดยปกติแล้วมักมีวิธีที่เขียนให้ง่ายกว่านี้ได้
ผมเชื่อว่าการคิดหาวิธีแก้ปัญหาที่กระชับกว่าจะช่วยลดความซับซ้อนของโค้ดและนโยบายได้
> "ทำงานทั้งหมดให้เสร็จได้ 90% โดยใช้เวลาเพียงครึ่งหนึ่งของเวลาที่เป็นไปได้"
ผมคิดว่าข้อความนี้จริงมากครับ แทนที่จะพยายามทำให้สมบูรณ์แบบในครั้งเดียว การได้ทบทวนรอบที่ 2 อย่างรวดเร็วช่วยลดความผิดพลาด และยังทำให้บริหารเวลาได้ดีขึ้นด้วย
> ระวัง off-by-one error อยู่เสมอ
ตอนแรกก็สงสัยว่า off-by-one error คืออะไร ที่แท้ก็คือกลุ่มข้อผิดพลาดที่เกี่ยวกับเงื่อนไขขอบเขตแบบ
for (int i = 1; i < n; ++i) { ... },for (int i = 0; i <= n; ++i) { ... }นี่เอง> จงหลีกเลี่ยงสถานการณ์ที่การเขียนใหม่ทั้งหมดตั้งแต่ต้น (ground-up rewrite) ดูน่าดึงดูด ไม่ว่าจะต้องแลกด้วยอะไรก็ตาม
ผมคิดว่านี่น่าจะเป็น pitfall ที่ทำให้ธุรกิจของบริษัท IT จำนวนมากไปต่อไม่ได้ และเมื่อมี technical debt สะสมอยู่มาก องค์กรวิศวกรรมที่ไม่สามารถจัดการมันได้ (หรือไม่อยากจัดการมัน) ก็มักจะมองว่าการเขียนใหม่ทั้งหมดเป็นทางเลือกที่น่าดึงดูด
ผมเห็นด้วยอย่างมากกับความคิดของผู้เขียนที่ว่า หากไม่มีเหตุผลที่น่าเชื่อถืออย่างแท้จริง ก็ควรหลีกเลี่ยงการ rewrite ให้มากที่สุด
ความคิดเห็นจาก Hacker News
การพัฒนาซอฟต์แวร์เป็นกระบวนการที่วนซ้ำของการลองทำและการเรียนรู้ นักพัฒนาที่มีประสบการณ์จะเพิ่มความเข้าใจผ่านการเขียนโค้ดและการทดสอบ และได้เรียนรู้จำนวนมาก แต่สิ่งเหล่านี้มักไม่ปรากฏชัดในการประชุมหรือการวางแผน นักพัฒนารุ่นจูเนียร์มักมีปัญหาในการส่งมอบโค้ดที่พร้อมใช้งานจริง และไม่อยากทำงานที่ท้ายที่สุดต้องถูกทิ้ง ผู้จัดการที่ขาดประสบการณ์ด้านการพัฒนาอาจทำให้ปัญหาเหล่านี้รุนแรงขึ้น
คำพูดที่ว่า "ขออภัยที่เขียนจดหมายยาว เพราะฉันไม่มีเวลาพอจะเขียนให้สั้น" เป็นคำกล่าวของ Blaise Pascal นักคณิตศาสตร์และนักปรัชญาชาวฝรั่งเศส ซึ่งสื่อว่าการเขียนให้สั้นนั้นยากกว่า
กฎ 90/50 เน้นย้ำว่าควรให้ความสำคัญกับการทดสอบและการจัดการข้อยกเว้นเพื่อยกระดับคุณภาพของโค้ด การตั้งค่าการทดสอบอัตโนมัติช่วยกำหนดความคาดหวังที่ชัดเจนให้กับ codebase
การศึกษาวิทยาการคอมพิวเตอร์มักชี้นำให้เขียนโค้ดที่ซับซ้อน แต่สิ่งสำคัญคือการเขียนโค้ดที่อ่านง่าย จำเป็นต้องมีการตั้งชื่อตัวแปรและฟังก์ชันที่เหมาะสม การจัดรูปแบบที่สม่ำเสมอ และการออกแบบแบบแยกเป็นโมดูล
กลไกที่เรียกว่า Ratchet มอบแนวทางที่สมบูรณ์แบบสำหรับอนาคต
ความพยายามที่จะทำให้ประสบการณ์และการรับรู้โดเมนเป็นเรื่องทั่วไปอาจนำไปสู่การเหมารวมที่ผิดพลาด การพัฒนาเป็นศิลปะแห่งการจัดการความซับซ้อน และการเรียนรู้จากความล้มเหลวเป็นสิ่งสำคัญ
คำกล่าวที่ว่า "90% แรกของงานใช้เวลา 90% และ 10% ที่เหลือก็ใช้เวลาอีก 90%" สะท้อนความเป็นจริงของการพัฒนาได้ดี เมื่อต้องคำนึงถึงการจัดการข้อยกเว้น ข้อผิดพลาด การใช้งาน และความปลอดภัย มักมีงานที่ไม่คาดคิดอีกมาก
บทความของ Joel Spolsky เตือนถึงความเสี่ยงของการเขียนใหม่ทั้งหมด และเน้นย้ำภูมิปัญญาของ DevOps
ควรปรับปรุงความอ่านง่ายของโค้ดให้เหมาะสมที่สุด และตระหนักว่ายิ่งใช้เวลานานกว่าจะพบบั๊ก ต้นทุนก็ยิ่งเพิ่มขึ้น ควรให้ความสำคัญกับหลักการของ functional programming และการใช้ระบบชนิดข้อมูลที่เข้มแข็งมีประโยชน์