1980년대 소프트웨어 업계 평균은 천 줄당 30~60개의 결함이었다. IBM의 Cleanroom 팀은 2만 줄짜리 컴파일러 인크리먼트를 테스트에서 53개의 결함이 발견된 상태로 출시했다. 이는 KLOC당 2.6개이다. 1만 줄짜리 개별 인크리먼트 중 일부는 시스템 테스트에서 결함이 전혀 발견되지 않은 채 진행되기도 했다.

가장 이상한 점? 프로그래머들은 자신의 코드를 실행하는 것이 금지되어 있었다.

Cleanroom software engineering이 실제로 의미하는 것

1980년대 IBM의 수학자 Harlan Mills가 개발한 Cleanroom software engineering은 Unit Testing과 디버깅 대신 formal specification, structured design, mathematical correctness verification에 의존하는 이론 기반 프로세스이다. 이름은 반도체 제조에서 유래했다. 칩 공장에서는 먼지를 들여놓고 나중에 닦아내지 않는다. 처음부터 오염을 방지한다.

Cleanroom에서는 개발자가 Unit Testing을 하지 않는다. 디버깅도 하지 않는다. 검증한다.

이 프로세스는 소프트웨어를 보통 5000~15000 줄의 코드로 된 인크리먼트로 분할한다. 각 인크리먼트는 사양화되고, 설계되고, 검증된 다음 완전한 단위로 통계적으로 테스트된다. 개발자는 코드를 작성하지만, 개발 중에 컴파일하거나 실행할 수 없다. 코드가 처음 실행되는 것은 공식적인 시스템 테스트 때이다.

box structure verification이 디버깅을 대체하는 방식

핵심 메커니즘은 box structure specification이다. 각 컴포넌트는 세 단계로 정의된다.

black box는 외부 동작을 사양화한다. 내부 상태를 언급하지 않고 자극과 반응을 정의한다.

state box는 내부 상태 변수와 상태 전이 함수를 추가한다.

clear box는 실제 구현이며, state box의 구조화된 개선이어야 한다.

이 계층 구조가 중요한 이유는 각 수준에서 독립적으로 정확성을 검증할 수 있기 때문이다. state box가 black box를 구현함을 증명하고, clear box가 state box를 구현함을 증명한다.

자명하지 않은 correctness argument를 가진 함수에 대해 실제로 어떻게 보이는지 다음과 같다:

from typing import Optional

def binary_search(arr: list[int], target: int) -> Optional[int]:
    """
    Black box spec:
      Pre:  arr is sorted in non-decreasing order.
      Post: Returns index i such that arr[i] == target,
            or None if target is not present.
    """
    low, high = 0, len(arr) - 1

    while low <= high:
        mid = (low + high) // 2

        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            low = mid + 1
        else:
            high = mid - 1

    return None

루프에 대한 verification argument가 중요하다. 팀은 이것을 함께 검토하고 세 가지 사실을 확인한다. 첫째, arr[mid] == target이면 postcondition이 즉시 만족된다. 둘째, arr[mid] < target이면 target은 mid보다 큰 인덱스에만 존재할 수 있으므로, low = mid + 1로 설정하면 target이 존재한다면 arr[low:high+1]에 있다는 invariant가 보존된다. 셋째, arr[mid] > target이면 high = mid - 1에 대해 대칭적인 주장이 성립한다.

이것은 누군가 빈 배열을 생각했는지 묻는 코드 리뷰가 아니다. 모든 가능한 입력이 지정된 출력을 생산한다는 구조화된 그룹 증명이다.

zero-defect 주장 뒤에 있는 IBM의 숫자

IBM은 1980년대 후반과 1990년대 초반에 세 개의 주요 프로젝트에 Cleanroom을 적용했다: COBOL Structuring Facility(4만 줄), 공군 헬리콥터 비행 프로그램(3만5000 줄), NASA 우주 수송 계획 시스템(4만5000 줄)이다.

COBOL/SF 데이터가 가장 상세하다. 처음 2만 줄짜리 인크리먼트는 formal specifications, box structure design, 그룹 correctness verification을 사용하여 개발되었다. 개발자들은 개발 중에 자신의 모듈을 컴파일하거나 실행할 수 없었다. 코드는 곧바로 시스템 테스트로 들어갔다.

결과: 테스트 중 53개의 결함이 발견되었다. 모든 결함의 90% 이상이 코드가 실행되기 전의 검증 단계에서 포착되었다.

비교를 위해, 당시의 기존 IBM 프로젝트는 실행 전에 약 60%의 결함을 발견했다. Cleanroom은 그 비율을 뒤집었다.

일부 인크리먼트, 특히 1만 줄 미만의 작은 것들은 시스템 테스트에서 결함이 전혀 발견되지 않았다고 보고되었다. 여기서 “1만 줄로 zero-defect”이라는 주장이 유래한다. 실제로 일어났다. 보편적이지는 않았지만, IBM이 표준 벤치마크로 삼을 만큼 재현 가능했다.

왜 자신의 코드를 실행하지 않으면 버그가 줄어드는가

이것은 개발자들의 머리를 혼란스럽게 하는 부분이다. 테스트를 하지 않는 것이 어떻게 더 나은 코드를 생산할 수 있을까?

대답은 인지적이지 기술적이지 않다. 자신의 작업을 확인하기 위해 코드를 실행할 수 없다는 것을 알면, 더 신중하게 설계한다. 더 작은 함수를 작성한다. 입력하기 전에 엣지 케이스를 생각한다. 안전망이 없기 때문에 타입 시스템과 구조적 프로그래밍에 의존한다.

