037. (Clean Architecture) 13. 컴포넌트 응집도

13. 컴포넌트 응집도

어떤 클래스를 어느 컴포넌트에 포함시켜야 할까?

이는 중요한 결정이므로 소프트웨어 엔지니어링 원칙에 의거하여 판ㅂ단해야 한다.

이번 포스팅에서는 컴포넌트 응집도와 관련된 세 가지 원칙을 논의한다.

  • 재사용/릴리스 등가 원칙 (REP : Reuse/Release Equivalence Principle)
  • 공통 폐쇄 원칙 (CCP : Common Closure Principle)
  • 공통 재사용 원칙 (CRP : Common Reuse Principle)

13.1. REP: 재사용/릴리스 등가 원칙

재사용 단위는 릴리스 단위와 같다.

지난 십여년은 Maven, Leiningen, RVM과 같은 모듈 관리 도구가 등장한 시기였다.

이 기간에 재사용 가능한 컴포넌트나 컴포넌트 라이브러리가 대거 만들어졌고, 소프트웨어의 재사용은 흔한 일이 되었다.

돌이켜보면 재사용/릴리스 등가 원칙은 너무 당연하다고 느껴질 수 있다.

소프트웨어 컴포넌트가 릴리스 절차를 통해 추적 관리되지 않거나 릴리스 번호가 부여되지 않는다면 해당 컴포넌트는 재사용할 수도, 재사용 되지도 않을 것이기 때문이다.

릴리스 번호가 없는 경우엔 재사용 컴포넌트들의 상호 호환성을 보증할 방법이 없는 것은 추론가능하지만 이 이유 때문만은 아니다.

좀 더 정확히는 새로운 버전이 언제 출시되고 무엇이 변했는지를 개발자들이 인지할 수 있어야하기 때문이다.

새로운 릴리스 소식이 들리면, 개발자는 새로운 릴리스의 변경 사항을 살펴보고 기존 버전을 계속 쓸지 업데이트할지를 결정한다.

따라서 릴리스 절차에는 적절한 공지와 함께 릴리스 문서 작성도 포함되어야 한다.

이를 통해 개발자는 새 릴리스를 통합할지, 한다면 언제 할지 등의 의사결정을 내릴 수 있다.

이 원칙을 소프트웨어 설계와 아키텍처 관점에서 보면 단일 컴포넌트는 응집성 높은 클래스와 모듈들로 구성되어야 함을 뜻한다.

단순히 뒤죽박죽 임의로 선택된 클래스와 모듈로 구성되어서는 안된다.

컴포넌트를 구성하는 모든 모듈은 서로 공유하는 중요한 테마나 목적이 있어야하기 때문이다.

당연한 논의를 언급하는 것처럼 보이겠지만, 이를 당연히 여지기 않는 시각으로 바라볼 수도 있다.

하나의 컴포넌트로 묶인 클래스와 모듈은 반드시 함께 릴리스할 수 있어야 하며, 하나의 컴포넌트로 묶인 클래스와 모듈은 버전 번호가 같아야하며,

동일한 릴리스로 추적 관리되고 동일한 리리스 문서에 포함되어야 한다는 사실은 컴포넌트 제작자 입장이나 사용자 입장에서도 이치에 맞는 이야기이다.

하지만 재사용/릴리스 등가 원칙을 원칙만으로 클래스와 모듈을 단일 컴포넌트로 묶는 방법을 제대로 설명하기는 힘들다.

하지만 이 원칙 자체는 매우 중요하다.

재사용/릴리스 등가 원칙을 위배하는 경우 컴포넌트 사용자가 이를 인지하고, 개발자들의 평이 안 좋아질 것이다.

재사용/릴리스 등가 원칙의 약점은 아래 두 원칙을 통해 보완할 수 있는데

공통 폐쇄 원칙과 공통 재사용 원칙은 재사용/릴리스 등가 원칙을 엄격하게 제약을 가하는 측면에서 정의되기 때문이다.

13.2. CCP: 공통 폐쇄 원칙

동일한 이유로 동일한 시점에 변경되는 클래스는 같은 컴포넌트로 묶고, 서로 다른 시점에 다른 이유로 변경되는 클래스는 다른 컴포넌트로 분리해야 한다.

