กฎ 10 ข้อของ NASA สำหรับการพัฒนาซอฟต์แวร์
(cs.otago.ac.nz)- บทวิเคราะห์เชิงวิพากษ์เกี่ยวกับกฎ 10 ข้อของ NASA สำหรับการพัฒนาซอฟต์แวร์
- กฎเหล่านี้มีไว้สำหรับระบบฝังตัวที่มีความสำคัญอย่างยิ่งยวด (เช่น ซอฟต์แวร์ยานอวกาศ)
- แต่ก็จำเป็นต้องถกเถียงกันว่ากฎเหล่านี้เหมาะสมกับสภาพแวดล้อมการพัฒนาแบบอื่นด้วยหรือไม่ หรือสามารถนำไปใช้กับภาษาอื่นที่ไม่ใช่ C ได้หรือไม่
1. รักษาโฟลว์การควบคุมให้เรียบง่าย (ห้ามใช้ goto, setjmp/longjmp, การเรียกซ้ำ)
- กฎข้อนี้ห้ามใช้การจัดการข้อยกเว้น (setjmp()/longjmp()) และการเรียกซ้ำ
- การเรียกซ้ำไม่ได้ไม่มีประสิทธิภาพเสมอไป หากใช้วิธีที่เหมาะสมก็สามารถรับประกันการสิ้นสุดได้
- การบังคับแปลงการเรียกซ้ำให้เป็นลูปอาจเสี่ยงทำให้โค้ดดูแลรักษายากขึ้น
คำวิจารณ์:
- การรับประกันการสิ้นสุดนั้นสำคัญ แต่การจำกัดอย่างสุดโต่งอาจทำลายความอ่านง่ายและการบำรุงรักษา
- การห้ามการเรียกซ้ำแบบเหมารวมมีแนวโน้มจะก่อให้เกิดความซับซ้อนที่ไม่จำเป็น
2. ทุกลูปต้องมีขอบเขตบนที่ชัดเจน
- คอมไพเลอร์ควรต้องสามารถวิเคราะห์จำนวนรอบของลูปแบบสถิตได้
- อย่างไรก็ตาม การกำหนดขอบเขตบนเพียงอย่างเดียวทำให้รับประกันเวลาในการทำงานจริงได้ยาก
- การจำกัดความลึกของการเรียกซ้ำอาจปลอดภัยพอ ๆ กับการกำหนดขอบเขตบนของลูป
คำวิจารณ์:
- การกำหนดขอบเขตบนเพียงอย่างเดียวไม่อาจรับประกันเวลาทำงานที่เป็นจริงได้
- แม้จะตั้งขอบเขตบนไว้ หากค่านั้นใหญ่เกินไป ในทางปฏิบัติก็แทบไม่ต่างจากลูปไม่สิ้นสุด
3. ห้ามจัดสรรหน่วยความจำแบบไดนามิกหลังการเริ่มต้นระบบ
- ในระบบฝังตัว หน่วยความจำมีจำกัด จึงมีเป้าหมายเพื่อป้องกันการล่มจากหน่วยความจำไม่พอ
- แต่บางครั้งการจัดสรรแบบไดนามิกที่คาดการณ์ได้อาจปลอดภัยกว่าการจัดการหน่วยความจำด้วยมือ
- ตัวอย่างเช่น หากใช้ real-time garbage collector (RTGC) ก็อาจทำให้การจัดสรรแบบไดนามิกคาดการณ์ได้เช่นกัน
คำวิจารณ์:
- แทนที่จะห้ามการจัดสรรแบบไดนามิกโดยสิ้นเชิง การวิเคราะห์รูปแบบการใช้หน่วยความจำเพื่อรับรองความปลอดภัยอาจเป็นแนวทางที่ดีกว่า
- หากใช้เครื่องมือวิเคราะห์แบบสถิตสมัยใหม่ (เช่น SPlint) ก็สามารถตรวจจับข้อผิดพลาดเกี่ยวกับหน่วยความจำแบบไดนามิกได้ล่วงหน้า
4. จำกัดขนาดฟังก์ชันให้อยู่ภายในกระดาษ A4 หนึ่งหน้า (ประมาณ 60 บรรทัด)
- ตรรกะคือหากฟังก์ชันยาวเกินไป ความอ่านง่ายจะลดลง
- แต่ในสภาพแวดล้อมการพัฒนาสมัยใหม่มีฟังก์ชัน code folding ดังนั้นขนาดของหน่วยตรรกะจึงสำคัญกว่าความยาวของฟังก์ชัน
คำวิจารณ์:
- ควรใช้ความซับซ้อนเชิงตรรกะเป็นเกณฑ์ มากกว่าขนาดทางกายภาพ (จำนวนบรรทัด)
- การแยกฟังก์ชันให้เล็กลงไม่ควรกลายเป็นเป้าหมายในตัวเอง → เพราะอาจทำให้ดูแลรักษายากขึ้นเสียอีก
5. แต่ละฟังก์ชันต้องมี assert อย่างน้อยสองจุด
assertมีประโยชน์มากต่อการดีบักและการทำเอกสาร- แต่การบังคับกำหนดจำนวนตายตัวอาจไม่มีประสิทธิภาพ
คำวิจารณ์:
- สิ่งสำคัญไม่ใช่จำนวนของ
assertแต่คือการระบุตำแหน่งที่จำเป็นต้องตรวจสอบความถูกต้องของข้อมูลให้ชัดเจน - การตรวจสอบอาร์กิวเมนต์ทั้งหมดและอินพุตจากภายนอกอาจใช้งานได้จริงมากกว่า
6. ลดขอบเขตการมองเห็นของออบเจ็กต์ข้อมูลให้เล็กที่สุด
- เป็นหลักการที่ดีซึ่งสนับสนุนการใช้ตัวแปรภายใน
- แต่ไม่ใช่แค่ฟังก์ชันเท่านั้น ขอบเขตของชนิดข้อมูลและฟังก์ชันก็ควรถูกลดให้เล็กที่สุดด้วย
คำวิจารณ์:
- ใน Ada, Pascal, JavaScript และภาษาฟังก์ชัน ชนิดข้อมูลและฟังก์ชันก็สามารถประกาศแบบเฉพาะที่ได้ → เป็นแนวทางที่ดีกว่ากฎของ NASA
7. ต้องตรวจสอบค่าที่คืนจากฟังก์ชันและความถูกต้องของพารามิเตอร์
- ต้องตรวจสอบค่าที่คืนกลับเสมอ
- แต่การตรวจสอบทุกกรณีทั้งหมดนั้นทำได้ยากในทางปฏิบัติ
คำวิจารณ์:
- เพื่อป้องกันข้อผิดพลาดขณะรัน จำเป็นต้องมีการตรวจสอบให้มากที่สุดเท่าที่ทำได้ แต่ก็ต้องคำนึงถึงข้อจำกัดด้านการใช้งานจริง
- โดยเฉพาะใน C การตรวจสอบค่าที่คืนกลับมีความสำคัญ แต่ในภาษาสมัยใหม่ (เช่น Java, Rust) สามารถใช้ระบบชนิดข้อมูลเพื่อจัดการให้ปลอดภัยยิ่งขึ้นได้
8. จำกัดการใช้ preprocessor (อนุญาตเฉพาะการ include header และแมโครง่าย ๆ)
- ห้ามใช้แมโครซับซ้อน, token pasting, variadic macro(...)
- อย่างไรก็ตาม variadic macro อาจมีประโยชน์เป็นเครื่องมือดีบัก
คำวิจารณ์:
- แทนที่จะจำกัดการใช้ preprocessor การส่งเสริมสไตล์การเขียนแมโครที่อ่านง่ายอาจเหมาะสมกว่า
- หากห้าม conditional compilation อย่าง
#ifdefก็อาจทำให้เขียนโค้ดข้ามแพลตฟอร์มได้ยากขึ้น
9. จำกัดการใช้พอยน์เตอร์ (ห้ามใช้พอยน์เตอร์สองชั้น, ห้ามใช้ function pointer)
- ห้ามใช้ function pointer → มีเป้าหมายเพื่อความเสถียรสูง
- แต่ function pointer เป็นสิ่งจำเป็นสำหรับ callback, strategy pattern, device driver เป็นต้น
คำวิจารณ์:
- หากบังคับให้เลือกฟังก์ชันผ่าน switch-case แทน function pointer จะทำให้อ่านโค้ดยากขึ้นและบำรุงรักษายากขึ้น
- ในการพัฒนาระบบปฏิบัติการ, network stack และไดรเวอร์นั้น function pointer เป็นสิ่งจำเป็น
- แทนที่จะจำกัดพอยน์เตอร์ วิธีที่ดีกว่าคือทำให้มั่นใจว่ามีการใช้พอยน์เตอร์อย่างปลอดภัย (เช่น smart pointer ของ C++, Rust เป็นต้น)
10. ตั้งค่าคำเตือนของคอมไพเลอร์ให้สูงสุดสำหรับโค้ดทั้งหมด และใช้เครื่องมือวิเคราะห์แบบสถิต
- กฎข้อนี้เป็นคำแนะนำที่ดีมาก
- การกำจัดคำเตือนจากคอมไพเลอร์ + การใช้เครื่องมือวิเคราะห์แบบสถิต = เพิ่มความเสถียร
คำวิจารณ์:
- กฎอื่น ๆ ของ NASA (เช่น ห้ามใช้พอยน์เตอร์, จำกัดขนาดฟังก์ชัน) มีจุดประสงค์เพียงเพื่อชดเชยข้อจำกัดของเครื่องมือวิเคราะห์แบบสถิต
- แต่เนื่องจากเครื่องมือวิเคราะห์แบบสถิตสมัยใหม่พัฒนาไปมากแล้ว การใช้เทคนิคการวิเคราะห์ที่ละเอียดขึ้นย่อมมีประโยชน์มากกว่าการจำกัดที่เข้มงวดเกินไป
6 ความคิดเห็น
ถ้ามองทั้งหมดจากมุมมองของระบบเรียลไทม์และระบบฝังตัว ก็เป็นกฎที่เข้าใจได้และจำเป็นทั้งนั้นครับ เครื่องมือวิเคราะห์แบบสแตติกจะช่วยทำหน้าที่แทนกฎเหล่านี้ได้ไหม?
ยกตัวอย่างเช่น ถ้าอนุญาตให้มีการจัดสรรหน่วยความจำแบบไดนามิก จะสามารถรับประกันได้หรือไม่ว่าในการใช้งานทุกสถานการณ์ การจัดสรรหน่วยความจำจะสำเร็จเสมอ?
ถ้าเรียนเรื่องการทดสอบซอฟต์แวร์ มักจะมีหลักที่ถูกพูดถึงตั้งแต่ชั่วโมงแรกของวันแรกเสมอ หนึ่งในนั้นคือ "การทดสอบที่สมบูรณ์แบบเป็นไปไม่ได้" ครับ
ดูเหมือนว่าผมจะสะดุดตากับข้อโต้แย้งมากกว่า
คงเป็นกฎที่ไม่ค่อยเข้ากับผมเท่าไหร่นะครับ 55
ดูเหมือนว่าไม่ใช่แค่ NASA เท่านั้น แต่ในอุตสาหกรรมอย่างการบิน/ยานยนต์ที่เกี่ยวข้องกับชีวิตโดยตรง ก็มักจะใช้กฎการเขียนโค้ดที่คล้ายกันอยู่บ่อย ๆ เหมือนกันนะ 555
https://github.com/kubernetes/kubernetes/…
ในซอร์สโค้ดของ Kubernetes ทำให้นึกถึงบล็อกโค้ดแบบ 'space shuttle style' ที่ว่ากันว่าเขียนตามแนวทางการเขียนซอร์สโค้ดของแอปพลิเคชัน NASA Space Shuttle ขึ้นมาครับ
เธรด HN ที่เกี่ยวข้อง: https://news.ycombinator.com/item?id=18772873
ความคิดเห็นจาก Hacker News
> ชื่อเรื่องต้องสื่อว่าเป็น คำวิจารณ์ ต่อกฎดังกล่าว
222