단위 크기: 패키지 응집도의 원칙
개발자가 어떻게 클래스를 패키지에 분류해 넣을지 결정할 때 도움이 되는 규칙 세가지를 알아보자. 이때 각 클래스의 상호 관계가 일부분이라도 밝혀져 있음을 가정한다.
모듈의 응집도는 모듈이 단 하나의 기능을 수행하는 속성을 의미했지만 패키지 응집도는 그보다 복잡하다. 재사용성과 개발 용이성에서 상충하는 가치를 검토하고 어플리케이션의 요구사항에 따라 패키지를 만들어야 한다.
재사용 릴리즈 등가 원칙 (REP, Reuse-Release Equivalence Principle)
재사용 단위가 릴리즈의 단위이다
재사용성 릴리즈 등가 원칙은 재사용 단위(예를 들면 패키지)는 릴리즈 단위보다 작을 수 없다. 재사용하는 모든 것은 반드시 릴리즈된 다음 추적되어야 한다. 단일 클래스 달랑 만들고 재사용할 수 있다고 말하기 힘들다. 해당 재사용 단위의 안정성과 지원에 대한 보장을 제공해야 재사용성이라는 말을 할 수 있다.
패키지 내용물을 볼 때 잠재적인 재사용자의 입장에서 봐야 한다. 만약 패키지에 재사용될 소프트웨어가 들어 있다면, 그 패키지에는 재사용을 목적으로 설계뙤지 않은 소프트웨어는 들어 있지 않아야 한다. 패키지의 모든 클래스가 재사용 가능하든지, 모두 그렇지 않든지 해야 한다.
패키지 안의 모든 클래스는 동일한 재사용자를 대상으로 해야 한다. 어떤 사람의 관점에서 봤을 때, 한 패키지 안에서 일부 클래스는 필요하지만 일부는 전혀 필요 없으면 안된다.
공통 재사용 원칙(CRP, Common-Reuse Principle)
패키지 안의 클래스들은 함께 재사용되어야 한다. 어떤 패키지의 클래스 하나를 재사용한다면 나머지도 모두 재사용되어야 한다.
함께 자주 재사용되는 클래스들은 동일한 패키지에 속하도록 해야 한다. 혼자 재사용되는 클래스는 거의 없다. 대부분 재사용 가능성에 대해 같은 추상적 범주에 있는 클래스들끼리 협력한다.
두 개의 패키지가 하나의 클래스로 인해 의존 관계가 생긴다고 가정하자. 그렇다면 의존되는 패키지가 변경되면 의존하는 패키지도 검증하고 다시 릴리즈 해야 한다. 따라서 어떤 패키징p 의존한다면 그 패키지의 모든 클래스에 의존하는지 확실히 해두어야 한다.
즉 CRP에 따르면 클래스 관계로 서로 단단히 묶여 있지 않은 클래스들은 같은 패키지에 넣지 말아야 한다.
공동 폐쐐 원칙(CCP, Common-Closure Principle)
같은 패키지 안의 클래스들은 동일한 종류의 변화에는 모두 폐쇄적이어야 한다.
어떤 한 패키지를 변경할 이유도 여러가지면 안된다. 동일한 이유로 변할 것 같은 클래스들은 한 장소에 모아놓아야 한다.
이 원칙은 개방 폐쇄 원칙(OCP)과 밀접한 관련이 있다. OCP는 특정 오류의 변화에 개방되어 있는 클래스들은 같은 패키지 안에 몰아넣어서 이 전략을 더욱 확대한다. 따라서 요구사항에 변화가 오더라도 그에 따라 변경할 패키지 수를 최소화 할 수 있다.
안전성: 패키지 결합도의 원칙
패키지 상호 관계에서 개바 ㄹ용이성과 논리적 설계 사이의 균형을 찾아 설계하는데 도움이 되는 세가지 규칙을 알아보자.
의존 관계 비순환 원칙(ADP, Acyclic-Dependecies Principle)
패키지 의존성 그래프에서 순환을 허용하지 말라
이런 형태의 패키지 의존성 그래프가 있다고 가정해보자. 이 상황에서 만약 Service A
패키지를 변경하고 새롭게 릴리즈 하려고 하면 누가 영향 받는지 알기 쉽다. Controller A
패키지가 영향을 받을 것이다. 즉 Service A
를 새롭게 릴리즈 되어도 Controller A
패키지만 신경 쓰면 된다. 다른 패키지들은 관심이 없다.
여기서 Repository A
패키지가 불행하게도 Controller A
패키지를 의존하게 됐다고 해보자.
이 상황에서 Service A
를 새롭게 변경하고 릴리즈 하려는 경우에 얼마나 많은 패키지를 확인해봐야 할까? Controller A
뿐만 아니라 Repository A
까지 검사를 해야 한다.
즉 양방향 의존성이 생기게 되면 그 사이클 안에 패키지가 새롭게 릴리즈 되어야 할 때 그 사이클의 모든 패키지가 영향을 받을 수 있으니 확인해야 하는 문제가 생긴다!!!
순환 끊기
의존성 순환이 생긴 경우 두가지 해결법이 있다.
- 의존 관계 역전 원칙을 적용한다.
위 예시의 경우Repository A
가 필요로 하는 내용을 추상화해서 인터페이스로 만들어Repository A
에 놓고, 그 구현체를Controller A
에 둬서 의존 관계가 역전이 되게 만든다. - 두 패키지가 서로 의존하는 새로운 패키지 만들기
위 예시의 경우Repository A
와Controller A
모두 의존하는 패키지를 새롭게 추가할 수 있다.
패키지는 하향식 설계를 할 수 없다.
패키지 의존 관계 다이어그램은 애플리케이션의 기능을 기술하는 일과는 관련없다. 단지 애플리케이션의 빌드 용이성을 보여준다. 설계와 구현 초기 단계에 클래스가 적은 경우 어떤 것을 폐쇄해야 할 지, 재사용 가능한 것은 무엇인지를 알기 어렵다. 그래서 패키지 의존 관계 구조는 시스템의 논리적 설계와 함께 성장하고 진화해야 한다.
- 설계와 구현을 해가면서 클래스 수가 점점 증가함
- SRP와 CCP를 통해 변경되기 쉬운 클래스를 함께 묶어둠
- 애플리케이션이 성장하면서 재사용 가능 요소를 고려하면서 CRP를 고려
- 순환이 나타나게 되면 ADP를 통해 구조 개선
안정된 의존 관계 원칙(SDP)
의존은 안정적인 쪽으로 향해야 한다.
설계는 완전히 정적일 수 없다. 설계를 계속 유지보수하려면 어느 정도 변동성이 필요하다. 우리는 공통 폐쇄 원칙(CCP)를 지킴으로써 이것을 달성할 수 있다. 이제 안정된 의존 관계 원칙을 사용해서 어떤 종류의 변화에는 민감한 패키지를 만든다.
쉽게 바뀔 것이라고 예상되는 패키지들이 바뀌기 어려운 패키지들의 의존 대상이 되어서는 안된다. 이렇게 되면 쉽게 바뀔 패키지들도 바꾸기 어렵게 되어버린다. SDP를 지킴으로써 쉽게 변경할 수 있도록 의도한 모듈이 변경하기 어려운 모듈의 의존 대상이 되지 않도록 보장할 수 있다.
여기서 안정적인 패키지란 무엇인가? 특정 패키지 X가 있다고 할 때 그 패키지를 여러 곳에서 사용하고 있다고 하자. 그렇다면 X는 변경되기 어렵다. 즉 안정적이라고 볼 수 있다.
반면 X가 만약 여러 패키지를 사용하고 있다면? 변경에 제한이 없다. 그래서 변경되기 쉽고 불안정적이라고 볼 수 있다.
안정성 측정법
a
: 이 패키지에 의존하는 외부 클래스 갯수b
: 외부 패키지에 의존하는 이 패키지의 클래스 갯수
안정성 측정법은 불안전성 = b / (a + b)
이다!!
만약 불안정성은 0과 1사이이며 클 수록 외부에 의존하는 경향이 크다.
결론은 남들 나에게 많이 의존하면 책임을 많이 지는 패키지이며 안정적이고, 내가 남들을 많이 의존하면 책임을 많이 지지 않으며 불안정적이다.
SDP를 위반한다면?
불안정한 패키지와 안정한 패키지가 의존하게 되는 불안정한 패키지의 클래스를 찾아서 DIP로 추출한 인터페이스를 새로운 패키지에 둘 수 있다.
안정된 추상화 원칙(SAP)
패키지는 자신이 안정적인 만큼 추상적이기도 해야 한다
어떤 패키지가 언정적이라면 확장할 수 있도록 추상 클래스들로 구성되어야 한다. 확장이 가능한 안정적인 패키지는 유연하며, 따라서 설계를 지나치게 제약하지 않는다.
추상성 측정법
a
: 패키지 안에 들어 있는 클래스 개수b
: 패키지 안에 들어있는 추상 클래스 개수
추상성 측정법은 추상성 = b/a
이다.
이상적인 상황과 비교
패키지는 안정성에 비해 너무 추상적이지 않고 추상성에 비해 너무 불안정하지도 않은 게 좋다. 자신이 추상적인 만큼 의존의 대상이 되고 자신이 구체적인 정도 만큼 다른 패키지에 의존한다.
x를 안정성이라하고 y를 추상성이라 할 때, (1,0)과 (0,1)을 잇는 직선을 주계열이라 하고 앞서 말한 좋은 패키지는 주계열에 근접할 수록 좋다.
이런 이상적인 상황과 거리를 구하는 공식은 다음과 같다.정규화된 거리 = | 안전성 + 추상성 - 1 |
거리가 클 수록 주계열과 멀어지고 이상적이 상황과 멀어진다.