모던지 / 컴퓨터과학 / 프로그래밍의 세계 3강. 복잡함을 다루는 유일한 방법

프로그래밍의 세계 3강. 복잡함을 다루는 유일한 방법

사람의 머리는 한 번에 몇 개밖에 붙들지 못합니다. 그래서 프로그래머는 복잡함을 없애는 대신 한 번에 볼 양을 줄이는 기술을 발명했습니다.

풀고 시작

문제 1. 좋은 모듈 경계를 정하는 기준으로 데이비드 파르나스가 제안한 것은 무엇일까요?
파르나스의 정보 은닉 원칙은 처리 순서가 아니라 변경 가능성을 기준으로 쪼개라는 것입니다. 바뀔 결정이 한 모듈 안에 갇혀 있으면 그 결정이 실제로 바뀔 때 고칠 곳도 한 군데뿐이죠. 순서대로 나누면 요구가 바뀔 때마다 여러 모듈이 동시에 흔들리고, 줄 수나 호출 빈도는 경계와 무관한 기준입니다.

없애는 것이 아니라 옮기는 것

사람의 작업 기억은 놀라울 만큼 좁습니다. 한 번에 몇 개의 항목까지 다룰 수 있는가에 대한 정확한 숫자는 과제에 따라 다르게 나오지만, 용량이 매우 제한적이라는 사실 자체는 확립되어 있습니다. 코드는 그런 사정을 봐주지 않습니다. 수십만 줄짜리 시스템 앞에서 프로그래머가 할 수 있는 일은 한 가지뿐입니다. 한 번에 봐야 할 덩어리 수를 줄이는 것이죠. 추상화(abstraction)는 여러 세부를 하나의 이름 아래 묶어서, 그 이름만 알면 안을 몰라도 쓸 수 있게 만드는 작업입니다. 여기서 오해가 자주 생깁니다. 추상화는 복잡함을 없애지 않습니다. 세부는 그대로 남아 있고 다만 이름 뒤로 옮겨졌을 뿐입니다. 다익스트라는 추상화의 목적이 모호하게 만드는 것이 아니라, 그 안에서는 완전히 정확할 수 있는 새로운 의미의 층을 만드는 것이라고 못 박았습니다. 정렬 함수를 부를 때 우리는 비교 횟수를 잊지만, 그 함수가 정렬을 정확히 해낸다는 사실은 잊지 않습니다.

무엇을 하는지와 어떻게 하는지

추상화가 실제로 작동하려면 약속과 구현이 갈라져야 합니다. 인터페이스는 무엇을 해 주는지를 선언하고, 구현은 그것을 어떻게 해내는지를 담습니다. 1972년 데이비드 파르나스는 시스템을 모듈로 쪼개는 기준에 관한 논문에서 이 원칙을 정보 은닉(information hiding)이라는 이름으로 정리했습니다. 순서대로 자르지 말고 바뀔 만한 결정을 기준으로 자르라는 것이었죠. 사용자 정보를 파일에 저장하던 프로그램을 데이터베이스로 옮길 때 호출하는 쪽 코드가 한 줄도 바뀌지 않는다면, 저장 방식이라는 결정이 저장소 모듈 안에 제대로 갇혀 있었다는 뜻입니다. 반대로 화면을 그리는 코드가 파일 형식을 알고 있다면, 저장 방식을 바꾸는 순간 화면 코드까지 함께 무너집니다. 좋은 인터페이스는 적게 약속하고 그 약속을 지킵니다.

결합도와 응집도, 그리고 화살표의 방향

모듈을 잘 나눴는지 재는 오래된 잣대가 두 가지 있습니다. 결합도(coupling)는 모듈들이 서로에게 얼마나 기대고 있는지를, 응집도(cohesion)는 한 모듈 안의 요소들이 얼마나 한 가지 목적을 향하는지를 가리킵니다. 목표는 결합도를 낮추고 응집도를 높이는 것인데, 이유는 미학이 아니라 수리 비용입니다. 결합도가 높으면 한 곳을 고칠 때 파급 범위가 넓어져 무엇이 깨질지 예측할 수 없고, 응집도가 낮으면 한 가지 기능을 고치려고 여러 파일을 돌아다녀야 합니다. 여기에 방향이라는 조건이 붙습니다. 레이어를 나눌 때는 위층이 아래층을 부르는 단방향을 지켜야 하고, 화살표가 순환하면 어느 쪽도 혼자 이해하거나 따로 테스트할 수 없게 됩니다. 그런데 정책이 세부에 매달리면 곤란하죠. 그래서 상위 층이 필요한 기능을 인터페이스로 선언하고 하위 층이 그것을 구현하게 해서 화살표를 뒤집는 기법을 씁니다. 의존성 역전이라 부르는 이 수법 덕분에 데이터베이스를 바꿔도 업무 규칙은 그대로 남습니다.

모든 추상화는 조금씩 샙니다

