GN⁺: NASA의 소프트웨어 개발 10가지 규칙
(cs.otago.ac.nz)- NASA의 10가지 소프트웨어 개발 규칙에 대한 비판적 분석
- 이 규칙들은 극도로 중요한 임베디드 시스템(예: 우주선 소프트웨어)을 위한 것
- 하지만 이러한 규칙이 다른 개발 환경에서도 적절한지, 또는 다른 언어(C가 아닌 언어)에서도 적용 가능한지에 대해 논의가 필요함
1. 단순한 제어 흐름 유지 (goto, setjmp/longjmp, 재귀 금지)
- 이 규칙은 예외 처리(setjmp()/longjmp())와 재귀를 금지함.
- 재귀가 반드시 비효율적인 것은 아님. 적절한 방법을 사용하면 재귀도 종료를 보장할 수 있음.
- 강제로 재귀를 루프로 변환하면 유지보수가 어려운 코드가 될 위험이 있음.
비판:
- 종료 보장이 중요하지만, 극단적인 제한은 가독성과 유지보수를 저해할 수 있음.
- 무조건적인 재귀 금지는 불필요한 복잡성을 초래할 가능성이 큼.
2. 모든 루프는 명확한 상한을 가져야 함
- 컴파일러가 루프 반복 횟수를 정적으로 분석할 수 있어야 함.
- 그러나 단순히 상한을 설정하는 것만으로는 실제 실행 시간 보장이 어려움.
- 재귀 깊이 제한을 두는 것이 루프 상한을 두는 것만큼 안전할 수 있음.
비판:
- 단순히 상한을 두는 것만으로는 현실적인 실행 가능 시간을 보장하지 못함.
- 상한을 설정해도 너무 큰 값이라면 사실상 무한 루프와 다를 바 없음.
3. 초기화 이후 동적 메모리 할당 금지
- 임베디드 시스템에서는 메모리가 한정적이므로 메모리 부족으로 인한 충돌을 방지하려는 목적.
- 하지만 수동 메모리 관리보다 예측 가능한 동적 할당이 더 안전할 수도 있음.
- 예를 들어, 실시간 가비지 컬렉터(RTGC) 를 사용하면 동적 할당도 예측 가능하게 만들 수 있음.
비판:
- 동적 할당 자체를 금지하는 것보다, 메모리 사용 패턴을 분석하여 안전성을 확보하는 것이 더 나은 접근 방식일 수 있음.
- 현대적인 정적 분석 도구(SPlint 등)를 활용하면 동적 메모리 관련 오류를 사전에 감지할 수 있음.
4. 함수 크기는 A4 용지 한 장 이내로 제한 (약 60줄)
- 함수가 너무 길면 가독성이 떨어진다는 논리.
- 하지만 현대 개발 환경에서는 코드 폴딩 기능이 있어, 함수 길이보다 논리적 단위의 크기가 더 중요함.
비판:
- 물리적인 크기(줄 수)보다 논리적 복잡성을 기준으로 삼아야 함.
- 함수를 작게 쪼개는 것 자체가 목표가 되어서는 안 됨 → 오히려 유지보수를 어렵게 만들 수도 있음.
5. 함수당 최소 두 개의 assert 문 사용
- assert는 디버깅과 문서화에 매우 유용함.
- 하지만 무조건적인 개수 제한은 비효율적일 수 있음.
비판:
- assert의 개수보다 데이터 유효성 검사가 필요한 위치를 명확히 하는 것이 중요함.
- 모든 인자와 외부 입력을 검증하는 것이 더 실용적임.
6. 데이터 객체의 스코프 최소화
- 지역 변수 사용을 권장하는 좋은 원칙.
- 하지만 함수뿐만 아니라 타입과 함수의 스코프도 최소화해야 함.
비판:
- Ada, Pascal, JavaScript, 함수형 언어에서는 타입과 함수도 지역적으로 선언 가능 → NASA 규칙보다 더 나은 접근 방식.
7. 함수 반환 값 및 매개변수 유효성 검증 필수
- 반환 값을 반드시 체크해야 함.
- 하지만 모든 경우를 체크하는 것은 현실적으로 어려움.
비판:
- 실행 오류를 방지하려면 가능한 많은 검사가 필요하지만, 실용적인 한계를 고려해야 함.
- 특히 C에서는 반환 값 체크가 중요하지만, 현대 언어(Java, Rust 등)에서는 타입 시스템을 활용해 더 안전하게 처리 가능.
8. 전처리기 사용 제한 (헤더 포함 및 단순 매크로만 허용)
- 복잡한 매크로, 토큰 결합, 가변 인자 매크로(...) 금지.
- 하지만 가변 인자 매크로는 디버깅 도구로 유용할 수 있음.
비판:
- 전처리기 사용을 제한하는 것보다 가독성 좋은 매크로 스타일을 권장하는 것이 바람직함.
- #ifdef 같은 조건부 컴파일을 막으면, 플랫폼 독립적인 코드 작성이 어려워질 수 있음.
9. 포인터 사용 제한 (이중 포인터 금지, 함수 포인터 금지)
- 함수 포인터 사용 금지 → 높은 안정성을 목표로 함.
- 하지만 함수 포인터는 콜백, 전략 패턴, 디바이스 드라이버 등에 필수적임.
비판:
- 함수 포인터 없이 switch-case로 함수 선택을 강제하면 코드 가독성이 떨어지고 유지보수가 어려워짐.
- 운영체제, 네트워크 스택, 드라이버 개발에서는 함수 포인터가 필수적임.
- 포인터 제한보다 안전한 포인터 사용을 보장하는 방법(C++ 스마트 포인터, Rust 등)이 더 나은 해결책.
10. 모든 코드에 대해 컴파일러 경고를 최대로 설정하고, 정적 분석 도구 사용
- 이 규칙은 매우 좋은 권장 사항.
- 컴파일러 경고 제거 + 정적 분석 도구 사용 = 안정성 향상.
비판:
- NASA의 다른 규칙(예: 포인터 금지, 함수 크기 제한)은 단순히 정적 분석 도구의 한계를 극복하려는 목적이 있음.
- 하지만 현대 정적 분석 도구는 매우 발전했으므로, 지나친 제한보다 더 정교한 분석 기법을 활용하는 것이 유용함.
전부 다 실시간, 임베디드 관점에서 보면 이해되고 필요한 규칙들이네요. 정적분석기가 이 규칙들을 대신해 줄 수 있을까요?
예를들어, 동적할당을 허용했을 때 모든 사용 시나리오에서 메모리 할당에 성공한다는것을 보장할 수 있을까요?
소프트웨어 테스트를 공부하면 항상 첫날 첫시간에 언급되는 명제들이 있죠. 그 중 하나가 "완벽한 테스트는 불가능하다"구요.
https://github.com/kubernetes/kubernetes/…
Kubernetes 소스코드 중 NASA Space Shuttle 애플리케이션 소스코드 작성 방법으로 작성했다 하는 'space shuttle style' 코드 블록이 생각났네요.
관련 HN Thread: https://news.ycombinator.com/item?id=18772873
Hacker News 의견
- 원문을 읽어보면 각 항목의 목적을 설명하고 있음
- 원문은 주로 C 언어를 대상으로 하며, C로 작성된 중요한 응용 프로그램의 신뢰성을 더 철저히 검사할 수 있도록 최적화하려고 함
- 원저자는 자신이 무엇을 하는지 명확히 이해하고 있으며, C 코드를 검증하는 여러 방법을 설명함
- 원문에 있는 논리는 모두 완벽하게 이해됨
- 아마도 작은 시스템에서 C를 배웠기 때문일 것임
- 임플란트 의료 기기를 위한 하드웨어용 C를 배웠으며, 실험실에서도 유사한 지침을 따랐음
- 마지막 단락은 훌륭함
- 규칙이 처음에는 엄격하게 느껴질 수 있지만, 코드의 정확성에 생명이 달려 있을 수 있는 경우를 고려해야 함
- 자동차의 안전벨트처럼 처음에는 불편할 수 있지만, 시간이 지나면 자연스럽게 사용하게 됨
- 이 규칙에 대한 나의 비판은 OP와는 매우 다를 것임
- setjmp/longjmp를 옹호하는 글을 처음부터 진지하게 받아들이기 어려웠음
- 이 패턴은 접근해본 사람이라면 누구나 명백히 문제가 있음
- 글은 setjmp/longjmp가 예외 처리라고 주장함
- 예외 처리는 좋다고 주장함
- 두 번째 전제에 심각한 문제가 있음
- 루프에 대해 최대 반복 횟수를 설정하라는 의미임
- 10^90은 어리석고 관련이 없음
- 이 지점 이후로 글을 읽지 않았음
- 규칙을 비판한다면 다음과 같은 점에 초점을 맞출 것임
- 함수 본문의 길이는 이해의 단순성과 상관관계가 없으며, 오히려 규칙이 암시하는 것과 반대일 수 있음
- 2개의 단언은 완전히 임의적이며, 단언할 수 있는 모든 것을 단언해야 함
- Ada, Pascal (Delphi), JavaScript, 또는 함수형 언어를 사용하는 사람들은 가능한 한 지역적으로 타입과 함수를 선언해야 함
- JavaScript에서의 개인적인 접근 방식은 명시적으로 값을 캡처하려는 경우를 제외하고 중첩된 방식으로 함수를 정의하지 않는 것임
- 오래된 정신 모델 때문일 수 있음
- 성능 프로파일링에서 함수가 호출될 때마다 재정의된다고 보여졌음
- 현대 JavaScript 인터프리터가 이렇게 작동한다고는 생각하지 않음
- 화살표 함수 도입 이후로 깊은 최적화가 이루어졌을 것임
- 오래된 습관은 쉽게 사라지지 않음
- 지역 변수를 캡처하지 않는 명명된 함수는 파일/모듈 범위에 유지함
- 다른 많은 노트는 흥미롭고 매우 세세한 부분임
- "기술적으로 정확한 것이 가장 좋은 정확성"이라는 방식으로 오래된 엔지니어들이 좋아함
- NASA 규칙이 전달하려는 신중함의 일반적인 톤이 매우 좋다고 생각하며 대부분을 지지함
- 문맥상, 이것들은 "규칙"이라기보다는 제안된 관행임
- 공식적인 "규칙"은 "NPR" 같은 이름의 문서에 있음
- 개발자는 이 "규칙"을 준수하거나 무시할 의무가 없음
- GCC는 컴파일 후 스택 사용량과 호출자-피호출자 관계를 얻을 수 있음
- setjmp()와 longjmp()는 예외를 처리하는 나쁜 방법임
- 청소 코드가 실행되지 않음
- 규칙의 정신을 따르면 청소가 필요한 자원이 없어야 함
- 주요 문제는 각 응용 프로그램에서 다르게 나타남
- 반복 제한이 초과되거나 시작 시 할당된 고정 자원이 충분하지 않을 때 어떻게 할 것인가
- 요즘 프로그래머들은 코드를 화면에서 읽기 때문에 종이 크기가 더 이상 관련이 없는 이유가 명확하지 않음
- 표준 페이지와 문자 크기에 대한 반복이 있었음
- 종이의 한계뿐만 아니라 인간의 한계 때문임
- 재귀에 대한 규칙은 필요한 스택 공간의 정적으로 알려진 경계를 보장하기 위한 것임
- 컴파일러에 의존한다는 비판은 맞지만, 런타임의 상한을 도출하기 위한 전제 조건임
- 안전이 중요한 시스템에서는 보장된 응답 시간이 필요함
- 제목은 규칙에 대한 _비판_임을 나타내야 함
- 엄격한 타입 사용을 권장함
- 모든 스칼라 타입에 대해 엄격한 타입 사용
- 제국 단위와 미터법 단위를 혼합하지 않음