Home 소프트웨어 아키텍처 101 - CHAPTER 3 모듈성
Post
Cancel

소프트웨어 아키텍처 101 - CHAPTER 3 모듈성

본인이 선택한 개발 플랫폼에서 모듈성과 그것을 구현한 수많은 코드를 이해하는 것은 아키텍트에게 대단히 중요한 일입니다. 우리가 아키텍처를 분석해야 할 많은 도구가 바로 이 모듈성에 기반하기 때문입니다. 모듈성은 일종의 구성 원리(organizing principle)입니다.

3.1 정의

사전에서 모듈을 찾아보면, ‘복잡한 구조를 만드는 데 쓰이는 각각의 표준화된 부품이나 독립적인 단위’라고 나옵니다.

프로그래밍 언어는 대부분 모듈성 메커니즘을 제공하며, 개발자는 보통 연관된 코드를 함께 묶는 수단으로 모듈을 사용합니다.

3.2 모듈성 측정

3.2.1 응집

응집(cohesion)은 한 모듈의 파트(part, 구성요소)가 동일한 모듈안에 얼마나 포함되어 있는지를 나타냅니다. 다시 말해, 모듈을 구성하는 파트가 서로 얼마나 연관되어 있는가, 하는 것입니다.

컴퓨터 과학자들은 응집도의 측정 범위를 정의했는데, 가장 좋은 것부터 나쁜 것 순으로 나열하겠습니다.

  1. 기능적 응집(functional cohesion)

    모듈의 각 파트는 다른 파트와 연관되어 있고 기능상 꼭 필요한 모든 것이 모듈에 들어있습니다.

  2. 순차적 응집(sequential cohesion)

    두 모듈이, 한 쪽이 데이터를 출력하면 다른 한 쪽이 이것을 입력 받는 형태로 상호작용합니다.

  3. 소통적 응집(communication cohesion)

    두 모듈이, 각자 정보에 따라 작동하고(거나) 어떤 출력을 내는 형태로 통신 체인(communication chain)을 형성합니다. 예를 들면, 데이터베이스에 레코드를 추가하면 그 정보에 따라 이메일이 만들어지는 식입니다.

  4. 절차적 응집(procedural cohesion)

    두 모듈은 정해진 순서대로 실행되어야 합니다.

  5. 일시적 응답(temporal cohesion)

    모듈은 시점 의존성(timing dependency)에 따라 연관됩니다. 예를 들어 많은 시스템들이 시동할 때 그다지 관련이 없어 보이는 것들을 죽 초기화하는 경우가 많은데, 이런 작업들이 일시적으로 응집됐다고 할 수 있습니다.

  6. 논리적 응집(logical cohesion)

    모듈의 내부 데이터는 기능적이 아니라, 논리적으로 연관되어 있습니다. 이를테면, 텍스트, 직렬화 객체, 스트림 형태로 받은 데이터를 변환하는 모듈이 그렇습니다. 서로 연관된 작업들이지만 하는 일은 전혀 다릅니다.

  7. 동시적 응집(coincidental cohesion)

    같은 소스 파링에 모듈 구성 요소가 들어 있지만 그 외에는 아무 연광성도 없습니다. 이는 가장 좋은 않은 형태의 응집입니다.

카이댐버와 케메러가 개발한 ‘메서드 응집 결여도(Lack of Cohesion in Methods, LCOM)’는 모듈(보통의 컴포넌트)의 구조적 응집도를 나타냅니다.

식 3-1 LCOM 버전 1

\[LCOM = \begin{cases} |P| - |Q|, & \text{if } |P| > |Q| \\ 0, & \text{그 외는} \end{cases}\]

[식 3-1]에서 P는 특정 공유 필드에 액세스하지 않는 메서드 수만큼 증가하고, 반대로 Q는 특정 공유 필드를 공유하는 메서드 수만큼 감소합니다. 이식은 점점 정교하게 다듬어져 1996년에는 LCOM96b라는 두 번째 버전이 등장했습니다(식 3-2).

식 3-2 LCOM96b

\[LCOM96b = \frac{1}{a} \sum_{j=1}^{a} \frac{m - \mu(A_j)}{m}\]

LCOM: 공유 필드를 통해 동유되지 않은 메서드의 총 개수

