SOLID 원칙이 벽과 방에 벽동을 배치하는 방법이라면 컴포넌트 원칙은 빌딩에 방을 배치하는 방법을 배운다.
컴포넌트는 배포 단위다. 자바의 경우 jar 파일이 컴포넌트다. 여러 컴포넌트를 서로 묶어 .war 같은 단일 아카이브로 만들 수 있다.
잘만든 컴포넌트라면 반드시 독립적으로 배포-개발 ㅈ가능해야한다
초창기에는 메모리에서 프로그램의 위치와 레이아웃을 개발자가 직접 제어했다. 이는 매우 낯설며 요즘 개발자들은 이를 고민할 필요가 없다. 이 시절에는 프로그램의 위치가 한번 결정되면 재배치가 불가능했다.
라이브러리 함수에 접근하려면 소스 코드를 직접 애플리케이션 코드에 포함시켜 단일 프로그램을 컴파일했다. 당시의 열악한 하드웨어 환경에서 해당 작업들은 매우 비싼 작업이었고 결국 컴파일러는 느린 장치를 이용해 소스 코드를 여러번 읽어야했다.
시간을 단축시키기 위해 애플리케이션 코드와 분리하여 개별적으로 메모리에 위치시켰다. 하지만 문제가 생겼다. 애플리케이션이 메모리 1~1000을 차지하고 라이브러리 함수가 1000~1500을 차지하고 있다고 가정해보자. 개발이 지속됨에 따라 애플리케이션의 크기가 커졌고 1500부터 시작하는 새로운 세그먼트를 분리하여 사용해야했다. 이는 당연히 지속 가능하지 않은 상황이며 무언가 조치가 필요하였다.
해결책은 재배치가 가능한 바이너리였다. 메모리에 재배치할 수 있는 형태의 바이너리를 생성하도록 컴파일러를 수정하자는 것이었다. 로더는 재배치 코드가 자리할 위치 정보를 전달받고 재배치 코드에는 로드한 데이터에서 어느 부분을 수정해야 정해진 주소에 로드할 수 있는지를 알려주는 플래그가 삽입되었다.
로더는 여러 개의 바이너리를 입력받은 후 단순히 하나씩 차례로 메모리로 로드하면서 재배치하는 작업을 처리했다. 이를 통해 오직 필요한 함수만을 로드할 수 있게 되었다. 또한 컴파일러는 바이너리 안의 함수 이름을 메타데이터 형태로 생성하도록 수정되었다.
-> 링킹 로더의 탄생
링킹 로더의 등장으로 개별적으로 컴파일고 로드할 수 있는 단위로 분리할 수 있게 됐다. 하지만 프로그램이 커지면서 너무 느려 참을 수 없는 지경까지 다다랐다. 마침내 로드와 링크가 두 단계로 분리되었다. 하지만 이 또한 시간이 지남에 따라 매우 느려지게 되었다.
머피의 법칙
컴파일하고 링크하는데 사용 가능한 시간을 모두 소모할 때까지 프로그램은 커진다.
무어의 법칙
컴퓨터 속도, 메모리, 집적도가 매 18개월마다 두 배로 증가한다.
승자는 무어였다. 1990년대 후반이 되자 개발자가 프로그램을 성장시키는 속도보다 링크 시간이 줄어드는 속도가 더 빨라지기 시작했다. 이렇게 공유 라이브러리 시대 열렸고 컴포넌트 플러그인 아키텍처가 탄생했다.
어떤 클래스를 어느 컴포넌트에 포함시켜야 할까? 이는 매우 중요한 사안으로 제대로 된 아키텍처를 위해선 원칙의 도움을 받아야 한다. 이 장에서는 세 가지 원칙에 대해 논의한다.
재사용 단위는 릴리즈 단위와 같다.
릴리즈 번호를 통해 재사용 컴포넌트들이 서로 호환되는지 보증한다. 개발자들은 이를 확인하여 새로운 릴리즈 소식을 접하면 업데이트 여부를 결정한다. 이러한 현상들은 객체 지향 모델의 오랜 약속 중 하나가 실현된 모습들이다.
하지만 위와 같은 조언은 매우 약하다. 어떤 클래스와 모듈을 묶어 컴포넌트로 만드는지에 대해 설며애주지 않기 때문이다.
동일한 이유로 동일한 시점에 변경되는 클래스를 같은 컴포넌트로 묶어라.
== 서로 다른 시점에 다른 이유로 변경되는 클래스는 다른 컴포넌트로 분리하라.
SRP를 컴포넌트 관점에서 다시 쓴 것이다. 즉, 변경의 이유가 여러개면 안된다는 뜻이며 마찬가지로 컴포넌트도 변경의 이유가 한 가지여야 한다.
대다수는 유지보수성이 재사용성보다 중요하다. 변경이 여러 곳에 분산되어 있기보단 단일 컴포넌트에서만 변경하여 재배포하는 것이 작업량을 최소화할 수 있다. 또한 이를 통해 OCP를 확대 적용 할 수 있다.
컴포넌트 사용자들을 필요하지 않은 것에 의존하게 강요하지 말라.
이는 클래스와 모듈을 어느 컴포넌트에 배치할지 결정하는데 도움이 되는 원칙이다. CRP는 같이 재사용되는 클래스와 모듈들을 한 컴포넌트에 포함해야 한다고 말한다.
사용하는 컴포넌트가 사용되는 컴포넌트에서 단 하나의 클래스만 이용한다고 해보자. 그렇다고 해도 의존성이 약해지진 않는다. 사용되는 컴포넌트가 변경될 때마다 사용하는 컴포넌트도 변경될 가능성이 높다.
따라서, 의존하는 컴포넌트가 있다면 해당 컴포넌트의 모든 클래스에 대해 의존함을 확실히 인지해야 한다. 이는 곧 서로 강하게 엮여있지 않은 클래스들을 동일한 컴포넌트에 위치시키면 안 된다는 뜻이다.
세 원칙들은 서로 상충된다. REP와 CCP는 포함 원칙이며 컴포넌트를 크게 만든다. CRP는 배제 원칙이며 더 작게 만든다. 이 셋의 균형을 찾아야 한다.
프로젝트 시작에서는 삼각형의 오른쪽에서 시작한다. 점점 성숙해질수록 점차 왼족으로 이동한다. 즉, 항상 요구에 맞게 균형을 잡는 것이 중요하다. 지금은 잘 분배했더라도 내년이 되면 맞지 않을 수 있다. 그에 맞게 컴포넌트 구성을 바꾸고 진화시켜야 한다.
컴포넌트 의존성 그래프에 순환이 있어서는 안 된다.
컴포넌트끼리 순환이 발생하면 다른 컴포넌트의 수정으로 본인의 컴포넌트가 정상적으로 작동되지 않을 수 있다. 이를 해결하기 위해선 두 가지 선택이 존재하는데, 첫째는 주 단위 빌드이고 두 번째는 의존성 비순환 원칙이다.
주 단위 빌드는 금요일에 통합을 시도하는 것이다. 하지만 프로젝트가 점점 커질수록 하루만에 통합을 완료하긴 쉽지 않다. 통합에 드는 시간이 늘어날 수록 효율성이 떨어지고 빌드 일정을 계속 늘려야 한다. 무언가 대책이 필요하다.
순환성 의존 제거를 통해 릴리즈 가능한 컴포넌트 단위의 분리를 보장하는 것이다. 이를 통해, 개별 개발자 또는 개발팀이 책임질 수 있는 한 단위가 된다. 그러다면 어떻게 설계해야 할까? 바로 의존성 관계 그래프에서 어느 컴포넌트에서 시작하더라도 최초의 컴포넌트로 돌아갈 수 없도록 하는 것이다. 이 구조를 비순환 방향 크래프라고 하며 순환이 없다.
하지만 순환이 깨지는 경우가 있을 수 있다. 아래 그림을 참고해보자.
Entities의 User가 Authorizer의 Permission 클래스를 참조한다고 해보자. 바로 순환이 발생하고 즉각적인 문제를 일으킨다. Database 컴포넌트 개발자는 이젠 Authorizer와 Interactors도 호환이 되어야 하며 이로 인해 Entities, Authorizer, Interactors가 하나의 큰 컴포넌트가 되어버렸다.
두 가지 방법이 있다.
하지만, 두 번째 해결책은 컴포넌트 구조가 변경된다.
더 안정된 쪽으로 의존하라.
당연한 말이다. 변경은 불가피하지만 우린 이를 최소화하려고 한다. 그러기 위해선 안정된 컴포넌트에 의존하는 것이 변경의 규모를 줄일 수 있을 것이다. 하지만 어떻게 안정성을 판단할 수 있을까??
컴포넌트가 위치상 어느 정도의 안정성을 가지는지 계산을 통해 알 수 있다.
하지만, 모든 컴포넌트가 안정적일 수는 없다. 최고로 안정된 시스템은 변경이 불가능하며 이는 우리가 바라는 상황이 아니다. 단순히 우리가 기대하는 것은 불안정한 컴포넌트와 안정된 컴포넌트를 분리하고 대부분의 개발을 불안정한 컴포넌트에서 수행하는 것이다.
컴포넌트는 안정된 정도만큼만 추상화되어야 한다.
그렇다면 고수준 정책은 어디에 위치시켜야 하는가? 이들은 가능한 변경되면 안되므로 안정된 컴포넌트(I = 0)에 위치해야 한다. 하지만 이는 수정을 어렵게 만들고 결국 시스템 아키텍처가 유연성을 잃는다. 그렇다면 위 상황을 해결하기 위해선 무엇을 해야하는가?
OCP에서 그 해답을 찾을 수 있다. 추상 클래스를 이용하여 확장을 쉽게 만들어 위 원칙을 준수하고 유연하게 만들 수 있다. 이 원칙은 곧 안정된 컴포넌트는 추상 컴포넌트여야 한다 라는 뜻이다.
SAP와 SDP를 결합하면 결국은 컴포넌트에 대한 DIP나 마찬가지다. 실제로 SDP는 의존성의 방향을, SAP는 추상화를 의미하기 때문이다. 그렇다면 추상화 정도를 어떻게 측정할 수 있을까?
A는 0과 1 사이의 값을 가지며, 1이면 추상 클래스만을 포함하는 것이다.
수직축에는 A를, 수평축에는 I를 나타내는 그래프를 보자.
출처 < https://share-factory.tistory.com/26 >유의미한 컴포넌트를 표시하면
반대로, 배제 구역도 있다.
고통의 구역
쓸모없는 구역
주계열로부터의 거리도 하나의 지표가 된다. 당연히 가까울 수록 바람직하다.
유효 범위는 0부터 1까지이며 D가 0이면 주계열 바로 위에 위치한다는 뜻이다. 이를 통해 주계열에 일치하도록 설계되었는지 계산할 수 있다. D가 0에 가깝지 않다면 재검토 후 재구성할 수 있다. 또한, 통계적으로 관리할 수도 있다. 평균과 분산을 구하여 극히 예외적인 컴포넌트를 식별할 수 있다.