10 quy tắc phát triển phần mềm của NASA
(cs.otago.ac.nz)- Phân tích mang tính phản biện về 10 quy tắc phát triển phần mềm của NASA
- Các quy tắc này được thiết kế cho những hệ thống nhúng cực kỳ quan trọng (ví dụ: phần mềm tàu vũ trụ)
- Tuy nhiên, cần thảo luận xem các quy tắc này có phù hợp trong những môi trường phát triển khác hay không, hoặc liệu chúng có áp dụng được cho các ngôn ngữ khác (không phải C) hay không
1. Duy trì luồng điều khiển đơn giản (goto, setjmp/longjmp, cấm đệ quy)
- Quy tắc này cấm xử lý ngoại lệ (
setjmp()/longjmp()) và đệ quy. - Đệ quy không nhất thiết là không hiệu quả. Nếu dùng phương pháp phù hợp, đệ quy vẫn có thể bảo đảm kết thúc.
- Việc ép buộc chuyển đệ quy thành vòng lặp có thể tạo ra mã khó bảo trì.
Phê bình:
- Bảo đảm kết thúc là quan trọng, nhưng các hạn chế cực đoan có thể làm giảm tính dễ đọc và khả năng bảo trì.
- Việc cấm đệ quy một cách tuyệt đối rất dễ dẫn đến sự phức tạp không cần thiết.
2. Mọi vòng lặp phải có giới hạn trên rõ ràng
- Trình biên dịch phải có khả năng phân tích tĩnh số lần lặp của vòng lặp.
- Tuy nhiên, chỉ đặt giới hạn trên thôi thì vẫn khó bảo đảm thời gian thực thi thực tế.
- Đặt giới hạn độ sâu đệ quy có thể an toàn tương đương với việc đặt giới hạn trên cho vòng lặp.
Phê bình:
- Chỉ đặt giới hạn trên không thể bảo đảm thời gian chạy khả thi trong thực tế.
- Ngay cả khi có giới hạn trên, nếu giá trị đó quá lớn thì trên thực tế cũng không khác mấy so với vòng lặp vô hạn.
3. Cấm cấp phát bộ nhớ động sau khi khởi tạo
- Trong hệ thống nhúng, bộ nhớ bị giới hạn nên mục tiêu là ngăn sự cố do cạn bộ nhớ.
- Tuy nhiên, cấp phát động có thể dự đoán được đôi khi còn an toàn hơn quản lý bộ nhớ thủ công.
- Ví dụ, dùng garbage collector thời gian thực (RTGC) có thể khiến cấp phát động trở nên có thể dự đoán.
Phê bình:
- Thay vì cấm hẳn cấp phát động, có thể tốt hơn nếu phân tích mẫu sử dụng bộ nhớ để bảo đảm an toàn.
- Có thể tận dụng các công cụ phân tích tĩnh hiện đại (như SPlint) để phát hiện sớm lỗi liên quan đến bộ nhớ động.
4. Giới hạn kích thước hàm trong một trang A4 (khoảng 60 dòng)
- Lập luận là hàm quá dài sẽ làm giảm khả năng đọc hiểu.
- Nhưng trong môi trường phát triển hiện đại, có tính năng code folding nên kích thước theo đơn vị logic quan trọng hơn độ dài của hàm.
Phê bình:
- Nên lấy độ phức tạp logic làm tiêu chí thay vì kích thước vật lý (số dòng).
- Việc chia nhỏ hàm không nên trở thành mục tiêu tự thân → ngược lại còn có thể làm việc bảo trì khó hơn.
5. Mỗi hàm phải có ít nhất hai câu lệnh assert
assertrất hữu ích cho việc gỡ lỗi và tài liệu hóa.- Tuy nhiên, áp đặt số lượng cứng nhắc có thể kém hiệu quả.
Phê bình:
- Điều quan trọng không phải là số lượng
assert, mà là xác định rõ những vị trí cần kiểm tra tính hợp lệ của dữ liệu. - Việc kiểm tra mọi tham số và mọi đầu vào bên ngoài thực tế hơn.
6. Giảm phạm vi của đối tượng dữ liệu xuống mức tối thiểu
- Đây là nguyên tắc tốt, khuyến khích dùng biến cục bộ.
- Tuy nhiên, không chỉ phạm vi của hàm mà cả phạm vi của kiểu và của hàm cũng nên được thu hẹp tối đa.
Phê bình:
- Trong Ada, Pascal, JavaScript và các ngôn ngữ hàm, kiểu và hàm cũng có thể được khai báo cục bộ → cách tiếp cận này tốt hơn cả quy tắc của NASA.
7. Bắt buộc kiểm tra tính hợp lệ của giá trị trả về và tham số hàm
- Giá trị trả về phải luôn được kiểm tra.
- Tuy nhiên, việc kiểm tra mọi trường hợp là rất khó trong thực tế.
Phê bình:
- Để ngăn lỗi khi chạy, cần càng nhiều kiểm tra càng tốt, nhưng cũng phải cân nhắc giới hạn thực tiễn.
- Đặc biệt trong C, việc kiểm tra giá trị trả về là quan trọng, nhưng trong các ngôn ngữ hiện đại (Java, Rust, v.v.) có thể xử lý an toàn hơn nhờ hệ thống kiểu.
8. Hạn chế sử dụng preprocessor (chỉ cho phép include header và macro đơn giản)
- Cấm macro phức tạp, token pasting, macro biến số đối số (...).
- Tuy nhiên, macro biến số đối số có thể hữu ích cho công cụ gỡ lỗi.
Phê bình:
- Thay vì chỉ hạn chế dùng preprocessor, nên khuyến khích phong cách macro dễ đọc.
- Nếu chặn biên dịch có điều kiện như
#ifdef, việc viết mã độc lập với nền tảng có thể trở nên khó khăn.
9. Hạn chế sử dụng con trỏ (cấm con trỏ kép, cấm con trỏ hàm)
- Cấm dùng con trỏ hàm → mục tiêu là độ ổn định cao.
- Tuy nhiên, con trỏ hàm là thiết yếu cho callback, strategy pattern, trình điều khiển thiết bị, v.v.
Phê bình:
- Nếu không có con trỏ hàm mà buộc phải chọn hàm bằng
switch-case, mã sẽ kém dễ đọc và khó bảo trì hơn. - Trong phát triển hệ điều hành, network stack và driver, con trỏ hàm là bắt buộc.
- Thay vì hạn chế con trỏ, các cách bảo đảm sử dụng con trỏ an toàn hơn (như smart pointer của C++, Rust, v.v.) là lời giải tốt hơn.
10. Với mọi mã nguồn, đặt cảnh báo trình biên dịch ở mức tối đa và dùng công cụ phân tích tĩnh
- Đây là một khuyến nghị rất tốt.
- Loại bỏ cảnh báo trình biên dịch + dùng công cụ phân tích tĩnh = tăng độ ổn định.
Phê bình:
- Một số quy tắc khác của NASA (ví dụ: cấm con trỏ, giới hạn kích thước hàm) thực chất nhằm khắc phục giới hạn của công cụ phân tích tĩnh.
- Tuy nhiên, công cụ phân tích tĩnh hiện đại đã tiến bộ rất nhiều, nên thay vì áp đặt hạn chế quá mức, việc tận dụng các kỹ thuật phân tích tinh vi hơn sẽ hữu ích hơn.
6 bình luận
Nhìn hoàn toàn từ góc độ thời gian thực và hệ thống nhúng thì đây đều là những quy tắc dễ hiểu và cần thiết. Liệu trình phân tích tĩnh có thể thay thế các quy tắc này không?
Ví dụ, nếu cho phép cấp phát động, liệu có thể đảm bảo rằng việc cấp phát bộ nhớ sẽ thành công trong mọi kịch bản sử dụng không?
Khi học về kiểm thử phần mềm, luôn có những mệnh đề được nhắc đến ngay từ buổi đầu tiên. Một trong số đó là "không thể có kiểm thử hoàn hảo".
Có vẻ phần phản đối lại thu hút mắt tôi hơn
nên chắc đây là những quy tắc không hợp với tôi rồi haha
Có vẻ không chỉ NASA mà trong các ngành gắn trực tiếp với tính mạng như hàng không/ô tô cũng thường áp dụng những quy tắc lập trình tương tự vậy haha
https://github.com/kubernetes/kubernetes/…
Nhìn vào mã nguồn Kubernetes, tôi chợt nhớ đến đoạn mã theo kiểu "space shuttle style" được cho là viết theo cách xây dựng mã nguồn ứng dụng của tàu con thoi NASA.
Thread HN liên quan: https://news.ycombinator.com/item?id=18772873
Ý kiến trên Hacker News
setjmp/longjmpsetjmp/longjmplà xử lý ngoại lệsetjmp()vàlongjmp()là cách tệ để xử lý ngoại lệ> Tiêu đề phải cho thấy đây là một lời phê bình đối với các quy tắc đó
222