코드가 엉망일 때 개발자들은 스파게티 같다고 말합니다. 그런데 정확히 무엇이 스파게티일까요.
다음 글 코드 냄새 #2에서 다음 패키지를 이어서 봅니다.
길어서? 지저분해서?
둘 다 아닙니다. 스파게티가 가리키는 건 제어 흐름입니다.
실행 순서를 눈으로 따라가려는데 면발처럼 얽혀서 어디로 튈지 모르는 상태, 그게 원래 뜻입니다.
이 말의 뿌리를 따라가면 1968년에 발표된 편지 한 통이 나옵니다.
다익스트라가 쓴 편지 한 통
1968년 3월, 에츠허르 다익스트라가 ACM(Association for Computing Machinery, 미국 계산기 학회) 학회지에 짧은 글을 싣습니다(원문).
제목은 「Go To Statement Considered Harmful」. 본문 두 쪽짜리 편지였습니다.
원래 다익스트라가 붙인 제목은 「A Case Against the Go To Statement」였습니다. 지금의 도발적인 제목은 당시 편집자였던 니클라우스 비르트가 바꿔 단 것이고요.
이 제목 형식이 훗날 “○○ Considered Harmful”이라는 관용구로 굳어졌으니, 편집자의 손이 꽤 멀리 갔습니다.
주장의 핵심은 이렇습니다. 사람은 정적인 코드를 읽고 동적인 실행 과정을 머릿속에 그린다.
그런데 goto가 많은 코드에서는 “지금 이 줄을 실행 중”이라는 사실만으로 프로그램이 어떤 상태인지 설명할 수가 없다. 어디서 넘어왔는지를 모르기 때문입니다.
순차 실행, 조건 분기, 반복만 쓴 코드는 다릅니다.
실행 위치를 좌표처럼 말할 수 있습니다. “바깥 루프 세 번째, 안쪽 if의 참 가지”처럼요.
goto는 이 좌표계를 무너뜨립니다.
이 주장에는 이론적 뒷배도 있었습니다. 2년 앞선 1966년, 코라도 봄과 주세페 야코피니가 순차·선택·반복 세 가지만으로 어떤 프로그램이든 표현할 수 있다는 걸 증명했습니다(논문).
goto 없이도 가능하다는 게 수학적으로 보장된 셈이죠.
goto가 만든 그림
당시 코드가 어떤 모양이었는지 보면 감이 옵니다.
10 IF X > 100 THEN GOTO 70
20 IF X < 0 THEN GOTO 90
30 Y = X * 2
40 IF Y > 50 THEN GOTO 70
50 PRINT Y
60 GOTO 100
70 PRINT "TOO BIG"
80 GOTO 100
90 PRINT "NEGATIVE"
100 END
줄 열 개짜리인데도 흐름을 종이에 그려야 이해됩니다. 각 GOTO가 선 하나고 그 선들이 위아래로 교차합니다.
여기에 줄이 500개가 되면 어떻게 될지는 상상하기 어렵지 않죠.
실제로 1970~80년대 BASIC과 초기 포트란 코드가 그랬고 그 뭉치를 보고 나온 말이 스파게티였습니다.
goto는 사라졌는데 스파게티는 남았다
여기서 이상한 일이 벌어집니다. 요즘 코드에는 goto가 거의 없습니다.
Swift에는 아예 없죠. 있는 언어에서도 자원 정리 지점으로 되돌아오는 관용구 정도로 용도가 좁아졌습니다.
그런데 스파게티 코드라는 말은 오히려 더 자주 쓰입니다.
goto는 원인이 아니라 증상 중 하나였기 때문입니다.
진짜 문제는 읽는 사람이 흐름을 예측할 수 없다는 것이고 현대 코드에는 그걸 만드는 다른 방법이 얼마든지 있습니다.
중첩 지옥. if 안에 if, 그 안에 클로저, 그 안에 또 if.
화살표 모양으로 들여쓰기가 깊어진다고 해서 파멸의 피라미드라 부릅니다. goto처럼 튀지는 않지만 어떤 조건 조합에서 어느 줄에 도달하는지 추적하기 어려운 건 똑같습니다.
콜백 지옥. 비동기 작업을 콜백으로 이어 붙이면 실행 순서와 코드 순서가 어긋납니다.
위에서 아래로 읽는 것이 실행 순서가 아니게 되는 순간, 좌표계는 다시 무너집니다.
// 실행 순서를 코드 순서로 읽을 수 없습니다
loadUser(id) { user in
loadProfile(user) { profile in
loadPosts(profile) { posts in
DispatchQueue.main.async {
self.render(posts) // 에러 처리는 어느 층에?
}
}
}
}
전역 상태. 어떤 함수든 아무 때나 바꿀 수 있는 변수가 있으면, 그 값이 왜 그렇게 됐는지 추적하려고 프로젝트 전체를 뒤져야 합니다.
싱글톤이 자주 도마에 오르는 이유이기도 합니다.
이벤트 수프. A가 알림을 쏘면 B가 받아서 상태를 바꾸고 그걸 관찰하던 C가 또 알림을 쏩니다.
각 조각은 짧고 깔끔한데 전체 흐름은 아무 데도 적혀 있지 않습니다.
반응형 코드에서 흔히 생기는 형태고 어떤 면에서는 goto보다 추적이 어렵습니다. 점프의 목적지가 코드에 안 적혀 있으니까요.
숫자로 잴 수 있을까
“이 코드 스파게티 같은데”는 주관적인 말입니다. 그래서 1976년 토머스 매케이브가 순환 복잡도(Cyclomatic Complexity)라는 지표를 내놓습니다(논문).
계산은 단순합니다. 코드의 제어 흐름을 그래프로 그린 뒤 독립적인 경로가 몇 개인지 셉니다.
실무에서는 분기점 개수에 1을 더하는 식으로 근사하고요. if, for, while, case, &&, ?? 하나당 1씩 올라간다고 보면 얼추 맞습니다.
값이 10을 넘어가면 함수를 쪼갤 때가 됐다고 보는 관례가 있습니다. 매케이브 본인도 이 숫자를 절대 기준이 아니라 합리적인 상한 정도로 제시했고요.
실제로 switch 하나로 20가지 케이스를 나열한 함수는 복잡도가 20을 넘어가지만 읽기는 쉽습니다.
SwiftLint의 cyclomatic_complexity 규칙이 비슷한 값을 재 줍니다.
다만 SwiftLint는 if·guard·for·while·repeat·case·catch만 세고 &&나 ??는 세지 않습니다. 기본 경고 기준은 10이고요.
프로젝트에 켜 두면 조용히 자라던 함수가 어느 날 걸립니다.
크누스의 반론
이 이야기를 다익스트라가 이겼다로 끝내면 절반만 아는 겁니다.
1974년 도널드 크누스가 「Structured Programming with go to Statements」라는 긴 논문으로 반론을 폅니다(논문).
요지는 goto 자체가 악이 아니라는 것이었습니다. 중첩 루프를 한 번에 빠져나가는 경우처럼, goto를 쓰는 편이 오히려 흐름이 명확해지는 상황이 있다는 거죠.
억지로 없애려고 불린 플래그 변수를 만들고 조건문을 덧대면 그게 더 나쁜 코드가 됩니다.
지금 언어들이 내린 결론은 절충안에 가깝습니다. 무제한 점프는 없애되, 자주 쓰이는 패턴은 전용 문법으로 만들어 뒀습니다.
break, continue, 레이블을 붙인 break label, 그리고 Swift의 defer와 guard가 그렇습니다.
// guard: 예외 상황을 위에서 걷어내고 본문은 평평하게
func process(_ data: Data?) throws -> Packet {
guard let data else { throw ParseError.empty }
guard data.count > headerSize else { throw ParseError.tooShort }
// 여기부터는 조건이 전부 정리된 상태
return try decode(data)
}
guard는 C 시절 goto cleanup 패턴이 언어 기능으로 정착한 것에 가깝습니다. 점프의 목적지를 하나로 고정하고 이름을 붙인 셈이죠.
정리하면
- 스파게티 코드는 지저분한 코드가 아니라 제어 흐름을 예측할 수 없는 코드입니다.
- 1968년 다익스트라의 편지가 출발점이고 핵심 논거는 “실행 위치를 좌표로 말할 수 있어야 한다”였습니다.
goto는 사라졌지만 깊은 중첩, 콜백 중첩, 전역 상태, 이벤트 연쇄가 같은 문제를 다시 만듭니다.- 순환 복잡도로 대략 잴 수 있고 10 근처가 흔한 경고선입니다.
- 크누스의 반론대로 목적은
goto박멸이 아니라 흐름의 예측 가능성입니다.guard와async/await는 그 목적을 언어가 대신 지켜 주는 장치입니다.
다음 편에서는 정반대 방향으로 망가진 코드를 봅니다. 흐름은 아주 반듯한데, 값 하나 추가하려면 파일 일곱 개를 고쳐야 하는 코드.
라자냐 코드입니다.
API와 버전 근거는 Go To Statement Considered Harmful에서 2026-08-17에 확인했습니다.
API와 버전 근거는 Flow Diagrams, Turing Machines and Languages에서 2026-08-17에 확인했습니다.
API와 버전 근거는 A Complexity Measure에서 2026-08-17에 확인했습니다.
API와 버전 근거는 Structured Programming with go to Statements에서 2026-08-17에 확인했습니다.
출처 및 확인 기준
- Go To Statement Considered Harmful — Edsger W. Dijkstra Archive · 저자 원문 · 확인 2026-08-17 · 근거: 1968년 goto 비판과 제어 흐름 추론 문제
- Flow Diagrams, Turing Machines and Languages — Corrado Böhm·Giuseppe Jacopini · 논문 원문 · 확인 2026-08-17 · 근거: 1966년 순차·선택·반복 구조의 표현 가능성
- A Complexity Measure — Thomas J. McCabe · 논문 원문 · 확인 2026-08-17 · 근거: 1976년 순환 복잡도 정의와 제어 흐름 그래프
- Structured Programming with go to Statements — Donald E. Knuth · 논문 원문 · 확인 2026-08-17 · 근거: 1974년 구조적 프로그래밍과 goto의 제한적 역할
