구조적 프로그래밍
다익스트라는 진공관 시대에 자신의 경력을 시작했다. 이 시대의 컴퓨터는 거대하고 쉽게 손상되며 느린데다가 결과마저 믿을 수 없어서 극도로 제한적으로만 사용되었다.
이 시기에는 프로그램을 바이너리로 또는 매우 투박한 기계어를 이용해서 작성했다. 입력은 종이테이프나 천공카드와 같은 물리적인 형태를 띄었고 수정과 컴파일, 테스트를 반복하는 일은 최소 몇 시간에서 며칠이 걸렸다.

4.1 증명
다익스트라가 초기에 인식한 문제는 프로그래밍은 어렵고, 프로그래머는 프로그래밍을 잘하지 못한다는 사실이었다.
아무리 단순한 프로그램이라도 너무 많은 세부사항이 존재했고 아주 작은 세부사항이라도 간과하면 예상 외의 방식으로 실패하곤 했다.
많은 세부사항이 존재
👉🏻 세부사항을 다 챙기는 것이 어렵다
👉🏻 작은 세부사항이라도 간과하게 된다
👉🏻 예상 외의 방식으로 실패한다
다익스트라는 증명(proof)이라는 수학적인 원리를 적용하여 이 문제를 해결하고자 했다.
공리, 정리, 따름정리, 보조정리로 구성되는 유클리드 계층구조를 이용하여 코드와 결합하여 코드가 올바르다는 사실을 스스로 증명하게 되는 방식이다.
다익스트라는 단순한 알고리즘에 대해 기본적인 증명을 작성할 수 있는 기법을 보여주기 위한 연구를 시작했다.
그 연구를 진행하면서 goto 문이 모듈을 더 작은 단위로 재귀적으로 분해하는 과정에 방해가 되는 경우가 있다는 사실을 발견했다. 모듈을 분해할 수 없다면 분할 정복 접근법을 사용할 수 없게 된다.
반면 goto문을 사용하더라도 문제가 되지 않는 경우도 있었다. 좋은 사용 방식은 if/then/else와 do/while과 같은 분기와 반복이라는 단순한 제어 구조에 해당한다는 사실이었다.
1950~60년대 초반엔 고급 언어로는 포트란과 코볼, 알골 같은 것들이 있었다. 분기와 반복이란 개념은 존재했지만 현대적인 방식으로 쓰이고 있지 않았고 프로그램 흐름 제어는 전적으로 goto를 이용한 점프에 의존하고 있었다.
if (a == b) {
x = 1;
} else {
x = 0;
} INTEGER A, B, X
IF (A .EQ. B) GOTO 10
X = 0
GOTO 20
10 X = 1
20 CONTINUE위는 동일한 if~else 분기를 C와 포트란으로 나타낸 것이다.
10, 20은 라벨이고 점프를 위해서 지정한다고 보면 된다.
while (n > 0) {
sum += n;
n--;
} INTEGER N, SUM
SUM = 0
10 IF (N .LE. 0) GOTO 20
SUM = SUM + N
N = N - 1
GOTO 10
20 CONTINUE위는 동일한 while 반복을 C와 포트란으로 나타낸 것이다.
이런 제어 구조만 이용한다면 증명이 가능한 단위까지 재귀적으로 세분화하는 것이 가능해보였다.
그는 이런 제어 구조는 순차 실행(sequential execution)과 결합 했을 때 특별하다는 사실을 깨달았다. 이런 제어 구조는 뵘(Bøhm)과 야코피니(Jacopini)가 다익스트라보다 2년 앞서 발견했는데 모든 프로그램을 순차(sequence), 분기(selection), 반복(interation)이란 세 가지 구조만으로 표현할 수 있다는 사실을 증명했다.
4.2 해로운 성명서
1968년 다익스트라는 CACM(Communications of the ACM) 편집자에게 편지를 썼고 그 내용은 같은 해 3월 호에 실렸다.