공통 폐쇄 원칙은 단일 책임 원칙을 컴포넌트 관점에서 다시 쓴 것이다.

단일 책임 원칙에서 단일 클래스는 변경의 이유가 여러 개 있어서는 안된다고 하듯이, 공통 폐쇄 원칙에서도 단일 컴포넌트는 변경의 이유가 여러개 있어서는 안되기 때문이다.

대다수의 애플리케이션에서 유지보수성(maintainability)은 재사용성보다 훨씬 중요하다.

만약 코드의 변경을 피할 수 없는 경우, 이 변경이 여러 컴포넌트의 변경을 요구하는 것보다는 단일 컴포넌트로 제한하는 것이 좋기때문이다.

단일 컴포넌트의 제한된 변경은 곧 해당 컴포넌트만의 재배포로 이어지기 때문이다.

공통 폐쇄 원칙은 같은 이유로 변경될 가능성이 있는 클래스는 모두 한 곳으로 묶을 것을 권장한다.

물리적 또는 개념적으로 강결합되어 항상 함께 변경되는 클래스들은 하나의 컴포넌트에 속해야하며, 이를 통해 소프트웨어를 릴리스, 재검증, 배포하는 일과 관련된 작업량을 최소화할 수 있다.

이 원칙은 개방 폐쇄 원칙과도 밀접하게관련되어 있다.

실제로 개방 폐쇄 원칙에서 말하는 폐쇄는 공통 폐쇄 원칙의 폐쇄와 동일한 뜻이기 때문이다.

물론 1005 완전한 폐쇄는 불가능하므로 전략으로 폐쇄해야할 필요가 있따.

공통 폐쇄 원칙에서는 동일한 유형의 변경에 대해 닫혀있는 클래스들을 하나의 컴포넌트로 묶음으로써 개방 폐쇄 원칙을 확대 적용한다.

따라서 변경이 필요한 요구사항이 발생 했을 때, 그 변경이 영향을 주는 컴포넌트들이 최소한으로 한정될 가능성을 더 높일 수 있다.

13.2.1. SRP와의 유사성

상술했듯 공통 폐쇄 원칙은 컴포넌트 수준의 단일 책임 원칙이다.

단일 책임 원칙에서는 서로 다른 이유로 변경되는 메서드를 서로 다른 클래스로 분리하라고 하며,

공통 폐쇄 원칙에서는 서로 다른 이유로 변경되는 클래스를 서로 다른 컴포넌트로 분리하라고 말한다.

공통 폐쇄 원칙과 단일 책임 원칙을 하나로 묶으면 아래와 같이 요약할 수 있다.

동일한 시점에 동일한 이유로 변경되는 것들을 한데 묶어라. 서로 다른 시점에 다른 이유로 변경되는 것들은 서로 분리하라.

13.3. CRP: 공통 재사용 원칙

컴포넌트 사용자들을 필요하지 않는 것에 의존하게 강요하지 말라

공통 재사용 원칙도 클래스와 모듈을 어느 컴포넌트에 위치시킬지 결정할때 도움되는 원칙이다.

공통 재사용 원칙에서는 같이 재사용되는 경향이 있는 클래스와 모듈들은 같은 컴포넌트에 포함해야한다고 말한다.

개별 클래스가 단독으로 재사용되는 경우는 거의 없으며, 대체로 재사용되는 클래스는 재사용 모듈의 일부로써 해당 모듈의 다른 클래스와 상호작용하는 경우가 많다.

공통 재사용 원칙은 이런 클래스들이 동일한 컴포넌트에 포함되어야한다고 말하는 원칙이며, 이에 따라 컴포넌트 내부에서는 클래스들 사이에 수많은 의존성이 있으리라고 예상해볼 수 있다.

간단한 사례로 컨테이너(container) 클래스와 해당 클래스의 이터레이터(iterator) 클래스를 예로 들 수 있다.

이 두 클래스는 강결합되어있기때문에 함께 재사용되며, 반드시 동일한 컴포넌트에 위치해야한다.