예를 들어, 프라이빗 필드(private field) a, b가 있는 클래스가 있다고 합시다. 이 클래스에는 a만 액세스하는 메서드가 있고 b만 액세스하는 메서드도 있습니다. 공유 필드 (a와 b)를 통해 공유되지 않는 메서드는 많기 때문에 이 클래스의 LCOM 점수는 높습니다. 즉, 메서드의 응집 결여도가 높은 것입니다.

스크린샷 2024-07-14 오후 2.23.08.png

클래스 X는 LCOM 점수가 낮고 구조적 응집이 우수한 반면, 클래스 Y는 응집이 결여되어 있습니다. 클래스 Y의 필드/메서드 세 쌍은 각자 자기 클래스에 두어도 별로 상관이 없을 듯 합니다. 클래스 Z는 응집이 조합된 모양새로, 세번째 필드/메서드 쌍은 개발자가 자체 클래스로 빼내도 됩니다.

LCOM 메트릭은 아키텍처 스타일을 전환하기 위해 코드베이스를 분석하는 이키텍트에게 매우 유용합니다.

LCOM의 모든 메트릭이 찾아내는 것은 구조적 응집 결여도일 뿐, 이 메트릭만으로 어떤 조각들이 서로 잘 맞는지 논리적으로 판달할 수는 없습니다. ‘왜’보다 ‘어떻게’가 중요한 소프트웨어 아키텍처 제 2법칙에 따른 것이죠.

3.2.2 커플링

코드베이스의 커플링은 그래프 이론에 기반한 좋은 분석 도구들이 많이 있습니다. 메서드의 호출과 반환은 호출 그래프를 형성하므로 수학적인 분석이 가능합니다.

  • 구심 커플링(afferent coupling): (컴포넌트, 클래스, 함수 등의) 코드 아티팩트로 유입되는(incoming) 접속 수
  • 원십 커플링(efferent coupling): 다른 코드 아티팩트로 유출되는(outgoing) 접속 수

3.2.3 추상도, 불안정도

  • 추상도(abstractness): 추상 아티팩트(abstract artifact)와 구상 아티팩트(concrete artifact, 구현체)의 비율. 즉, 구현 대비 추상화 정도.

    식 3-3 추상도

    \[A = \frac{\sum{m^a}}{\sum{m^c}}\]
    • $m^a$: 모듈에 있는 추상(abstract) 요소(인터페이스 또는 추상 클래스)
    • $m^c$: 모듈에 있는 구상(concrete) 요소(비 추상 클래스)
  • 불안정도(instability): 원심 커플링과 (구심 커플링 + 원심 커플링)의 비율

    식 3-4 불안정도

    \[I = \frac{C^e}{C^e + C^a}\]
    • $C^e$: 원심(나가는) 커플링
    • $C^a$: 구심(들어오는) 커플링

    불안정도는 코드베이스의 변동성(volatility)을 의미하므로 불안정도가 높은 코드베이스는 변경 시 커플링이 높아 더 깨지기 쉽습니다. 예를 들어, 여러 다른 클래스를 호출해서 작업을 위임하는 클래스는 호출되는 메서드 중 하나라도 변경되면 호출하는 이 클래스 역시 잘못될 공산이 매우 큽니다.

3.2.4 메인 시퀀스로부터의 거리

메인 시퀀스로부터의 거리(distance from the main sequence)는 아키텍처 구조를 평가하는 몇 가지 전체적인 메트릭 중 하나로, 불안정도와 추상도를 이용하여 계산합니다(식 3-5).

식 3-5 메인 시퀀스로부터의 거리

\[D = |A + I - 1|\]
  • $A$: 추상도
  • $I$: 불안정도

추상도, 불안정도는 비율이므로 항상 0과 1사이의 값입니다. 따라서 그 관계를 [그림 3-2]와 같은 그래프로 그려볼 수 있습니다.

스크린샷 2024-07-14 오후 5.55.40.png

메인 시퀀스로부터의 거리는 추상도와 불안정도 사이의 이상적인 관계를 나타냅니다. 이상적인 선에 가까운 클래스는 서로 경쟁적인 두 메트릭의 건전한 조합이라고 볼 수 있죠. 예를 들어, [그림 3-3] 같은 클래스의 경우 메인 스퀀스로부터의 거리는 D입니다.