여기서 불편한 진실을 하나 봐야 합니다. 조엘 스폴스키는 사소하지 않은 추상화는 어느 정도 샌다고 정리했고, 이것을 누수 추상화(leaky abstraction)라고 부릅니다. 네트워크의 TCP는 데이터를 순서대로 빠짐없이 전달하겠다고 약속하지만, 실제로는 패킷이 사라질 수 있는 회선 위에서 재전송으로 그 약속을 흉내 냅니다. 케이블이 뽑히면 약속은 깨지고, 뽑히지 않아도 재전송 때문에 응답이 갑자기 느려지는 순간 아래층의 사정이 위로 새어 나옵니다. 데이터베이스 접근을 객체처럼 감싸 주는 도구도 마찬가지여서, 잘 돌아가다가 성능 문제가 터지면 결국 그 아래에서 어떤 질의가 나가는지 봐야 합니다. 그렇다고 추상화가 실패라는 뜻은 아닙니다. 추상화는 평소에 배워야 할 양을 크게 줄여 주고, 다만 문제가 생겼을 때 한 층 아래를 볼 수 있어야 한다는 조건이 붙을 뿐입니다. 새지 않는 추상화를 찾는 대신 어디서 새는지 아는 편이 현실적입니다.

이름을 못 짓는다는 신호

넷스케이프의 필 칼튼이 남긴 농담이 있습니다. 컴퓨터과학에는 어려운 문제가 두 개 있는데 캐시 무효화와 이름 짓기라는 것이죠. 농담처럼 들리지만 이유는 진지합니다. 이름을 붙인다는 것은 그 덩어리가 무엇이고 무엇이 아닌지 경계를 선언하는 일이고, 그 경계가 곧 추상화이기 때문입니다. 그래서 어떤 함수나 모듈에 적당한 이름이 도무지 떠오르지 않는다면, 대개 작명 실력이 부족한 것이 아니라 그 안에 서로 다른 책임이 섞여 있다는 신호입니다. 이름이 어색하게 길어지거나 그리고라는 연결어가 들어간다면 경계를 다시 그으라는 뜻이죠. 반대로 정확한 이름을 찾은 순간 코드가 저절로 정리되는 경험은 프로그래머라면 대부분 해 봤을 것입니다. 다음 강에서는 이렇게 나눈 구조를 여러 사람이 오랜 시간 함께 고쳐 나갈 때 생기는 문제들, 즉 소프트웨어 공학의 영역으로 넘어갑니다.

인출 문제

문제 1. 추상화가 인지 부하를 줄이는 방식에 대한 설명으로 옳은 것은 무엇일까요?
추상화는 세부를 없애는 것이 아니라 이름 아래로 옮기는 일이며, 덕분에 한 번에 다룰 항목 수가 줄어듭니다. 다익스트라가 강조했듯 목적은 모호해지는 것이 아니라 그 층에서는 정확하게 말할 수 있는 새 의미 수준을 만드는 것입니다.
문제 2. 결합도와 응집도를 낮추고 높이려는 실질적 이유는 무엇일까요?
두 지표는 미학이 아니라 수리 비용에 대한 잣대입니다. 결합이 느슨하면 한 곳을 고쳐도 파급이 좁고, 응집이 높으면 한 기능을 고치러 여러 파일을 돌아다니지 않아도 되죠. 메모리나 실행 속도와는 직접적인 관계가 없습니다.
문제 3. 누수 추상화 개념이 말하려는 바는 무엇일까요?
TCP가 순서대로 빠짐없는 전달을 약속해도 회선이 끊기거나 재전송으로 느려지면 아래층 사정이 드러나듯, 사소하지 않은 추상화는 어느 정도 샙니다. 그렇다고 추상화가 쓸모없다는 뜻은 아니며, 평소 학습량을 줄여 주되 문제 상황에서는 한 층 아래를 볼 수 있어야 한다는 조건이 붙는 것입니다.
문제 4. 함수나 모듈에 알맞은 이름이 도무지 떠오르지 않는다면 그것은 무엇의 신호일까요?
이름을 붙이는 일은 그 덩어리가 무엇이고 무엇이 아닌지 경계를 선언하는 일이라서, 이름이 어색하게 길어지거나 그리고라는 연결어가 끼어들면 대개 작명 실력이 아니라 책임이 섞인 구조가 문제입니다. 주석이나 중복은 별개의 사안이고, 식별자 규칙은 이름 짓기의 어려움과 관계가 없습니다.

생각해볼 질문 (정답 없음)

  • 아래층을 몰라도 쓸 수 있게 만드는 것이 추상화의 목적인데, 문제가 생기면 아래층을 알아야 한다면, 우리는 도대체 무엇을 절약한 것일까요.

이전: 프로그래밍의 세계 2강 · 다음: 프로그래밍의 세계 4강

모던지 · 궁금하면 모던지 GitHub · 2026-09-10