로버트 C. 마틴의 클린 아키텍처: 소프트웨어 구조와 설계의 원칙을 읽으며 기록하고 싶은 부분만 발췌해 각색하여 작성한 글입니다. 하여 실제 글쓴이의 의도와 다르게 작성될 수 있음을 알립니다.
데이크스트라는 진공관 시대에 자신의 경력을 시작했는데, 이 시대는 컴퓨터가 거대하고, 쉽게 손상되며, 느린 데다가 결과마저 믿을 수 없는, 그래서 오늘날의 기준으로 보면 극도로 제한적으로만 사용될 때 였다.이 초창기에는 프로그램을 바이너리 또는 매우 투박한 기계어를 사용해서 작성했고, 입력은 종이테이프나 천공카드와 같은 물리적인 형태를 띠었다. 수정·컴파일테스트를 반복하는 일은 최소 몇 시간에서 며칠이 걸렸다.
데이크스트라는 이처럼 원시적인 환경에서 위대한 발견을 해냈다.
# 증명
데이크스트라가 초기에 인식한 문제는 프로그래밍은 어렵고, 프로그래머는 프로그래밍을 잘하지 못한다는 사실이었다. 모든 프로그램은 설령 단순할지라도 인간의 두뇌로 감당하기에는 너무 많은 세부사항을 담고 있었다. 아주 작은 세부사항이라도 간과하면 프로그램이 동작하는 것처럼 보이더라도 결국엔 예상 외의 방식으로 실패하곤 했다.
데이크스트라는 증명 proof 이라는 수학적인 원리를 적용하여 이 문제를 해결하고자 했다. 그의 비전은 공리, 정리, 따름정리, 보조정리로 구성되는 유클리드 계층구조를 만드는 것이었다.
유클리드 계층구조
공리(axiom)는 증명 없이 참으로 받아들이는 명제를 뜻한다. 유클리드 기하학에서 "두 점이 주어졌을 때, 두 점을 지나는 직선이 단 하나 존재한다"는 명제 역시 증명할 수 없기에 공리라고 부른다. 정리(theorem)는 증명이라는 과정을 통해 참이라는 것이 밝혀진 명제다. 이러한 정리를 증명하는 데 필요한 정리를 보조정리(lemma)로 부르며, 정리를 통해 자연스럽게 도출되는 정리를 따름정리(corollary)라고 부른다. 데이크스트라는 프로그램을 증명하는 이러한 계층구조를 만들고자 했다.
데이크스트라는 수학자가 유클리드 계층구조를 사용하는 방식을 프로그래머도 사용할 수 있다고 믿었다. 다시 말해 프로그래머는 입증된 구조를 이용하고, 이들 구조를 코드와 결합시키며, 그래서 코드가 올바르다는 사실을 스스로 증명하게 되는 방식이었다. 당연하게도 데이크스트라는 이렇게 하려면 단순한 알고리즘에 대해 기본적인 증명을 작성할 수 있는 기법을 보여줘야 한다는 사실을 깨달았다. 그러나 이 연구는 상당히 힘들어 보였다.
데이크스트라는 이 연구를 진행하면서 goto 문장이 모듈을 더 작은 단위로 재귀적으로 분해하는 과정에 방해가 되는 경우가 있다는 사실을 발견했다. 만약 모듈을 분해할 수 없다면, 합리적으로 증명할 때 필수적인 기법인 분할 정복 접근법을 사용할 수 없게 된다.
반면 goto 문장을 사용하더라도 모듈을 분해할 때 문제가 되지 않는 경우도 있었다. 데이크스트라는 이런 goto문의 '좋은' 사용 방식은 if/then/else와 do/while과 같은 분기의 반복이라는 단순한 제어 구조에 해당한다는 사실을 발견했다. 모듈이 이러한 종류의 제어 구조만들 사용한다면 증명 가능한 단위까지 모듈을 재귀적으로 세분화하는 것이 가능해 보였다.
그는 이러한 제어 구조는 순차 실행 sequential execution과 결합했을 때 특별하다는 사실을 깨달았다. 이러한 제어 구조는 데이크스트라보다 2년 앞서 발견됐는데, 모든 프로그램을 순차 sequence, 분기 selection, 반복 iteration이라는 세 가지 구조만으로 표현할 수 있다는 사실이 증명되었다. 즉, 모듈을 증명 가능하게 하는 바로 그 제어 구조가 모든 프로글램을 만들 수 있는 제어 구조의 최소 집합과 동일하다는 사실이었다. 구조적 프로그래밍은 이렇게 탄생했다.
데이크스트라는 단순한 열거법을 이용해 순차 구문 sequential statement이 올바름을 입증할 수 있다는 사실을 보여주었다. 이 기법에서는 각 순차 구문의 입력을 순차 구문의 출력까지 수학적으로 추적한다. 이 접근법은 일반적인 수학적 증명 방식과 다를 바 없다.
분기 selection의 경우, 데이크스트라는 열거법을 재적용하는 방식으로 처리했다. 먼저 분기를 통한 각 경로를 열거했다. 결과적으로 두 경로가 수학적으로 적절한 결과를 만들어낸다면, 증명은 신뢰할 수 있게 된다.
반복 iteration은 조금 다르다. 반복이 올바름을 증명하기 위해 데이크스트라는 귀납법을 사용했다. 열거법에 따라 1의 경우가 올바름을 증명했다. 그리고 N의 경우가 올바르다고 가정할 때 N+1의 경우도 올바름을 증명하며, 이 경우에도 열거법을 사용했다. 또한 반복의 시작 조건과 종료 조건도 열거법을 통해 증명했다.
이 증명은 고되고 복잡했지만, 증명은 증명이었다. 이 증명을 해냄으로써 프로그램에서도 정리에 대한 유클리드 계층구조를 만들 수 있을 거라는 생각이 실제로 이루어질 듯 보였다.
# 해로운 성명서
1968년 데이크스트라는 CACM(Communications of the ACM) 3월호에 "goto문의 해로움 Go To Statement Considered Harmful"이라는 제목으로, 세 가지 제어 구조에 대한 자신의 의견을 피력했다. 그리고 프로그래밍 세계에는 불이 붙었고, 이를 지지하는 의견과 부정적인 의견이 오고가는 전쟁이 10년 이상 지속되었다.
마침내 데이크스트라의 승리로 논쟁이 수그러들었다. 컴퓨터 언어가 진화하면서 goto 문장을 계속 뒤편으로 밀려났고, 마침내 거의 사라졌다. 대다수의 현대적 언어는 goto 문장을 포함하지 않으며, 당연히 LISP에서는 처음부터 없었다. 제어흐름을 제약 없이 직접 전환할 수 있는 선택권 자체를 언어에서 제공하지 않기 때문에 현재의 우리 모두는 선택의 여지없이 구조적 프로래머이다.
# 기능적 분해
구조적 프로그래밍을 통해 모듈을 증명 가능한 더 작은 단위로 재귀적으로 분해할 수 있게 되었고, 이는 결국 모듈을 기능적으로 분해할 수 있음을 뜻했다. 즉, 거대한 문제 기술서를 받더라도 문제를 고수준의 기능들로 분해할 수 있다. 그리고 이들 각 기능은 다시 저수준의 함수들로 분해할 수 있고, 이러한 분해 과정을 끝없이 반복할 수 있다. 게다가 이렇게 분해한 기능들은 구조적 프로그래밍의 제한된 제어 구조를 이용하여 표현할 수 있다.
# 엄밀한 증명은 없었다
하지만 끝내 증명은 이루어지지 않았다. 프로그램 관점에서 정리에 대한 유클리드 계층구조는 끝내 만들어지지 않았다. 그러나 다행히도 무언가가 올바른지를 입증할 때 사용하는 전략에 유클리드 방식같이 엄밀한 수학적인 증명만이 있는 것은 아니다. 상당히 성공한 또 다른 전략으로는 과학적 방법이 있다.
# 과학이 구출하다
과학은 근본적으로는 수확과는 다른데, 과학 이론과 법칙은 그 올바름을 절대 증명할 수 없기 때문이다. 실험을 아무리 많이 수행하더라도 또는 경험적인 증거를 아무리 많이 수집하든지 간에, 언젠가는 다른 실험을 통해 운동 법칙과 만유인력의 법칙이 잘못되었음이 밝혀질 가능성은 항상 열려있다.
이 점이 바로 과학적 이론과 법칙이 지닌 본성이다. 즉, 과학적 방법은 반증은 가능하지만 증명은 불가능하다.
과학은 서술된 내용이 사실임을 증명하는 방식이 아니라 서술이 틀렸음을 증명하는 방식으로 동작한다. 각고의 노력으로도 반례를 들 수 없는 서술이 있다면 목표에 부합할 만큼은 참이라고 본다. 물론 서술이 모두 증명 가능한 것은 아니다. "이것은 거짓말이다"와 같은 서술은 참도 아니고 거짓도 아니다. 이는 증명이 불가능한 서술 중 가장 간단한 사례 중 하나다.
결론적으로 수학은 증명 가능한 서술이 참임을 입증하는 원리라고 볼 수 있다. 반면 과학은 증명 가능한 서술이 거짓임을 입증하는 원리라고 볼 수 있다.
# 테스트
데이크스트라는 "테스트는 버그가 있음을 보여줄 뿐, 버그가 없음을 보여줄 수는 없다"고 말한 적이 있다. 다시 말해 프로그램이 잘못되었음을 테스트를 통해 증명할 수는 있지만, 프로그램이 맞다고 증명할 수는 없다. 테스트에 충분한 노력을 들였다면 테스트가 보장할 수 있는 것은 프로그램이 목표에 부합할 만큼은 충분히 참이라고 여길 수 있게 해주는 것이 전부다.
이 같은 사실이 내포하는 의미는 너무도 충격적이다. 소프트웨어 개발이 수학적인 구조를 다루는 듯 보이더라도, 소프트웨어 개발은 수학적인 시도가 아니라는 사실이다. 오히려 소프트웨어는 과학과 같다. 최선을 다하더라도 올바르지 않음을 증명하는 데 실패함으로써 올바름을 보여주기 때문이다.
구조적 프로그래밍은 프로그램을 증명 가능한 세부 기능 집합으로 재귀적으로 분해할 것을 강요한다. 그러고 나서 테스트를 통해 증명 가능한 세부 기능들이 거짓인지를 증명하려고 시도한다. 이처럼 거짓임을 증명하려는 테스트가 실패한다면, 이 기능들은 목표에 부합할 만큼은 충분히 참이라고 여기게 된다.
# 결론
구조적 프로그래밍이 오늘날까지 가치 있는 이뉴는 프로그래밍에서 반증 가능한 단위를 만들어 낼 수 있는 바로 이 능력 때문이다. 또한 흔히 현대적 언어가 아무런 제약 없는 goto 문장은 지원하지 않는 이유이기도 하다. 뿐만 아니라 아키텍처 관점에서는 기능적 분해를 최고의 실천법 중 하나로 여기는 이유이기도 하다.
가장 작은 기능에서부터 가장 큰 컴포넌트에 이르기까지 모든 수준에서 소프트웨어는 과학과 같고, 따라서 반증 가능성에 의해 주도된다. 소프트웨어 아키텍트는 모듈, 컴포넌트, 서비스가 쉽게 반증 가능하도록 (테스트하기 쉽도록) 만들기 위해 분주히 노력해야 한다. 이를 위해 구조적 프로그래밍과 유사한 제한적인 규칙들을 받아들여 활용해야 한다.