DO-178B 추적성 설계로 배우는 안전필수 소프트웨어 증명법

여객기 한 대에 들어가는 비행 제어 소프트웨어는 요구사항 문서만 수천 페이지에 이른다. 그중 한 줄이 검증 없이 넘어가면 그 구멍이 어디서 왔고 어디로 이어지는지 누가 장담할 수 있을까? 항공 소프트웨어 업계는 이 불확실성을 그냥 두지 않는다. 요구사항마다 어느 설계에서 나와 어느 코드로 구현되고 어느 테스트로 확인됐는지, 그 실을 끊김 없이 잇는다.
이 규율을 문서로 담아 놓은 게 RTCA DO-178B다. 항공 인증 자체가 아니라 이 표준이 세운 사고방식 — 무엇을 왜 만들었는지, 그게 제대로 확인됐는지 실을 놓치지 않고 잇는 습관 — 을 빌려 온다.
실패의 무게가 엄격함을 정한다
DO-178B는 모든 소프트웨어를 같은 강도로 검증하라고 요구하지 않는다. 대신 소프트웨어 하나가 잘못됐을 때 벌어질 결과의 무게를 먼저 재고 그 무게에 맞춰 검증 활동의 양을 정한다. 이 결과 등급의 이름이 소프트웨어 레벨(Software Level)이다. A부터 E까지 다섯 단계로 나뉘고 A가 가장 무겁다. 오작동이 승무원과 승객의 생명에 직결되는 소프트웨어는 A, 오작동이 나도 운항에 별다른 영향이 없는 소프트웨어는 E — 그 사이에 B·C·D가 자리 잡는다.
레벨을 나누는 이유는 단순하다. 모든 코드를 A 수준으로 검증하면 비용이 감당 안 되고 전부 E 수준으로 넘기면 정작 위험한 부분까지 헐겁게 지나간다. 결과가 무거울수록 요구되는 검증 활동 — 독립적인 리뷰, 코드 커버리지 범위, 문서화 밀도 — 이 촘촘해진다.
| 레벨 | 실패 결과 | 대략적인 예 | 요구 강도 |
|---|---|---|---|
| A | 재앙적 — 다수 인명 손실 가능 | 1차 비행 제어 | 최고 (독립 검증 + 전체 커버리지) |
| B | 위험 — 심각한 부상 가능 | 보조 항법 계통 | 매우 높음 |
| C | 중대 — 승무원 부담 급증 | 항법 보조 표시 | 중간 |
| D | 경미 — 불편 수준 | 부가 정보 표시 | 낮음 |
| E | 무영향 — 운항에 지장 없음 | 객실 편의 시스템 | 최소 |
이 배분 감각이 낯설지 않은 이유가 있다. 웹 서비스를 짤 때도 결제 로직과 배너 문구를 같은 강도로 리뷰하지는 않는다. 다른 점은 그 구분을 "느낌"이 아니라 "결과가 잘못됐을 때 무슨 일이 벌어지는가"라는 질문 하나로 미리 등급화해 둔다는 것이다. 등급을 나중에 즉흥적으로 매기지 않고 시작 전에 정해 두니 나중에 누가 "이 정도면 됐지 않냐"고 우겨도 기준이 흔들리지 않는다.
레벨을 가르는 기준은 하나 더 있다. 검증하는 사람과 만든 사람이 같아도 되는지 여부다. 레벨이 높을수록 코드를 짠 사람과 그 코드를 검토하는 사람이 겹치지 않아야 한다는 독립성 요구가 붙는다. 낮은 레벨에서는 같은 사람이 만들고 확인해도 크게 문제 삼지 않지만 결과가 무거워질수록 이 겹침 자체를 위험 요소로 본다. 만든 사람 눈에는 자기가 짠 논리가 당연해 보이기 마련이라서다.
요구사항에서 검증까지, 실이 끊기면 안 된다
DO-178B가 개발 전체에 요구하는 원칙을 한 단어로 추리면 추적성(Traceability)이다. 요구사항 하나에서 시작한 실이 상위 설계, 하위 설계, 코드, 테스트, 검증 결과까지 끊기지 않고 이어져야 한다는 뜻이다. 실이 어디선가 끊기면 그 구간은 "왜 이렇게 만들었는지" 아무도 설명 못 하는 블랙박스가 된다.
이 실은 두 방향으로 당겨 봐야 진짜다. 정방향(forward)은 요구사항에서 출발해 코드와 테스트까지 내려가며 "이 요구사항이 실제로 구현되고 확인됐는가"를 확인하는 절차다. 역방향(backward)은 그 반대 방향이다. 코드나 테스트에서 시작해 위로 거슬러 올라가며 "이 코드 줄은 어느 요구사항에서 나왔는가"를 거꾸로 짚는다. 정방향만 확인하면 요구사항이 빠짐없이 구현됐는지는 알아도 몰래 섞인 코드는 못 본다는 게 맹점이고 역방향만 보면 근거는 확인해도 아직 구현 안 된 요구사항을 놓친다는 게 또 다른 맹점이다. 두 맹점을 다 잡으려면 정방향과 역방향을 같이 당겨야 빠진 것도, 몰래 섞인 것도 걸린다.
이 구조를 실무로 옮기면 어렵지 않다. 요구사항마다 고유 식별자(ID)를 붙이고 설계 문서와 코드 주석과 테스트 케이스에 그 ID를 그대로 박아 둔다. "이 함수는 REQ-042를 구현한다", "이 테스트는 REQ-042를 확인한다"는 식이다. 이 ID 하나가 사슬 전체를 꿰는 못 역할을 하고 못이 빠지면 사슬은 흩어진 고리 뭉치로 남는다.
코드 커버리지도 이 시선으로 다시 읽으면 다르게 보인다. 테스트가 코드의 몇 퍼센트를 지나갔는지만 보면 절반짜리 확인이다. 놓치는 지점은 다르다. 빈틈이 생겼을 때 그게 요구사항이 빠져서인지, 테스트를 안 짜서인지, 죽은 코드라서인지, 아니면 요구사항에도 없는데 몰래 들어간 코드라서인지 구분해야 한다. 추적성이 없으면 이 넷을 구분할 방법이 없고 남는 건 "커버리지 80%"라는 숫자 하나뿐이다.
테스트를 요구사항에 묶어 두면 테스트의 목적 자체가 달라진다. 구현 세부사항이 아니라 요구사항이 정말 만족됐는지를 확인하는 시험이 되고 이 방식을 요구사항 기반 테스트(requirements-based testing)라 부른다. 코드를 리팩터링해도 요구사항이 그대로면 테스트는 안 깨져야 정상이다. 반대로 요구사항 문장 하나 안 건드렸는데 테스트가 깨진다면 그 테스트는 요구사항이 아니라 구현 방식 자체를 시험하고 있었다는 뜻이다.
증거는 저절로 안 남는다
추적성은 한 번 그려 놓으면 끝나는 그림일까? 아니다. 코드가 바뀌고 요구사항이 수정되는 매 순간 실이 같이 끊어질 위험에 놓인다. DO-178B가 형상관리(Configuration Management)를 별도 활동으로 못 박아 둔 이유가 여기 있다. 형상관리는 쉽게 말해 "어느 버전이 어느 시점에 무엇으로 이루어져 있었는지"를 흔들리지 않게 붙잡아 두는 장치다. 기준선(baseline)을 정하고 그 기준선에서 무엇이 왜 바뀌었는지 변경 이력을 남기고 그 결과물을 나중에 그대로 다시 꺼낼 수 있게 보관한다.
기준선을 정해 두면 별거 아닌 듯해도 효과가 크다. "이 버전에서는 무엇이 확정됐다"는 지점을 콕 찍어 두면 그 지점 이후로 무엇이 바뀌었는지 물었을 때 답이 명확해진다. 기준선 없이 변경 이력만 쌓아 두면 오히려 특정 시점의 전체 상태를 복원하기가 더 어렵다. 변경 하나하나는 기록돼 있어도 "그래서 그 시점에 전체가 어떤 모습이었나"는 흩어진 조각을 매번 다시 맞춰야 나온다. 버그를 역추적할 때도 마찬가지라서 그 시점의 기준선이 없으면 원인을 찾는 데 걸리는 시간이 몇 배로 늘어난다.
품질보증(Quality Assurance, QA)은 다른 층위에서 움직이는 활동이다. QA는 결과물이 맞았는지가 아니라 정해 둔 절차를 실제로 지켰는지를 확인한다. 검증과 QA는 여기서 갈린다. 코드가 옳은지는 검증(verification)의 일이고 "검증을 하기로 한 방식대로 정말 검증했는지"는 QA의 일이다. 이 구분이 은근히 크다. 검증만 있고 QA가 없으면 검증 절차 자체가 슬그머니 생략되거나 형식만 남아도 아무도 못 잡기 때문이다.
형식과 절차만으로는 충분하지 않다. 근거가 비어 있을 때 어떻게 처리할지도 규칙으로 정해 둬야 한다. 재시도 파이프라인을 설계할 때 비슷한 문제를 직접 겪어 봐서 이게 왜 중요한지 안다. 멱등성 계약이 확인되지 않은 재시도를 기본으로 진행시키면 이중 처리가 조용히 쌓인다는 걸 겪고 나서 규칙을 뒤집었다. 판단할 근거가 없으면 통과가 아니라 실패로 처리한다는 규칙이다. DO-178B의 QA도 같은 결을 가진다. "확인했는지 안 했는지 기록이 없다"는 상태를 "확인된 것"으로 봐주지 않는다. 기록이 없으면 그 항목은 아직 끝나지 않은 일이다.
내부 기록이 심사대에 오르는 지점
지금까지 쌓은 계획서·요구사항·설계 기술서·소스 코드·테스트·결과·리포트·QA 기록을 한데 모은 것을 생애주기 데이터(Life-cycle Data)라 부른다. 이 데이터 전체가 인증연계(Certification Liaison) 단계에서 외부 심사 기관과 만난다. 내부에서만 돌던 기록이 남에게 설명 가능한 형태로 넘어가는 지점이라는 뜻이다.
이 이관(handoff)이 아무 조건 없이 매끄럽게 진행되지는 않는다. 조건이 하나 있다. 안에서 쌓은 증거가 밖에서도 다시 확인 가능해야 한다는 조건이다. "우리끼리는 이렇게 확인했다"는 말만으로는 부족하다. 어느 요구사항이 어느 설계 결정을 거쳐 어느 코드로 어느 테스트로 확인됐는지, 심사하는 쪽이 그 실을 그대로 따라갈 수 있어야 한다. 추적성은 결국 이 지점을 위한 준비였다. 평소에 실을 이어 놓지 않고 심사 직전에 급조하면 그 실은 진짜 개발 과정을 반영하지 못하고 겉치레로만 남는다.
이건 일반 코드 리뷰에도 그대로 적용되는 이야기다. 왜 이 함수가 이런 모양으로 짜였는지 리뷰어에게 설명 못 하는 코드는 6개월 뒤 다른 사람이 물어봐도 여전히 설명 못 한다. 그 시점엔 만든 사람도 이유를 까먹었을 가능성이 크기 때문이다. 심사대와 나중의 나 자신은 생각보다 같은 질문을 던진다. "이거 왜 이렇게 만들었죠?"
레거시 코드를 재사용하거나 도구가 만든 결과물을 그대로 믿는 경우도 이 흐름과 맞닿아 있다. 이전에 개발된 소프트웨어를 가져다 쓰려면 그 소프트웨어의 실이 지금 프로젝트의 요구사항까지 이어지는지 다시 확인해야 하고 자동화 도구가 만든 산출물도 그 도구 자체가 신뢰할 만하다는 근거가 따로 있어야 한다. 편하다고 실을 건너뛴 대가는 나중에 심사대에서 그 구간만 뻥 뚫려 있는 모습으로 돌아온다.
도구 자격(tool qualification)이라는 개념도 같은 논리를 따른다. 코드를 자동으로 생성하거나 테스트를 자동으로 돌리는 도구를 쓴다면 그 도구가 낸 결과를 사람이 직접 만든 것과 똑같이 믿어도 되는지 별도로 따져야 한다. 도구가 틀리면 그 도구를 거친 결과 전부가 한꺼번에 의심받는다. 도구를 얼마나 신뢰하느냐에 따라 사람이 다시 확인해야 하는 범위가 달라진다.
내 프로젝트에 오늘 옮겨 담을 것
항공 인증 문서를 그대로 옮기자는 이야기가 아니다. 대부분의 팀에게는 그럴 여유도 이유도 예산도 없다. 다만 이 표준이 붙잡고 있는 습관 몇 가지는 프로젝트 크기와 무관하게 값이 있다.
- 요구사항이나 이슈에 고유 ID를 붙이고 커밋 메시지와 코드 주석과 테스트 이름에 그 ID를 그대로 남긴다.
- 테스트를 짤 때 "이 테스트가 무엇을 확인하는지"를 요구사항 문장으로 되짚어 본다. 거꾸로 짚이지 않으면 그 테스트는 구현 세부사항만 확인하고 있을 확률이 높다.
- 커버리지 빈 구간을 만나면 원인을 넷 중 하나로 분류한다. 요구사항 누락, 테스트 누락, 죽은 코드, 아니면 승인 안 된 코드.
- 변경할 때는 기준선을 남기고 무엇이 왜 바뀌었는지를 로그로 고정한다. 나중에 되돌리거나 설명해야 할 때 이 로그가 유일한 증거다.
- 검증 결과가 없거나 애매하면 통과가 아니라 미완료로 표시한다. 관대한 기본값은 나중에 비싸게 돌아온다.
- 자동 생성 코드나 AI 제안처럼 도구가 만든 결과는, 그 도구를 얼마나 믿을 수 있는지 확인한 뒤에만 그대로 반영한다.
이 중 제일 먼저 손댈 만한 건 첫 줄이다. 요구사항과 코드와 테스트에 같은 ID 하나만 박아 둬도 나중에 "이 코드가 왜 이렇게 짜여 있지"라는 질문에 답할 실마리가 생긴다. 남은 숙제는 이 실을 자동으로 확인하는 일이다. ID가 코드에는 있는데 테스트에는 빠진 경우를 사람이 매번 눈으로 찾는 대신, 커밋이 올라올 때마다 스크립트가 짝을 맞춰 보는 장치를 다음에 붙여 볼 생각이다.
아직 댓글이 없습니다.