2장이 “부드러움”을 감정이 아니라 기술로 다루겠다고 예고했다면, 3장은 그 기술의 가장 낮은 벽돌부터 다시 쌓는다. 무엇이 코드를 부드럽게 만드는가. 마틴의 대답은 뜻밖의 곳에서 시작한다 — 우리가 매일 쓰는 세 가지 프로그래밍 패러다임이 그렇게 만든다. 그런데 이 장이 던지는 진짜 통찰은 패러다임이 무엇을 해 주느냐가 아니라, 무엇을 못 하게 하느냐에 있다.
패러다임이라는 말은 거창하게 들리지만 실은 단순하다. 프로그래밍을 하는 방식, 어떤 구조를 쓰고 언제 그것을 쓰는지에 관한 약속이다. 우리에겐 세 개가 있다. 구조적 프로그래밍, 객체지향 프로그래밍, 함수형 프로그래밍. 놀라운 사실은 이 셋이 전부라는 것이다. 그리고 더 놀라운 사실은, 이 셋이 하나같이 우리에게서 무언가를 앗아간다는 것이다.
세 패러다임은 각자 무언가를 빼앗는다
우리는 새 도구를 만나면 대개 “이제 무엇을 더 할 수 있게 됐나”를 묻는다. 라이브러리는 능력을 더해 주고, 프레임워크는 손이 덜 가게 해 준다. 그래서 패러다임도 그런 것이라 짐작하기 쉽다 — 객체지향은 상속과 다형성이라는 새 무기를 쥐여 주고, 함수형은 고차 함수라는 새 재주를 얹어 주는 것이라고. 마틴은 이 직관을 정면으로 뒤집는다. 세 패러다임은 어느 것도 새 능력을 더해 주지 않는다. 셋 다 정반대로, 프로그래머가 하던 어떤 일을 앗아간다.
구조적 프로그래밍은 goto를 빼앗는다. 제어 흐름을 아무 데로나 점프시키던 자유를 몰수하고, 순차·분기·반복이라는 세 구조 안에 가둔다. 객체지향 프로그래밍은 함수 포인터를 빼앗는다 — 정확히는 그 위험한 직접 사용을 다형성이라는 규율로 대체한다. 함수형 프로그래밍은 할당(assignment)을 빼앗는다. 한 번 값을 정한 변수를 다시 바꾸지 못하게 막는다.
세 이야기의 구조가 똑같다는 데 주목해야 한다. 각 패러다임은 우리에게서 하나씩을 거둬 가되, 그 대가로 아무것도 손에 쥐여 주지 않는다. goto가 사라진 자리에 새 문법이 들어오는 게 아니라 그냥 goto가 없어질 뿐이다. 그래서 패러다임의 역사는 능력의 확장사가 아니라 규율의 부과사다. 우리는 점점 더 많은 것을 할 수 있게 된 게 아니라, 점점 더 하지 말아야 할 것을 배워 온 것이다.
판단 기준: 어떤 규칙이나 패러다임을 도입할지 저울질할 때, “이게 무엇을 가능하게 하나”보다 “이게 무엇을 못 하게 막아 주나”를 먼저 물어라 — 좋은 제약은 실수의 여지를 줄여 준다. 함정: 제약을 손실로만 읽으면 “왜 굳이 손발을 묶느냐”는 반발에 걸린다. 빼앗김이 곧 안전이라는 역설을 놓치면, 규율을 자유의 훼손으로 오해하게 된다.
왜 빼앗는 것이 이득인가
능력을 잃는 것이 어떻게 진보일 수 있나. 이 물음에 답하려면 우리가 무엇을 두려워하는지를 봐야 한다. 프로그래밍에서 가장 값비싼 것은 못 하는 일이 아니라 잘못 하는 일이다. goto로 아무 데나 뛸 수 있다는 것은, 아무 데나 잘못 뛴 코드를 사람이 도저히 따라 읽을 수 없게 된다는 뜻이기도 하다. 함수 포인터를 손으로 다룰 수 있다는 것은, 초기화되지 않은 포인터 하나로 시스템 전체를 예측 불가능하게 만들 수 있다는 뜻이다. 아무 때나 변수를 다시 쓸 수 있다는 것은, 두 실행 흐름이 같은 변수를 동시에 건드려 경쟁 상태에 빠질 수 있다는 뜻이다.
패러다임이 빼앗는 것은 하나같이 이런 위험의 원천이다. 그러니 그 능력을 몰수한다는 것은 곧 그 능력이 부르던 재앙 한 부류를 통째로 없앤다는 것이다. 우리는 자유를 조금 내주고, 그 대가로 특정 종류의 버그가 원천적으로 불가능해지는 세계를 산다. 이것은 손해가 아니라 거래다 — 2장의 어휘로 말하면, 무엇을 지불하고 무엇을 사는가의 문제다. 여기서 지불하는 것은 표현의 자유이고, 사는 것은 예측 가능성이다.
그래서 이 셋은 서로 직교한다. goto를 못 쓰게 하는 규율, 함수 포인터를 규율로 감싸는 것, 할당을 막는 것은 서로 다른 위험을 다스리므로 겹치지 않는다. 셋을 한꺼번에 받아들여도 충돌하지 않고, 각자가 각자의 재앙을 봉인한다. 한 언어가 세 패러다임을 모두 품을 수 있는 것도 이 때문이다.
판단 기준: 팀에 규칙을 세울 때, 그 규칙이 봉인하는 “구체적 사고(事故)“를 하나 이상 이름 댈 수 있어야 한다 — 봉인 대상이 분명한 제약만이 값을 한다. 함정: 봉인하는 사고를 대지 못하면서 “관례니까” 도입하는 규칙은 자유만 깎아먹는 순수 비용이다. 모든 제약이 미덕인 건 아니다. 값을 하는 제약은 반드시 무언가를 대신 막아 준다.
발견이지 발명이 아니다
이 세 패러다임에는 공통점이 하나 더 있다. 전부 놀랄 만큼 이른 시기에, 그것도 좁은 창 안에서 나타났다는 것이다. 함수형의 뿌리인 람다 계산법은 1930년대에 이미 있었고, 세 패러다임이 프로그래밍의 형태로 자리 잡은 것은 대략 1958년에서 1968년 사이의 10년이다. 그리고 마틴은 못을 박는다 — 그 이후로 새로운 패러다임은 하나도 나오지 않았다.
반세기가 넘도록 언어는 수없이 태어나고 죽었는데 패러다임은 셋에서 멈췄다는 사실은 무엇을 뜻하나. 이것들이 누군가의 취향이나 유행으로 발명된 게 아니라, 계산이라는 활동에 원래부터 있던 경계를 사람이 뒤늦게 발견한 것에 가깝다는 뜻이다. 순차·분기·반복이 제어 흐름의 전부라는 것, 함수 포인터의 규율이 다형성이라는 것, 부수효과의 근원이 할당이라는 것 — 이것들은 발명품이라기보다 수학적 사실에 가깝다. 그래서 셋으로 닫혔고, 넷째가 오지 않는다.
여기에는 실천적 함의가 있다. 다음 은총알을 기다리며 네 번째 패러다임을 찾아 헤맬 이유가 없다는 것이다. 판을 뒤집을 새 패러다임은 오지 않을 가능성이 높다. 우리가 할 일은 새것을 기다리는 게 아니라, 이미 손에 쥔 세 개의 규율을 언제 어디에 쓸지를 아는 것이다.
판단 기준: “이걸 근본부터 바꿔 줄 새 패러다임이 곧 나올 것”이라는 기대가 판단에 끼어들면, 그 기대를 반세기의 정체(停滯)라는 사실 앞에 세워 검증하라. 함정: 새 언어나 프레임워크의 등장을 새 패러다임의 도래로 착각하기 쉽다. 문법이 달라진 것과 계산을 규율하는 방식이 달라진 것은 층위가 다르다 — 대부분의 “혁신”은 셋 중 하나를 새 옷으로 갈아입힌 것에 지나지 않는다.
세 규율이 곧 아키텍처의 세 관심사다
여기까지는 언어와 문법의 이야기처럼 들린다. 이것이 어떻게 아키텍처와 이어지나. 마틴이 이 장을 책의 초입에 놓은 진짜 이유가 여기 있다. 세 패러다임이 다스리는 세 가지 위험이, 놀랍게도 아키텍처가 다루는 세 가지 관심사와 정확히 포개진다.
함수형 프로그래밍이 다스리는 것은 할당 — 즉 데이터의 위치와 접근이다. 이것은 아키텍처에서 동시성과 상태 관리, 데이터를 어디에 어떻게 두느냐의 문제로 자란다. 객체지향이 다스리는 것은 함수 포인터 — 즉 제어 흐름의 방향이다. 이것은 아키텍처에서 컴포넌트 사이 의존성을 어느 쪽으로 흐르게 할 것인가, 경계를 어떻게 넘을 것인가의 문제로 자란다. 구조적 프로그래밍이 다스리는 것은 분기와 점프 — 즉 함수 하나의 내부 논리다. 이것은 아키텍처에서 각 모듈을 어떻게 검증 가능한 단위로 쪼갤 것인가의 토대가 된다.
그러니 세 패러다임은 저마다 다른 층위에서 같은 일을 한다 — 무절제한 자유를 몰수하고 그 자리에 규율을 세운다. 함수의 알고리즘 수준에서(구조적), 컴포넌트를 잇는 수준에서(객체지향), 데이터를 다루는 수준에서(함수형). 이 책의 나머지가 다룰 SOLID 원칙도, 컴포넌트 원칙도, 아키텍처 경계도 결국 이 세 규율의 확장이다. 아키텍처란 큰 규모에서 되풀이되는, 무언가를 못 하게 막는 기술인 것이다.
판단 기준: 아키텍처 결정을 내릴 때 그 결정이 세 관심사 중 어느 것을 다스리는지 짚어라 — 데이터 접근인가(함수형 계열), 의존성 방향인가(객체지향 계열), 모듈 내부 논리인가(구조적 계열). 층위를 알면 어떤 규율을 꺼내 쓸지가 정해진다. 함정: 세 관심사를 한 덩어리로 뭉뚱그리면 엉뚱한 도구를 든다. 동시성 문제를 의존성 역전으로 풀려 하거나 의존성 문제를 불변성으로 풀려 하는 식의 헛발질은, 어느 층위의 위험인지를 먼저 가르지 않은 데서 온다.
자유의 반납으로 지은 집
이 장의 결론은 담백하지만 방향을 뒤집는다. 좋은 소프트웨어는 프로그래머가 할 수 있는 일을 늘려서 만들어지지 않는다. 할 수 있는 일 중 해서는 안 될 것을 스스로 반납해서 만들어진다. goto를 반납하고, 함수 포인터의 무분별한 사용을 반납하고, 아무 때나 값을 갈아치우는 자유를 반납한다. 그렇게 반납한 자유의 총합 위에, 사람이 읽고 따라갈 수 있고 안전하게 변경할 수 있는 코드가 선다.
2장에서 소프트웨어의 값어치는 변경을 값싸게 흡수하는 구조에 있다고 했다. 이제 그 구조가 무엇으로 지어지는지가 드러난다 — 규율로, 곧 절제된 자유로 지어진다. 무엇이든 할 수 있는 코드는 무엇이든 잘못될 수 있는 코드이고, 잘못될 여지가 많은 코드는 결코 부드러울 수 없다. 부드러움은 방종이 아니라 규율에서 온다.
- 구조적·객체지향·함수형 세 패러다임은 능력을 더해 주지 않고
goto·함수 포인터·할당을 하나씩 빼앗는다 — 프로그래밍의 역사는 규율의 부과사다. - 빼앗김은 손실이 아니라 거래다. 표현의 자유를 내주고 특정 부류의 재앙(추적 불가능한 흐름·포인터 오류·경쟁 상태)이 원천 봉인된 예측 가능성을 산다.
- 세 패러다임은 1958~1968의 좁은 창에서 나타났고 이후 넷째는 없다 — 발명이 아니라 발견에 가깝고, 새 패러다임을 기다릴 이유는 없다.
- 세 규율은 아키텍처의 세 관심사(데이터 접근·의존성 방향·모듈 내부 논리)와 포개진다. SOLID도 컴포넌트 원칙도 경계도 결국 이 세 규율의 확장이다.
다음 장부터 이 책은 세 패러다임을 하나씩 열어 각자가 무엇을 빼앗고 그 대가로 아키텍트에게 무엇을 쥐여 주는지를 따진다. 첫 문을 여는 것은 가장 오래되고 가장 근본적인 규율, 구조적 프로그래밍이다.
다음장으로 4장