Chapter 4

컴포넌트 원칙

  • 4.1 컴포넌트란?
  • 4.2 컴포넌트의 역사와 재배치 가능성
  • 4.3 REP: 재사용/릴리스 등가 원칙
  • 4.4 CCP: 공통 폐쇄 원칙
  • 4.5 CRP: 공통 재사용 원칙
  • 4.6 응집도의 균형 삼각형
  • 4.7 ADP: 비순환 의존성 원칙
  • 4.8 하향식 설계의 함정
  • 4.9 SDP: 안정된 의존성 원칙
  • 4.10 SAP: 안정된 추상화 원칙

컴포넌트는 배포 단위야. 시스템의 구성요소로 배포할 수 있는 가장 작은 단위지. 잘 설계된 컴포넌트는 독립적으로 배포 가능하고, 독립적으로 개발 가능해야 해. 그런데 어떤 클래스를 어떤 컴포넌트에 넣어야 하고, 컴포넌트 사이의 관계는 어떻게 잡아야 할까?

먼저 컴포넌트가 뭔지 정의하고 가자. 자바에서는 jar 파일, 루비에서는 gem 파일, .NET에서는 DLL이야. 컴파일형 언어에서는 바이너리 파일의 묶음, 인터프리터형 언어에서는 소스 파일의 묶음이지. 옛날에는 프로그래머가 메모리 주소를 직접 제어하고, 라이브러리 함수의 소스 코드를 직접 컴파일해서 프로그램에 포함시켰어. 메모리가 비싸던 시절이라 라이브러리를 별도 메모리 영역에 따로 컴파일해두고 참조하는 방식이 나왔는데, 프로그램이 커지면서 주소 재배치가 복잡해졌지. 이걸 해결한 게 **재배치 가능 바이너리(relocatable binary)**야. 컴파일러가 재배치 가능한 형태로 바이너리를 만들고, 링커가 엮어서 실행 파일을 만들어. 이게 발전해서 지금의 **동적 링크 라이브러리(DLL, shared library)**가 된 거야. 런타임에 플러그인 형태로 끼워넣을 수 있는 컴포넌트 -- 이 역사가 말해주는 건 컴포넌트 플러그인 아키텍처가 가능해졌다는 거야.

이제 컴포넌트에 어떤 클래스를 넣어야 하는지 -- 응집도에 대한 세 가지 원칙을 보자. REP(재사용/릴리스 등가 원칙) -- 재사용의 단위는 릴리스의 단위와 같아. 컴포넌트를 재사용하려면 버전 번호와 릴리스 노트가 있어야 하고, 하나의 컴포넌트에 묶인 클래스와 모듈은 함께 릴리스할 수 있어야 해. 관련 없는 클래스들을 억지로 묶으면 안 돼.

CCP(공통 폐쇄 원칙) -- 같은 이유로, 같은 시점에 변경되는 클래스를 같은 컴포넌트에 묶어. 이건 SRP의 컴포넌트 버전이야. 변경이 발생했을 때 영향받는 컴포넌트 수를 최소화하면 재배포도 최소화되지. CCP는 OCP와도 밀접해 -- 같은 유형의 변경에 대해 닫혀 있도록 클래스를 묶는 거야.

CRP(공통 재사용 원칙) -- 컴포넌트의 사용자들을 필요하지 않는 것에 의존하게 강요하지 마. 함께 재사용되지 않는 클래스는 같은 컴포넌트에 넣지 마. 이건 ISP의 컴포넌트 버전이야. 어떤 컴포넌트의 클래스 하나만 쓰더라도 그 컴포넌트 전체에 의존하게 되거든.

이 세 원칙은 서로 긴장 관계에 있어. REP + CCP는 컴포넌트를 크게 만드는 방향이고, CRP는 작게 만드는 방향이야. 삼각형의 꼭짓점으로 놓으면, 한 변에 가까워지면 반대편 꼭짓점의 원칙을 위반하게 돼. 프로젝트 초기에는 CCP가 REP보다 중요하고(개발 편의가 재사용성보다 중요하니까), 프로젝트가 성숙하면 REP 쪽으로 이동해서 재사용성을 높이지.

이제 컴포넌트 사이의 관계 -- 결합도에 대한 세 가지 원칙이야. ADP(비순환 의존성 원칙) -- 컴포넌트 의존성 그래프에 순환(cycle)이 있으면 안 돼. "아침 증후군"이라는 게 있어. 어제 퇴근할 때 잘 돌아가던 코드가 오늘 출근하니 안 돌아가. 누군가 밤새 내가 의존하는 코드를 바꿔놓은 거지. 컴포넌트 의존성 구조를 **DAG(방향 비순환 그래프)**로 만들어야 해. A가 B에, B가 C에, C가 다시 A에 의존하면 어디서부터 빌드해야 할지 모르게 되고, 사실상 세 컴포넌트가 하나의 거대한 컴포넌트가 돼버려. 순환을 끊는 방법은 두 가지야 -- DIP로 의존성 방향을 뒤집거나, 공통 부분을 새 컴포넌트로 추출하거나.

많은 사람이 컴포넌트 구조를 하향식으로 설계할 수 있다고 생각하는데, 저자는 이게 안 된다고 해. 컴포넌트 의존성 다이어그램은 기능의 지도가 아니라 빌드 가능성과 유지보수성의 지도야. 시스템이 성장하면서 컴포넌트 구조도 함께 진화해야 하지.

SDP(안정된 의존성 원칙) -- 안정성의 방향으로 의존해. 변경하기 어려운(안정된) 컴포넌트에 의존해야지, 변경하기 쉬운(불안정한) 컴포넌트에 의존하면 안 돼. 저자는 불안정성(Instability) 지표 I를 제시해:

I = Fan-out / (Fan-in + Fan-out)

Fan-in은 들어오는 의존성, Fan-out은 나가는 의존성이야. I = 0이면 최대한 안정, I = 1이면 최대한 불안정하지. SDP가 말하는 건 의존성 방향으로 I가 감소해야 한다는 거야.

SAP(안정된 추상화 원칙) -- 컴포넌트는 안정된 정도만큼 추상화되어야 해. 안정적인 컴포넌트는 추상적이어야 하고(인터페이스와 추상 클래스 위주), 불안정한 컴포넌트는 구체적이어야 해(구현 클래스 위주). 추상도를 y축, 불안정성을 x축으로 놓으면, 좋은 컴포넌트는 (0,1) 또는 (1,0) 근처에 있어. (0,0) 근처는 고통의 구역(Zone of Pain) -- 안정적인데 구체적이라 변경하기 힘든 곳. (1,1) 근처는 쓸모없는 구역(Zone of Uselessness) -- 추상적인데 아무도 안 쓰는 곳이야.


정리

4장 읽고 기억할 거 세 가지:

  1. 컴포넌트 응집도의 세 원칙(REP, CCP, CRP)은 긴장 관계에 있어. 포함 원칙(REP, CCP)과 배제 원칙(CRP) 사이에서 프로젝트의 현재 필요에 맞는 균형점을 찾고, 시간이 지나면서 조정해야 해.
  2. 컴포넌트 의존성 그래프에 순환이 생기면 독립적 릴리스가 불가능해져. DIP로 의존성을 뒤집거나 새 컴포넌트를 추출해서 순환을 끊어야 하지.
  3. 안정된 컴포넌트는 추상적이어야 확장이 가능해. 불안정성 지표와 추상화 정도를 조합하면 컴포넌트의 건강 상태를 정량적으로 진단할 수 있어.