스크린샷 2024-07-14 오후 5.59.43.png

[그림 3-3]에서 개발자는 후보 클래스를 그래프로 그려보고 이상적인 선에서 얼마나 떨어져 있는지 거리를 잽니다. 이 선에 가까울수록 클래스 균형이 잘 맞다는 방증입니다. 오른쪽 위로 치우친 부분을 쓸모없는 구역(zone of uselessness, 너무 추상화를 많이 해서 사용하기 어려운 코드), 반대로 왼쪽 아래로 치우친 부분을 고통스런 구역(zone of pain, 추상화를 거의 안 하고 구현 코드만 잔뜩 넣어 취약하고 관리하기 힘튼 코드)이라고 합니다(그림 3-4).

스크린샷 2024-07-14 오후 11.17.22.png

3.2.5 커네이선스

  • 커네이선스(connascence): 변화 종속성. 어느 한쪽을 바꾸면 다른 쪽도 함께 바꾸어야 전체적으로 정합성이 유지되는 상태.

    두 컴포넌트 중 한 쪽이 변경될 경우 다른 쪽도 변경해야 전체 시스템의 정합성이 맞는다면 이들은 커네이선스를 갖고 있는 것이다. - 밀러 페이지-존스(Meilir Page-Jones)

    • 정적 커네이선스
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
      정적 커네이선스(static connascence)는 (실행 시간 커플링과 정반대인) 소스 코드 레벨의 커플링으로, ‘Structured Design(Prentice-Hall, 1979)’에서 등장하는 구심/원심 커플링을 발전시킨 개념입니다. 다시 말해 아키텍트는 구심적이든 원심적이든 다음 종류의 정적 커네이선스를 뭔가에 커플링된 정도라고 보는 것입니다.
        
      - 명칭 커네이선스(CoN)
            
          여러 컴포넌트의 엔티티명이 일치해야 합니다.
            
      - 타입 커네이선스(CoT)
            
          여러 컴포넌트의 엔티티 타입이 일치해야 합니다.
            
      - 의미 커네이선스(CoM) 또는 관례 커네이선스(CoC)
            
          여러 컴포넌트에 걸쳐 어떤 값의 의미가 일치해야 합니다.
            
      - 위치 커네이선스(CoP)
            
          여러 컴포넌트는 값의 순서가 일치해야 합니다.
            
      - 알고리즘 커네이선스(CoA)
            
          여러 컴포넌트는 특정 알고리즘이 일치해야 합니다.
    
    • 동적 커네이선스

      동적 커네이선스(dynamic connascence)는 런타임 호출을 분석하는, 페이지-존스가 정의한 또 다른 유형 커네이선스입니다.

      • 실행 커네이선스(CoE)

        여러 컴포넌트의 실행 순서가 중요합니다.

      • 시점(타이밍) 커네이선스(CoT)

        여러 컴포넌트의 실행 시점이 중요합니다.

      • 값 커네이선스(CoV)

        상호 연관된 다수의 값들이 함께 변경할 때 발생합니다.

      • 식별(아이덴티티) 커네이선스(CoI)

        여러 컴포넌트가 동일한 엔티티를 참조할 때 발생합니다.

    • 커네이선스 속성

      • 강도

        아키텍트는 개발자가 어떤 유형의 커네이선스를 얼마나 쉽게 리팩토링할 수 있는지에 따라 커네이선스 강도(strength)를 결정합니다.

        정적 커네이선스는 개발자가 간단히 소스 코드를 분석하거나 최신 도구를 활용하면 어렵잖게 개선할 수 있기 때문에 아키텍트는 동적 커네이선스보다 정적 커네이선스를 선호합니다.

        스크린샷 2024-07-15 오후 6.35.28.png

      • 지역성

        커네이선스의 지역성(locality)은 코드베이스의 모듈들이 서로 얼마나 가까이 있는가, 입니다. (동일한 모듈에서) 근접한 코드(proximal code)는 보통 (별개의 모듈 또는 코드베이스로) 더 분리된 코드보다 높은 형태의 커네이선스를 가집니다. 즉, 모듈을 서로 떨어트렸을 때 커플링이 형편없는 형태의 커네이선스는 모듈을 서로 가까이 붙여 놓는 식으로 개선할 수 있습니다.

      • 정도

        커네이선스 정도(degree)는 커네이선스가 미치는 영향의 규모(소수의 클래스에 영향을 미치는가, 아니면 수많은 클래스에 영향을 미치는가)에 관한 것입니다. 이 값이 작을수록 코드베이스 입장에서는 바람직하겠죠.

        • 페이지-존스가 제시한, 커세이선스를 이용해 시스템의 모듈성을 개선하는 세 가지 방법
          1. 시스템을 캡슐화한 요소들로 잘게 나누어 전체 커네이선스를 최소화한다.
          2. 캡슐화 경계를 벗어나는 나머지 커네이선스를 모조히 최소화한다.
          3. 캡슐화 경계 내부에서 커네이선스를 최대화한다.
        • 짐 웨이리치(Jim Weirich)의 두 가지 조언
          • 정도의 규칙(Rule of Degree)

            강한 형태의 커네이선스를 보다 양한 형태의 커네이선스로 전환하라.

          • 지역성의 규칙(Rule of Locality)

            소프트웨어 엘리먼트 간의 거리가 멀어질수록 보다 약한 형태의 커네이선스를 사용하다.