그리고 프로그래밍 세계에는 불이 붙었다. 그 당시엔 인터넷이 없었지만 학술지 출판사의 편집자에게 편지를 보낼 수 있었고 부정적이고 모욕적인 편지도 있었지만 강력하게 지지하는 의견도 있었다.
그리고 다익스트라가 승리하며 논쟁은 수그라들었다.
프로그래밍 언어가 진화하며 goto문은 계속 사라져갔고 현대에 들어와서는 거의 찾아볼 수 없게 되었다.
현재 우리 모두는 구조적 프로그래머이며, 여기에는 선택의 여지가 없다. 제어 흐름을 제약 없이 직접 전환할 수 있는 선택권 자체를 언어에서 제공하지 않기 때문이야.
제어 흐름을 직접 전환한다는 것이 무엇일까?
아마도 지금 실행 중인 위치에서 프로그래머가 원하는 임의의 다른 위치로 바로 점프하는 것을 말하는 것으로 보인다. 구조를 거치지 않고, 중간 단계를 무시하고 어디든 바로 보내는 것이 goto의 능력이기도 하니까.
즉, 논리적으로 검증하지 않고 점프가 가능하다는 말이다. 현대의 대부분의 언어는 goto가 없다. 즉 프로그래머의 니즈에 따라 분기와 반복 등을 맘대로 들락날락 할 수 없다.
관련 이야기는 다른 포스트에 다뤘으니 읽어보아도 좋을 것 같다.
4.3 기능적 분해
구조적 프로그래밍을 통해서 모듈을 증명 가능한 더 작은 단위로 재귀적으로 분해할 수 있게 되었고 이는 결국 모듈을 기능적으로 분해할 수 있음을 뜻했다.
즉, 거대한 문제를 만나더라도 문제를 고수준의 기능들로 분해하고 각 기능들은 저수준의 함수들로 분해할 수 있고 이 과정을 끝없이 반복할 수 있다는 뜻이다.
4.4 과학이 구출하다
끝내 증명은 이뤄지지 않았다. 프로그램 관점에서 정리에 대한 유클리드 계층구조는 끝내 만들어지지 않았다.
대부분의 프로그래머는 기능 하나하나를 엄밀히 증명하는 고된 작업이 이득을 가져오리라 생각치 않았다.
다행히도 증명에 유클리드 방식과 같이 수학적인 증명만 있는 것은 아니다.
과학은 근본적으로 수학과 다른데, 과학 이론과 법칙은 그 올바름을 절대 증명할 수 없기 때문이다.
수학은 주어진 공리 체계 안에서 논리적으로 필연적인 진리를 다루고, 과학은 현실 세계를 설명하는 반증 가능한 모델로서 언제든 수정·확장될 수 있다.
수학은 ‘틀릴 수 없는 세계’를 다루고, 과학은 ‘틀릴 수 있음에도 불구하고 가장 잘 맞는 설명’을 다룬다.
과학은 서술된 내용이 사실임을 증명하는 방식이 아니라 서술이 틀렸음을 증명하는 방식으로 동작한다.
결론적으로 수학은 증명 가능한 서술이 참임을 입증하는 원리, 과학은 증명 가능한 서술이 거짓임을 입증하는 원리라고 볼 수 있다.
4.5 테스트
테스트는 프로그램이 잘못되었음을 증명할 수 있지만 프로그램이 맞다고는 증명할 수 없다.
테스트에 충분한 노력을 들였다면 테스트가 보장할 수 있는 것은 프로그램이 목표에 부합할 만큼 충분히 참이라고 여길 수 있게 해주는 것이 전부다.
소프트웨어는 과학과 같다. 최선을 다하더라도 올바르지 않음을 증명하는 데 실패함으로써 올바름을 보여주기 때문이다.
4.6 결론
구조적 프로그래밍이 오늘날까지 가치 있는 이유는 프로그래밍에서 반증 가능한 단위를 만들어 낼 수 있는 바로 이 능력 때문이다.
또한 현대적인 언어들이 제약 없는 goto문을 지원하지않는 이유이기도 하다.
작은 기능에서부터 큰 컴포넌트에 이르기까지 모든 수준에서 소프트웨어는 과학과 같고, 따라서 반증 가능성에 의해 주도된다.
소프트웨어 아키텍트는 모듈, 컴포넌트, 서비스가 쉽게 반증 가능하도록(테스트하기 쉽도록) 만들기 위해 노력해야 한다.