Go To statement Considered Harmful 논문 리뷰
서문
이 글은 Goto문을 사용하지 말아야한다고 주장하는 다익스트라의 논문 "Go To statement Considered Harmful"을 읽고 개인적인 감상평을 이야기하는 글입니다.
리뷰하기 앞서: Goto문이란
1#include <stdio.h>23int main() {4int number = 5;5if (number >= 0) {6goto even;7} else {8goto odd;9}1011even:12printf("짝수");1314odd:15printf("홀수");1617return 0;18}
위의 코드와 같이 지정한 레이블로 코드 흐름을 즉시 이동하는 일종의 jump 문법입니다. C를 배워본 사람은 알겠지만, 문법은 있지만 거의 쓰지 말라고 배웁니다.
Goto문은 해롭다고 여겨진다
서두에서 다익스트라는 goto문을 적게 쓸수록 실력 좋은 프로그래머라는 생각이 든다고 밝히며 고차원 개발 언어 (high level langauge) goto문을 제외할 것을 제안합니다.그에 앞서 2가지, 주장이 시작되기 전 필요한 공통된 이해를 설명하는데 각각 다음과 같습니다.
기저1: 프로그래밍의 목적은 프로그램이 만드는 프로세스에 있다
"프로그래밍의 목적은 프로그램 그 자체가 아닌 프로그램이 만드는 프로세스에 있다."
어찌보면 당연한 이야기입니다. 우리가 프로그램을 만드는 것은 프로그램이 원하는 동작을 하길 바라기 때문입니다. 예를 들어 계산기 프로그램을 만드는 이유는 계산기가 '계산'을 하길 기대하기 때문입니다. 논리적으로 문제가 없는 계산기 프로그램을 만들더라도 그것이 계산기가 1+1=2
라는 간단한 계산도 제대로 못한다면 우리는 계산기를 만들었다고 할 수 없습니다.
또한 프로그래머는 한번 만들어진 프로그램의 동작에 개입할 수 없습니다. 즉, 계산이라는 프로세스는 오직 계산기에 의해서만 이루어진다는 것이죠.
기저2: 인간은 동적인 프로세스를 파악하는 지능이 정적인 관계를 이해하는 지능보다 상대적으로 낮다.
쉽게 이야기해서 정적인 코드를 해석하는 것은 쉽지만, 동적인 프로세스가 어떻게 진행되었는지는 파악하기 어렵다는 이야기입니다.다음의 코드를 봅시다.
1let count = 0;23while (count < 100) {4if (count % 13 < 3) {5count /= 6;6} else {7count += 5;8}9}
우리는 위 프로그램이 "count가 100보다 커지면 종료된다" 그리고 "count는 13으로 나누어 나머지가 3보다 작으면 6으로 나누고 아니면 5를 더한다" 라는 규칙을 쉽게 파악할 수 있습니다.하지만 count는 프로세스가 진행됨에 따라 변형되고 특정 타이밍에 count의 값이 어떻게 되는지는 파악하기 어렵습니다. 예를 들어 10번째 루프에서 count의 값이 어떻게 되는지 바로 파악이 되시나요?
따라서 다익스트라는 정적인 프로그램과 동적인 프로세스 사이의 갭을 최대한 줄여야한다고 주장합니다.
프로세스의 진행도를 알기 위해 필요한 것들
만약 A라는 언어로 작성된 프로그램이 다음과 같이 할당문의 연속으로 구성되어 있다고 합시다.
1const a = 1;2const b = 2;3const c = 3;4let e = c;5e = e * a;6// ...
위의 코드에서 특정 시점의 프로세스의 상태를 설명할 땐 코드의 한 지점을 가리키면 됩니다. 이를 텍스트 인덱스(textual index)라고 합시다.이해를 위해 조건을 붙이겠습니다.
A 언어는 한 줄에 하나의 명령만 실행할 수 있습니다. 즉, const a = 1; const b = 2;
와 같이 한 줄에 이어서 코드를 작성할 수 없습니다.
이제 텍스트 인덱스는 line number와 일치하게 됩니다. 몇번 줄을 진행하고 있는지 알려주는 것으로 프로세스가 얼만큼 진행되었는지 알 수 있습니다.
언어에 if문, if-else문, switch문 같은 분기를 발생시키는 문법을 포함하게 되면 코드 안에서 건너뛰는 부분이 생길지라도 여전히 몇번 줄을 진행하고 있는지 알려주는 것으로 프로세스의 진행도를 알 수 있습니다.
1const a = 1;2const b = 2;3const c = 3;4let e = c;5if (a === 3) {6e = e * a;7} else {8e = e * b;9}10// if-else가 추가되었고 일부 코드 진행이 생략될 수 있게11// 되었지만, 프로세스의 진행 상태를 lineNumber를 가리키는 것으로12// 특정할 수 있는 것은 여전하다.
이제 A언어에 "절차(procedure)"라는 개념을 추가해봅시다. 절차는 일종의 subRutine, function이라고 보면 됩니다.
1function add3(num) {2const three = 3;34return num + three;5}67const a = 1;8const b = 2;9const c = 3;10let d = c;1112d = add3(0);13const e = add3(d);1415// 이제 텍스트 인덱스가 2를 가리킨다고 해도16// 12번 줄의 add3를 호출해서 인덱스 2에 도달했는지,17// 13번 줄의 add3를 호출해서 인덱스 3에 도달했는지18// 알 수 없다.
절차가 추가된 언어에선 텍스트 인덱스로는 프로세스의 상태를 설명할 수 없습니다.
2번줄을 진행하고 있다고만 말하는 것으로는 12번줄 다음 2번줄로 왔는지, 13번줄 다음 2번줄로 왔는지 알 수 없습니다. 따라서 프로세스의 진행 상태를 나타내려면 텍스트 인덱스가 어떤 절차 안에 들어있는지를 함께 명시해야만 합니다.
이제 프로세스의 진행도는 텍스트 인덱스의 배열로 나타낼 수 있습니다. js가 함수 호출 스택을 만드는 것으로 이해하면 됩니다. 앞선 예시로 보면
[12, 2]
: 12번 줄 다음 2번줄로 이어짐
[12, 3]
: 13번 줄 다음 3번줄로 이어짐으로 이해할 수 있습니다.
배열의 길이는 함수가 얼마나 깊게 호출되는지에 따라 결정될 것입니다.이제 반복문을 A언어에 추가해봅시다.
1function add3(num) {2const three = 3;34return num + three;5}67const a = 1;8const b = 2;9const c = 3;10let d = c;1112d = add3(0);13const e = add3(d);1415while (d > 0) {16d--;17}1819while (e > 0) {20e--;21add3(10);22}2324// 이제 얼만큼 반복되었는지도 알아야 프로세스의 진행도를 알 수 있다.
이제 프로세스의 맥락을 이해하려면 반복문의 반복 횟수까지 알아야합니다.반복 횟수를 나타내기 위해 "동적 인덱스(dynamic index)"라는 개념을 추가합시다.
반복문 안에서 함수를 호출할수도 있으니, 텍스트 색인 배열과 동적 인덱스를 알아야 프로세스의 진행도를 알 수 있습니다.
[21, 2], 1
: 21번 줄을 1번째 진행중이었고 함수를 통해 2번 줄을 진행중임
[15], 2
: 15번 줄을 2번째 진행중이었음
프로세스의 진행도는 왜 알아야하죠?
앞서 언급한 텍스트 인덱스의 배열, 동적 인덱스를 통해 프로세스의 진행 과정을 나타내는데 성공했습니다.왜 우리가 이런 인덱스를 동원해 프로세스의 특정 진행 지점을 알아야할까요?
그 이유는 우리가 변수의 값을 프로세스의 진행을 통해서만 해석할 수 있기 때문입니다.방의 인원수를 나타내는 변수 n이 있고 0부터 시작한다고 해봅시다.
1// n은 방의 인원수를 나타낸다.2let n = 0;
이제 우리는 방에 사람이 들어올때마다 n을 증가시켜 방의 인원수를 나타낼 수 있습니다.
1let n = 0;2while (untilPeopleIncome) {3n++;4}56n; // 방의 인원수
Goto는 이러한 좌표계를 무너뜨린다
goto를 이용해 3번줄로 이동해버리면 n의 의미가 깨집니다. n은 이제 방의 인원수보다 큰 수 일수도 있습니다.
1let n = 0;2while(untilPeopleIncome) {3n++;4}5if(someCondition) {6goto 37}8n; // 방의 인원수와 같다고 할 수 있을까?
goto문을 무분별하게 사용하다보면 이렇게 텍스트 인덱스, 동적 인덱스로 나타낼 수 있는 프로세스의 진행도를 무의미하게 만듦니다. 텍스트 인덱스, 동적 인덱스가 고유하더라도 그에 해당하는 프로세스의 진행도의 해석이 더 이상 유의미하지 않기 때문입니다.
결론: 그럼에도 불구하고
다익스트라는 goto문은 너무 원시적이라 프로그램을 망치기 딱 좋다고 말하지만, abortion문 (js의 throw
) 같이 프로세스의 흐름을 건너뛰는 것 자체를 부정하지는 않습니다. 하지만 이런 문법이 프로세스를 해석하는 것을 방해해선 안된다고 합니다.
요약
- 우리는 상태값을 해석하려면 프로세스의 진행도를 알아야 한다.
- 근데 goto를 무분별하게 쓰면 프로세스의 진행도를 알아도 상태값을 해석하기 어려워진다.
- 그러니까 goto 쓰지마라
- 그래도 throw같은 코드를 쓰지 말라는 것은 아니다. 상태 해석에만 문제가 없으면 상관 없다.
리뷰 후기
이제는 거의 사라져서 볼 수 없는 goto 문법이지만, 여전히 goto의 제한된 버전인 throw
같은 문법들이 현대 개발 언어에 남아있다는 점이 논문을 읽는데 재미있는 포인트들이었습니다.
논문에서 언급하지는 않지만, throw
같은 abortion 문도 막아야한다고 주장하지 않은 것은 에러가 발생했을 때만 점프가 일어나 해석을 방해하지 않기 때문이라고 생각합니다.
앞으로는 코드를 작성할 때 프로세스의 흐름 파악과 해석을 어렵게 하고 있지 않은지 좀 더 주의깊게 봐야할 것 같습니다.
리뷰에 적지는 않았지만, 흥미로운 점이 하나 더 있었습니다. 논문 내에서 재귀로 반복문을 없앨 수 있지만, 성능상 이유로 반복문은 있는게 좋은 것 같다고 하는 점, 그리고 정적인 코드와 동적인 프로세스 사이의 갭을 줄여야한다고 하는 것도 그렇고 이미 좋은 코드는 선언적이라는 함수형 패러다임에 대한 인사이트를 어느정도 가지고 있다는 점입니다.