다익스트라의 Goto Statement Considered Harmful를 읽고
전 회사에서 RN으로 모바일 앱을 만들 때 클린 아키텍처 구조를 사용했는데, 그것에 대해서 아는 것이 많이 없어서 실전적 레벨에서의 코드를 작성함에 필요한 최소한 부분만 이해했고 이론적인 부분들은 많이 부족하다 느꼈었다.
그리고 몇 주 전, 좋은 기회를 받아서 토스 엑셀 5기 프리코스에 참여할 수 있었는데 생각치 못한 여러 관점에서 코드와 아키텍처를 바라볼 수 있음을 느꼈다.
그래서 프리코스 과정이 끝난 뒤, 몇 분들을 모아 클린 아키텍처 책 스터디를 시작했다.
책을 읽어나가는 중 다익스트라의 Go To Statement Considered Harmful 글의 원문을 접해서 읽게 되었고 이에 대한 리뷰를 해보고자 펜을 잡아들었다.(펜이 아니라 키보드인데 이런 것은 일단 넘어가자)
전제 1: 프로세스의 본질
My first remark is that programming is an activity by which the programmer produces a static text whose effect is to create a process.
The real criterion for the programmer is whether the process behaves in the desired way.
Once the program has been handed over to the machine, the corresponding process is delegated to the machine.
프로그래머의 책임 대상은 프로그램 텍스트가 아니라 그 프로그램이 만들어낸 프로세스의 동작이다.
프로그래머가 프로그램을 이해하고 통제한다고 할 수 있는 것은 그 프로세스가 어떻게 진행되는지를 예측하고 설명할 수 있어야한다.
그는 실행 흐름이 인간이 통제 가능한가?를 기준으로 제어 구조를 평가하겠다고 선언한 셈이다.
전제 2: 인간 인지의 한계
Our intellectual powers are rather geared to static relations and we are poorly equipped for visualizing processes evolving in time.
For that reason we should do our utmost to shorten the conceptual gap between the static program and the dynamic process.
We should try to make the correspondence between the program (spread out in text space) and the process (spread out in time) as simple as possible.
그는 인간의 인지 능력에 대한 전제를 명확히 했다. 인간은 정적인 개념엔 강하지만 시간에 따라 변하는 실행 흐름은 잘 다루지 못한다.
그래서 프로그래머가 머리 속으로 어디를 실행 중인지, 여기서 어디로 갈 수 있는지 시뮬레이션이 어려운 코드는 본질적으로 위험하다.
그래서 좋은 프로그램이란 정적 텍스트만 봐도 동적 실행 흐름이 예측 가능한 코드이다.
프로세스의 진행이란 무엇인가
다익스트라는 질문을 하나 던진다.
프로세스가 어느 지점까지 실행되었는지를 설명하려면 무엇을 필요로 하는가?
이 질문이 모든 논의의 출발점이다. 그의 관점에서 프로세스의 진행이란
- 지금 실행이 어디까지 왔는가
- 여기서 재개하려면 무엇을 알아야 하는가
를 명확하게 기술할 수 있는 상태를 의미한다.
순차적 코드와 텍스트 인덱스
순차적 코드는 goto문이 쓰이지 않고 단순한 대입들의 나열이 이어진 코드이다.
순차적 코드에서는
- 코드의 위치
- 실행 중인 지점
이 1:1로 대응한다. 그는 이때의 코드 위치를 텍스트 인덱스(textual index)라고 부른다.
이 구조에서는 프로세스의 진행이 곧 현재의 텍스트 인덱스이며 실행이 멈췄을 때, 지금 몇 번째 라인을 실행 중인지 알면 완전한 동일한 지점에서 재개가 가능하다.
A();
B();
C();위와 같은 순차적 코드에서는 실행 흐름이 코드의 순서와 정확히 일치한다.
따라서 “지금 B를 실행 중이다”라는 설명만으로도 프로세스의 현재 상태를 완전히 설명할 수 있다.
이때 코드 상의 위치가 곧 프로세스의 진행을 나타내는 텍스트 인덱스가 된다.
제어 구조는 프로세스의 진행을 어떻게 보존하는가
분기 구조
- 조건절(if - then)
- 대안절(if - then - else)
- 선택절(case)
- 조건 분기
다익스트라는 위와 같은 분기 구조들을 안전한 구조로 보았다.
그 이유는 분기를 포함하더라도 현재 실행 중인 코드 위치를 코드 구조만으로 명확히 설명할 수 있기 때문이다.
즉, 분기 전후에도 코드의 위치는 동일하기 때문에 진행 상태를 표현하는 좌표계가 깨지지 않는다.
다익스트라가 말하는 좌표란 현재 실행 상태(코드 라인, 실행 맥락)를 사람이 설명하기 위해 사용하는 기준이다.
이는 데이터 구조나 수학적 개념이 아니라, "지금 이 프로그램이 어디까지 실행되었는가"를 말하기 위한 인지적 기준이다.
if (x > 0) {
A();
} else {
B();
}
C();이 코드에서 실행 경로는 A 또는 B로 나뉘지만, 분기 이후 C를 실행 중이라는 상태의 의미는 변하지 않는다.
어떤 분기를 거쳐 왔는지와 관계없이 “현재 C를 실행 중이다”라는 설명만으로
실행 상태를 명확히 설명할 수 있다.
프로시저 호출
하지만 프로시저가 개입한다면 이야기가 달라진다.
프로시저 호출이 등장하면 단일 텍스트 인덱스만으로는 정보가 부족해진다. '어떤 프로시저에서 왔는지'도 필요하기 때문이다.
그래서 다익스트라는 프로세스의 진행 = 텍스트 인덱스들의 순열 이라고 정의한다.
이 순열의 길이는 콜 스택의 깊이와 정확히 일치한다.
void foo() {
A();
B();
}
int main() {
foo();
C();
}만약 현재 B를 실행 중이라면, 실행 상태는 단순히 “B를 실행 중이다”가 아니라
“foo 내부에서 B를 실행 중이며, main에서 foo를 호출한 상태”로 설명된다.
이처럼 프로시저 호출이 포함된 경우, 프로세스의 진행은 텍스트 인덱스들의 순열, 즉 콜 스택으로 표현된다.
프로시저의 호출로 인해 진행 상태가 구조적으로 확장될 뿐 인지적으로 어렵게 만들어지진 않는다.
반복 구조
그렇다면 반복은 어떻게 모델링하는가?
- while B repeat A
- repeat A until B
논리적으로 재귀로 대체할 수 있지만 다익스트라는 반복절의 사용을 권장한다.
그 이유는 아래와 같다.
- 인간에게 익숙한 사고 방식
- 귀납적 사고가 반복절의 구조에 자연스럽게 반영되었기 때문
반복이 등장하며 동적 인덱스라는 새로운 개념이 추가되었다.
동적 인덱스는 모두가 추론할 수 있듯 반복문이 실행된 횟수를 말한다.
반복문에 진입할 때마다 하나의 동적 인덱스가 생성된다. 반복이 중첩되면 각 반복문은 각자의 동적 인덱스를 갖는다.
정리: 프로세스 진행을 표현하는 좌표계
다익스트라는 프로세스의 진행은 (텍스트 인덱스 + 동적 인덱스)의 조합으로 유일하게 표현될 수 있다고 결론 내렸다.
중요한 것은 이 인덱스들은 프로그래머가 직접 관리하지 않고 실행 과정에서 동적으로 결정된다는 것이다.
- 순차 코드는 텍스트 인덱스만으로 실행 상태를 설명할 수 있다
- 분기 구조는 실행 경로를 나누지만, 실행 위치를 설명하는 데 혼란을 만들지 않는다
- 프로시저 호출은 콜 스택이라는 구조적 확장이다
- 반복 구조는 동적 인덱스를 통해 실행 상태를 안정적으로 표현한다
주장: goto문은 해롭다!
다익스트라는 goto문이 실행 흐름을 자유롭게 이동시킬 수 있게 만드는 대신,
실행 상태를 유용하고 관리 가능한 방식으로 설명하기 어렵게 만든다고 지적한다.
The introduction of the go to statement makes it extremely hard to describe the progress of a process in a useful and manageable way.
그에 따르면 goto문을 사용하는 순간, 프로세스의 실행 상태를 설명하기 위해 사용할 수 있는 좌표는 프로그램 시작 이후 지금까지 몇 번의 동작이 실행되었는지라는 기준밖에 남지 않는다.
The only coordinate left to characterize the progress is the number of actions performed since the start.
즉, 실행 상태를 “지금 어디를 실행 중인가?”가 아니라 “지금 몇 번째 실행인가?” 로 설명해야 하는 상황이 되는 것이다.
A();
goto X;
B();
X:
C();이 코드에서 “현재 C를 실행 중이다”라는 정보만으로는 실행 상태를 충분히 설명할 수 없다.
A에서 바로 점프해 왔는지, B를 건너뛰고 도달했는지에 따라 실행 맥락이 전혀 달라지기 때문이다.
즉, 같은 코드 라인이라도 실행 이력에 따라 의미가 달라지게 된다.
다익스트라는 이러한 좌표가 본질적으로 하나뿐이며, 사람이 사용하기에는 극도로 어렵다고 말한다.
Such coordinates are intrinsically unique, but extremely hard to use.
이 좌표는 단순한 순번 카운터에 불과하다. 코드 구조와 아무런 관계도 없고, 정적 텍스트만 보고는 유추할 수도 없으며, 실제로 실행을 따라가며 세지 않으면 알 수가 없다.
If we want to define all progress points, the task becomes hopelessly complicated.
이로 인해 goto문이 존재하는 코드에서는
현재 실행 중인 코드 라인을 알고 있더라도 그 의미를 파악하기가 어렵다.
같은 줄이라도 여러 지점에서 점프해 도달할 수 있고, 그에 따라 실행 맥락이 모두 달라지기 때문이다.
결국 “지금 이 줄을 실행 중이다”라는 말만으로는 상황을 충분히 설명할 수 없게 된다.
다익스트라는 이러한 특성을 근거로 goto문을 너무 원시적이며, 임의적인 점프를 과도하게 허용하는 구조라고 평가한다.
The go to statement is too primitive; it permits too much arbitrary jumping.
그리고 바로 그 이유 때문에 그 사용은 해롭다고 보아야 한다고 결론짓는다.
Its use should be regarded as harmful.
결론
다익스트라는 goto문이 실행 상태를 코드 구조만으로 설명할 수 없게 만든다는 점을 문제 삼았다.
프로세스는 사람이 예측하고 설명 가능해야 하며, goto는 이 조건을 깨뜨린다.
이 관점은 구조적 프로그래밍으로 이어졌다. 임의적인 점프를 배제하고 조건문·반복문·프로시저 같은 제한된 제어 구조만 허용함으로써, 실행 흐름을 코드 구조로 설명 가능하게 만들려는 시도였다. 이 기준은 이후의 언어 설계에 반영되어 오늘날까지 이어지고 있다.
이 논문을 통해 현대 프로그래밍 언어에 왜 goto문이 사라졌는지, 그리고 분기와 반복이 어떤 방식으로 구조화되었는지에 대한 역사적 배경을 이해할 수 있었다. 개발을 하다보면 OOP와 FP에 대해서 취향이나 방법론에 대한 논쟁이 이어지지만, 어떤 패러다임을 사용하고 선호하든 우리는 모두 구조적 프로그래머이다.
추상화된 제어 흐름을 문법으로 정리해낸 다익스트라의 문제의식에 경의를 표하며 이 글을 마친다.