이것은 외과 의사들이 체크리스트를 사용하는 이유와 같다. 제약은 다른 정신적 모드를 강제한다.

통계적 품질 관리 층면도 있다. Cleanroom은 operational profile에 기반한 statistical usage testing을 사용한다. 테스트 케이스는 개발자의 버그 위치에 대한 추측이 아니라, 실제 사용자 행동의 확률 분포에서 추출된다. 이는 버그를 사냥하는 것뿐만 아니라 신뢰성을 측정하고 있다는 의미이다.

Cleanroom을 틈새로 남겨둔 트레이드오프

Cleanroom은 세계를 정복하지 못했다. 이유가 있다.

첫째, 훈련 장벽이 심각하다. formal specifications를 작성하고 mathematical correctness arguments를 구성할 수 있는 팀이 필요하다. 2025년 기준 대부분의 CS 졸업생은 비자명한 함수에 대한 형식적 증명을 해본 적이 없다.

둘째, 사전 설계 비용이 높다. IBM은 COBOL/SF 프로젝트에서 specification 텍스트가 설계 텍스트를 4대1로 초과했다고 보고했다. 설계 시간과 테스트 시간을 교환하고 있는 것이다. 이것은 컴파일러와 비행 소프트웨어에는 효과가 있다. 매주 요구사항이 바뀌는 CRUD 앱에는 효과가 없다.

셋째, zero-defect 주장은 defect density에 관한 것이지 모든 버그가 없다는 것에 관한 것이 아니다. Cleanroom 인크리먼트는 여전히 specification errors를 가질 수 있다. black box가 잘못되었다면, 검증된 clear box도 구조적으로 잘못된 것이다.

관료주의 없이 Cleanroom 규율을 훔치는 방법

아마도 완전한 Cleanroom을 채택할 수는 없을 것이다. 제품 관리자가 4대1의 사양 대 코드 비율을 기다리지는 않을 것이다. 하지만 고가치 부분은 훔칠 수 있다.

1. 구현보다 먼저 contract를 작성하라.

preconditions, postconditions, invariants를 사용하여 black box를 정의하라. 비공식적인 주석조차도 최적화 전에 경계에 대해 생각하게 만든다.

from typing import List, Tuple

def partition(nums: List[int], pivot: int) -> Tuple[List[int], List[int]]:
    """
    Black box spec:
      Pre:  True (any list of integers is valid).
      Post: left contains exactly the elements of nums where x <= pivot.
            right contains exactly the elements of nums where x > pivot.
            len(left) + len(right) == len(nums).
    """
    left = [x for x in nums if x <= pivot]
    right = [x for x in nums if x > pivot]

    # Runtime checks act as lightweight verification witnesses.
    assert all(x <= pivot for x in left)
    assert all(x > pivot for x in right)
    assert len(left) + len(right) == len(nums)

    return left, right

2. 일부 Unit Tests를 verification arguments로 대체하라.

테스트를 작성하기 전에, 코드가 왜 올바른지에 대한 한 문장의 주장을 작성하라. 그 문장을 구성할 수 없다면 설계가 너무 복잡한 것이다. 이것이 현대 팀을 위한 가장 효과적인 Cleanroom 실천이다.

3. property-based testing을 statistical usage testing으로 사용하라.

Python의 Hypothesis나 JavaScript의 fast-check 같은 도구는 분포로부터 입력을 생성한다. 이것은 예제 기반 Unit Tests보다 Cleanroom의 statistical testing에 정신적으로 더 가깝다.

4. 컴파일을 검증에서 분리하라.

한 줄 쓰고 컴파일하고 오타를 고치고 다음 줄을 쓰는 습관이 있다면, 그것은 반사적인 디버깅이다. 실행하기 전에 완전한 논리 단위를 작성해 보아라. 불편함이 핵심이다.

FAQ

Cleanroom은 오늘날에도 사용되는가?

안전이 중요한 영역과 미션 크리티컬한 영역에서 살아남아 있다. NASA, FAA, 일부 의료 기기 제조업체가 이 프로세스의 변형을 사용한다. 상용 소프트웨어에서는 드물다.

정말로 Unit Testing 없이 코드를 출시할 수 있는가?

그것과 동등하게 엄격한 것으로 대체하는 경우에만 가능하다. Cleanroom 팀은 대부분의 팀이 테스트에 쓰는 시간보다 더 많은 시간을 검증에 썼다. 시간이 사라진 것이 아니다. 왼쪽으로 이동했을 뿐이다.

Cleanroom은 제로 버그를 보장하는가?

아니다. 구현이 높은 확률로 specification과 일치함을 보장할 뿐이다. specification이 잘못되었다면 버그는 완벽하게 보존된다.

생산성에 미친 영향은 어땠는가?

IBM은 COBOL/SF 프로젝트에서 1인월당 400줄 이상의 코드 생산성을 보고했다. 이는 주로 대폭적으로 줄어든 테스트 시간이 증가한 설계 노력을 상쇄했기 때문이다.

결론

다음에 누군가 어떤 방법론이 zero-defect 소프트웨어를 제공한다고 주장하면, 프로젝트 데이터를 요구하라. IBM의 Cleanroom 숫자는 실제이지만, 특정한 맥락에서 나왔다: 경험 있는 팀, 형식적 훈련, 인크리멘탈한 제공, 그리고 디버깅 대신 검증하려는 의지.

1만 줄의 zero-defect 인크리먼트는 달성 가능하다. 다만 대부분의 조직이 지불하려 하지 않는 만큼의 선견지명이 필요할 뿐이다.