
Uncle Bob, 로버트 C. 마틴의 책이다. 50년이 넘게 프로그래머로 일한 사람이 소프트웨어 설계에 대해 쓴 책이다. 개발 서적 중 바이블로 불리는 이유가 있다는 걸 읽으면서 느꼈다.
왜 구조가 중요한가
책은 생산성이 시간이 갈수록 0에 수렴하는 그래프로 시작한다. 기능을 추가할 때마다 드는 시간이 늘어나다가, 결국 거의 아무것도 못 만드는 상태가 된다. 코드가 돌아가는 동안 개발자들은 "다음에 정리하면 된다"고 생각하지만 그 다음은 오지 않는다.
저자는 아키텍처의 목표를 이렇게 정의한다.
"소프트웨어 아키텍처의 목표는 필요한 시스템을 만들고 유지보수하는 데 투입되는 인력을 최소화하는 데 있다."
소프트웨어는 행위(지금 원하는 대로 동작하는가)와 구조(나중에 바꾸기 얼마나 쉬운가) 두 가지 가치를 갖는다. 저자의 주장은 구조가 더 중요하다는 것이다. 잘 동작하지만 변경 불가능한 소프트웨어보다, 지금 동작하지 않아도 변경 가능한 소프트웨어가 더 가치 있다. 기능은 나중에 추가할 수 있지만, 망가진 구조는 회복하기 어렵다.
대학 프로젝트 할 때 이 그래프를 체감했다. 초반엔 기능이 빠르게 붙는데 어느 순간부터 뭘 하나 건드리면 다른 데서 터진다. "일단 돌아가게 만들자"의 누적이 결국 코드를 아무도 건드리기 싫은 상태로 만든다. 그때 아키텍처를 알았다면 달랐을 것 같다.
패러다임: 소프트웨어는 무언가를 제한해 왔다
프로그래밍 패러다임 세 가지(구조적, 객체지향, 함수형)를 다루면서, 저자는 각각이 "무엇을 추가했는가"가 아니라 "무엇을 제한했는가"의 관점으로 설명한다.
구조적 프로그래밍은 goto를 없앴다. 객체지향은 함수 포인터의 직접 사용을 제한하고 다형성을 안전하게 줬다. 함수형은 변수 변경 자체를 제한한다.
이 세 패러다임이 뒤에 나오는 아키텍처 원칙과 어떻게 연결되는지가 이 책의 설계다. 구조적 → 함수와 모듈 단위 분해. 객체지향 → 다형성으로 의존성 방향을 뒤집는다. 함수형 → 상태를 격리해 컴포넌트 간 의도치 않은 의존을 끊는다. 패러다임 파트를 읽을 때는 몰랐는데 아키텍처 파트를 읽으면서 "아, 이래서 앞에서 그 이야기를 한 거구나"가 됐다.
SOLID: 클래스 수준의 설계 원칙
SOLID 다섯 원칙(SRP, OCP, LSP, ISP, DIP)이 나온다. 각 원칙을 이미 알고 있었는데, 이 책에서 다르게 읽힌 건 원칙들 간의 연결 때문이다.
특히 DIP(의존성 역전 원칙)가 이 책 전체의 뼈대가 된다는 걸 느꼈다. 중요한 건 단순히 "방향을 바꾸자"가 아니다. 런타임 제어 흐름과 소스코드 의존 방향을 분리할 수 있다는 것이다. 다형성을 쓰면 실행 중에는 A가 B를 호출하는데, 소스코드상으로는 B가 A를 의존하게 만들 수 있다. 이 메커니즘이 "엔티티가 데이터베이스를 모른다"는 구조를 가능하게 한다.
컴포넌트: 같은 원칙이 스케일만 바뀐다
4부는 컴포넌트, 배포 가능한 독립 단위를 다룬다. SOLID 원칙이 클래스 수준에서 컴포넌트 수준으로 확장된다.
응집도 원칙 세 가지(REP, CCP, CRP)는 긴장 삼각형 구조를 이룬다. 함께 배포되는 것은 함께 묶고(REP), 같은 이유로 같은 시점에 변하는 것은 함께 묶고(CCP), 함께 쓰이는 것만 같이 있어야 한다(CRP). 세 원칙은 동시에 만족시킬 수 없다. CCP와 CRP는 반대 방향으로 당기고, 어디에 무게를 둘지는 프로젝트 성숙도와 팀 상황에 따라 달라진다.
결합도에서는 순환 의존성 제거가 핵심이다. A→B→C→A처럼 순환이 생기면 셋 중 하나를 바꿀 때 셋 다 빌드해야 한다. 의존성 역전으로 사이클을 끊는다.
아키텍처: 어디에 경계를 그을 것인가
5부가 이 책의 핵심이다.
저자는 Screaming Architecture를 이야기한다. 좋은 아키텍처는 그 시스템이 무엇을 하는지 소리쳐야 한다. 프로젝트 구조를 펼쳤을 때 "이건 Rails 앱이야"가 아니라 "이건 헬스케어 시스템이야"가 보여야 한다는 것이다. 프레임워크가 아키텍처를 결정하는 순간, 도메인이 프레임워크의 관례 안에 갇히고 테스트도 프레임워크에 종속된다.
의존성 규칙은 이를 구조로 만든다. 소스코드 의존성은 반드시 안쪽을 향해야 한다. 엔티티(가장 안쪽, 전사적 비즈니스 규칙) → 유스케이스(애플리케이션 특화 규칙) → 인터페이스 어댑터 → 프레임워크/드라이버(가장 바깥쪽). 바깥이 안을 알 수 있지만, 안은 바깥을 몰라야 한다.
경계(Boundary) 논의도 비중 있게 다뤄진다. 어디에 경계를 그을지, 비용을 얼마나 들여서 경계를 만들지는 아키텍처에서 가장 어려운 판단이다. 경계를 만들면 변경을 격리할 수 있지만 그 자체로 비용이 든다. 일급 경계가 필요하지 않은 상황에서는 부분적 경계(인터페이스만 두거나, 단방향 경계만 두거나)로 비용을 줄이는 방법도 제시한다. 지금 당장 완전한 경계가 아니라도, 나중에 그을 수 있도록 준비해두는 것이 핵심이다.
Humble Object 패턴은 경계에서 테스트 가능성을 확보하는 방법이다. View는 테스트하기 어렵다. 포맷팅 로직을 View에서 Presenter로 분리하면, Presenter는 테스트할 수 있고 View는 화면에 뿌리는 것만 남는다. 데이터베이스 게이트웨이도 SQL과 도메인 매핑을 분리하면 같은 효과가 난다. 테스트 가능성이 설계의 기준이 된다는 관점이 인상적이었다.
아키텍트는 반드시 코드를 직접 다뤄야 한다는 주장도 여기 있다. 설계와 구현 사이에는 피드백 루프가 있어야 하는데, 아키텍트가 코드를 짜지 않으면 그 루프가 끊긴다.
세부사항: 결정을 미뤄라
데이터베이스는 세부사항이다. 데이터베이스는 결국 데이터를 담는 I/O 장치일 뿐이고, 비즈니스 규칙은 어떤 방식으로 데이터를 영속화하는지 알 필요가 없다. 웹도 세부사항이다. HTTP 요청 구조가 유스케이스에 영향을 줘서는 안 된다. 프레임워크도 세부사항이다.
이 주장의 실용적 의미는 결정을 미룰 수 있다는 것이다. 데이터베이스를 확정하지 않아도 유스케이스를 먼저 만들고 테스트할 수 있다. 좋은 아키텍처는 가능한 한 많은 선택지를 가능한 한 오래 열어둔다.
읽고 나서
이 책을 읽기 전까지 아키텍처는 "큰 회사가 큰 시스템 만들 때 하는 것"처럼 느껴졌다. 읽고 나니 아키텍처는 규모의 문제가 아니라 변경 비용의 문제라는 게 보인다. 코드 한 줄을 짤 때도 "이 의존성이 옳은 방향인가"를 묻는 것이 아키텍처다.
당장 내 코드에서 바꿔보고 싶은 건 의존성 방향이다. 유스케이스가 데이터베이스 코드를 직접 부르고 있다면 그건 잘못된 방향이다. 인터페이스를 두고 방향을 뒤집는 것부터 해볼 수 있다. 작게라도.
e-book으로 읽었는데 집중이 잘 안 됐다. 다이어그램이 많은 책이라 작은 화면에서 흐름 따라가기가 쉽지 않았다. 다음엔 종이책으로 사서 다시 읽을 생각이다.