3.2.6 커플링과 커네이선스 메트릭을 통합

구조적 프로그래밍에서 데이터 커플링(data coupling, 메서드 호출)이라고 부르는 커네이선스는 커플링이 어떻게 나타나야 하는지 알려줍니다.

3.3 모듈에서 컴포넌트로

이 책에서는 연관된 코드의 묶음을 ‘모듈(module)’이라는 일반 용어로 표현하지만, 대부분의 플랫폼은 소프트웨어 아키텍트에게 핵심 구성 요소 중 하나인 ‘컴포넌트(component)’ 형태로 지원합니다. 논리적, 물리적 분리에 관한 개념과 그에 따른 분석은 컴퓨터 과학 초창기부터 존재했지만, 아직도 컴포넌트와 분리에 관한 수많은 글과 의견들이 쏟아져 나오고 있는 가운데 개발자와 아키텍트는 좋은 결과를 내기 위해 애쓰고 있습니다.

요약

3.1 정의

  • 모듈성: 복잡한 구조를 만드는 표준화된 부품이나 독립된 단위.
  • 프로그래밍 언어는 모듈성 메커니즘을 제공하며, 연관된 코드를 묶는 수단으로 사용됨.

3.2 모듈성 측정

  • 응집(Cohesion): 모듈의 구성 요소가 얼마나 연관되어 있는가를 측정.
    1. 기능적 응집: 모듈의 각 부분이 기능적으로 연관.
    2. 순차적 응집: 출력이 다른 모듈의 입력으로 사용됨.
    3. 소통적 응집: 정보에 기반한 상호작용.
    4. 절차적 응집: 순서대로 실행됨.
    5. 일시적 응집: 특정 시점에 관련 작업이 함께 실행.
    6. 논리적 응집: 논리적으로 연관된 작업.
    7. 동시적 응집: 연관성 없는 요소들이 모듈 내에 존재.
  • 커플링(Coupling): 모듈 간의 의존성.
    • 구심 커플링: 코드 아티팩트로 유입되는 접속 수.
    • 원심 커플링: 다른 코드 아티팩트로 유출되는 접속 수.
  • 추상도(Abstractness): 추상 아티팩트와 구상 아티팩트의 비율.
  • 불안정도(Instability): 모듈의 변동성(원심 커플링과 구심 커플링의 비율).
  • 메인 시퀀스로부터의 거리(Distance from the Main Sequence): 추상도와 불안정도의 이상적인 관계로부터의 거리 측정.
  • 커네이선스(Connascence): 두 모듈이 서로 종속된 정도.
    • 정적 커네이선스: 소스 코드 수준의 커플링.
    • 동적 커네이선스: 런타임 상의 커플링.

3.3 모듈에서 컴포넌트로

  • 모듈: 연관된 코드를 묶는 일반 용어.
  • 컴포넌트: 플랫폼에서 아키텍트에게 제공되는 핵심 구성 요소로, 논리적 및 물리적 분리에 관한 개념.
This post is licensed under CC BY 4.0 by the author.

소프트웨어 아키텍처 101 - CHAPTER 2 아키텍처 사고

소프트웨어 아키텍처 101 - CHAPTER 4 아키텍처 특성 정의