이처럼 공통 재사용 원칙은 각 컴포넌트에 어떤 클래스들을 포함시켜야야 하는지 설명해준다.

하지만 이것이 공통 재사용 원칙의 전부는 아니다.

공통 재사용 원칙은 동일한 컴포넌트로 묶이면 안되는 클래스가 무엇인지도 설명해준다.

어떤 컴포넌트가 다른 컴포넌트를 사용하면 두 컴포넌트 사이에는 의존성을 발생한다.

이때 사용중인 컴포넌트가 사용되는 컴포넌트에 포함된 단 하나의 클래스만 참조하는 경우를 생각해보자.

하나의 클래스만 참조하더라도 두 컴포넌트간의 의존성은 전혀 약해지지않는다.

위와 같은 의존성으로 사용되는 컴포넌트가 변경될 때마다 사용하는 컴포넌트도 변경해야할 가능성이 높다.

또는 사용하는 컴포넌트를 변경하지 않더라도 재컴파일, 재검증, 재배포를 해야하는 가능성은 여전히 남아있다.

심지어 사용되는 컴포넌트에서 발생한 변경이 사용하는 컴포넌트와는 전혀 관련없는 경우에도 해당한다.

따라서 의존하는 컴포넌트가 있다면 해당 컴포넌트의 모든 클래스에 대해 의존함을 확실히 인지해야 한다.

바꾸어 말해, 한 컴포넌트에 속한 클래스들을 더 작게 그루핑할 수 없다.

즉, 일부 클래스에만 의존하더라도 다른 클래스와 독립적일 수 없음을 확실히 인지해야 한다.

결과적으로 공통 재사용 원칙은 어떤 클래스르 한 데 묶어도 되는지보다는 어떤 클래스를 한 데 묶어서는 안되는 지에 대해 말해주는 경우가 대다수이다.

13.3.1. ISP와의 관계

공통 재사용 원칙은 인터페이스 분리 원칙의 포괄적인 버전이다.

인터페이스 분리 원칙은 사용하지 않은 메서드가 있는 클래스에 의존하지 말라고 하며,

공통 재사용 원칙은 사용하지 않는 클래스를 가진 컴포넌트에 의존하지 말라고 한다.

공통 재사용 원칙과 인터페이스 분리 원칙을 하나로 묶으면 아래와 같이 요약할 수 있다.

필요하지 않은 것에 의존하지 말라

13.4. 컴포넌트 응집도에 대한 균형 다이어그램

세 원칙을 자세히 살펴보면 서로 상충되는 부분이 있음을 알 수 있다.

먼저 재사용/릴리스 등가 원칙과 공통 폐쇄 원칙은 포함(inclusive) 원칙이며, 컴포넌트를 더욱 크게 만든다.

공통 재사용 원칙은 배제(exclusive) 원칙이며, 컴포넌트를 더욱 작게 만든다.

따라서 이 원칙들이 균형을 이루는 방향을 설계가 되어야 한다.

아래는 세 원칙에 대한 균형 다이어그램이다.

Cohesion principles tension diagram

위 다이어그램의 각 변(edge)는 반대쪽 꼭지점에 있는 원칙을 포기했을 때 감수해야하는 비용을 나타낸다.

오로지 재사용/릴리스 등가 원칙과 공통 재사용 원칙에만 중점을 두면 사소한 변경에도 많은 컴포넌트를 변경해야하며,

반대로 공통 폐쇄 원칙과 재사용/릴리스 등가 원칙에만 집중하면 불필요한 릴리스가 너무 빈번해진다.

프로젝트 초기에는 공통 폐쇄 원칙이 재사용/릴리스 등가 원칙보다 훨씬 중요한데, 개발 가능성이 재사용성보다 더욱 중요하기 때문이다.

일반적으로 프로젝트는 삼각형의 오른쪽에서 시작하는 편이며, 이때는 오로지 재사용성만 포기하면 된다.

프로젝트가 진행되며 삼각형에서 점차 왼쪽으로 이동해가며 컴포넌트 구조도 변하게 된다.

다시 말해 프로젝트가 실제로 수행하는 기능보다는, 프로젝트가 발전되고 사용되는 방법과 더 관련이 있따고 볼 수 있다.