2022.05.24
모듈성은 일종의 구성 원리(organizing principle)이다.
물리학에 비유하자면, 소프트웨어 시스템은 엔트로피(무질서)가 증가하는 방향으로 움직이는 복잡한 시스템을 모델링한다.
아키텍트는 끊임 없이 에너지를 소비해서 시스템을 구조적으로 탄탄하게 유지해야 한다.
모듈성을 잘 유지하는 건 우리가 암묵적(implicit) 아키텍처 특성이라고 정의한 것의 좋은 예가 된다.
사전에서 모듈을 찾아보면, '복잡한 구조를 만드는 데 쓰이는 각각의 표준화한 부품이나 독립적인 단위'라고 나온다. 우리는 모듈성을 이용해 객체 지향 언어의 클래스나 함수형 언어의 함수가 될 만한 서로 연관된 코드를 논리적으로 묶는다.
프로그래밍 언어에 내장된 패키징 메커니즘은 이제 워낙 다양해져서 개발자가 그 중 하나를 선택하는 것조차 힘들어졌다. 예를 들어, 요즘 언어는 대부분 각각 가시성과 스코핑 규칙이 다른 함수/메서드, 클래스, 패키지/네임스페이스에 개발자가 로직을 정의할 수 있다.
아키텍트는 개발자가 코드를 어떻게[ 패키징하는지 반드시 알아야 한다. 아키텍처에 중요한 영향을 미치기 때문이다.
우리는 아키텍처를 논할 때 클래스, 함수처럼 코드를 묶어 놓은 덩어리를 모듈성이라는 일반 용어로 나타낸다. 이것은 논리적인 구분이지 물리적인 구분은 아닌데, 이 차이점이 굉장히 중요한 경우가 있다. 예를 들어, 모놀리식 애플리케이션은 편의상 꽤 많은 클래스를 한 덩이로 묶어도 크게 상관없지만, 아키텍처를 재구축할 때에는 이렇게 커플링된 구조가 모놀리스를 나누는 데 걸림돌이 된다. 따라서 모듈성은 특정 플랫폼에 함축되어 있거나 불가피한 물리적인 분리와 다른 개념으로 바라보는 게 좋다.
응집(cohesion)은 한 모듈의 파트(구성 요소)가 동일한 모듈 안에 얼마나 포함되어 있는지를 나타낸다.
다시 말해, 모듈을 구성하는 파트가 서로 얼마나 연관되어 있는가, 하는 것이다.
응집된 모듈을 나누려고 해봐야 더 커플링되고 가독성을 떨어진다.
래리 콘스탄틴(Larry Constantine)
컴퓨터 과학자들이 정의한 응집도의 측정 범위를 가장 좋은 것부터 나쁜 것 순으로 나열해 보자.
응집은 커플링보다는 덜 정확한 메트릭이므로 아키텍트 재량에 따라 특정된 모듈의 응집도는 다르다.
예를 들어, 모듈을 다음과 같이 정의했다고 가정하자.
마지막 두 항목은 고객 관리 모듈에 그냥 두거나, 다음과 같이 2개의 모듈로 나눌 수 있다.
하지만 이것도 경우에 따라 어떠한 것도 정확한 구성이 될 수 있다.
위와 같은 질문이 소프트웨어 아키텍트 업무의 핵심이라고 할 수 있는 트레이드오프 분석이다.
컴퓨터 과학자들은 응집의 주관성을 전제로, 응집도(응집의 결여도)를 가늠할 수 있는 정말 우수한 구조적 메트릭을 개발했다.
메서드의 응집 결여도(Lack of Conhesion in Methods, LCOM)는 모듈(보통 컴포넌트)의 구조적 응집도를 나타낸다.
LCOM : 공유 필드를 통해 공유되지 않는 메서드의 총 갯수
클래스 X는 구조적 응집이 우수한 반면, 클래스 Y는 응집이 결여되어 있다. 클래스 Y의 필드/메서드 세 쌍은 각자 자기 클래스에 두어도 별로 상관이 없을 듯 하다. 클래스 Z는 응집이 조합된 모양새로, 세 번째 필드/메서드 쌍은 개발자가 자체 클래스로 빼도 된다.
다행이도 코드베이스의 커플리은 그래프 이론에 기반한 좋은 분석 도구들이 많이 있다. 메서드의 호출과 반환은 호출 그래프를 형성하므로 수학적인 분석이 가능하다.
워드 요던(Edward Yourdon)과 래리 콘스탄틴(Larry Constantine)이 지은 Structured Design
에는 구심 커플링, 원심 커플링을 비롯한 중요한 개념들이 대거 등장한다.
구심 커플링은 (컴포넌트, 클래스, 함수 등의) 코드 아티팩트로 유입되는 접속 수를, 원심 커플링은 다른 코드 아티팩트로 유출되는 접속 수를 나타낸다.
컴포넌트 커플링이 아키텍트에게 유의미한, 있는 그대로의 가치가 있다면, 여기서 파생된 다른 메트릭들도 잘 살펴볼 필요가 있다.
추상도는 추상 아티팩트와 구상 아티팩트(구현체)의 비율, 즉 구현 대비 추상화 정도를 나타낸다.
가령, 추상화를 전혀 하지 않고 (main()
메서드 하나에 코드를 전부 다 몰아넣은 경우처럼) 하나의 엄청나게 큰 함수 코드가 있는 코드베이스도 있고, 반대로 너무 지나치게 추상화해서 코드가 서로 어떻게 연결되어 있는지 개발자가 파악하기 어려운 코드베이스도 있다.
추상도 = 추상 요소 / 구상 요소
아키텍트는 추상 아티팩트의 총 개수와 구상 아티팩트의 총 개수로 추상도를 계산한다.
여기서 파생된 불안정도는 원심 커플링과의 비율이다.
불안정도 = 원심 커플링 / (원심 커플링 + 구심 커플링)
불안정도는 코드베이스의 변동성을 의미하므로 불안정도가 높은 코드베이스는 변경 시 커플링이 높아 더 깨지기 쉽다. 예를 들어, 여러 다른 클래스를 호출해서 작업을 위임하는 클래스는 호출되느,ㄴ 메서드 중 하나라도 변경되면 호출하는 이 클래스 역시 잘못될 공산이 매우 크다.
메인 시퀀스로부터의 거리는 아키텍처 구조를 평가하는 몇 가지 전체적인 매트릭 중 하나로, 불안정도와 추상도를 이용하여 계산한다.
메인 시퀀스로부터의 거리 = | 추상도 + 불안정도 - 1 |
밀러 페이지-존스는 1996년에 출간된 What Every Programmer Should Know About Object-Oriented Design
에서 구심/원심 커플링 메트릭을 더욱 발전시킨 커네이선스(connascence) 개념을 객체 지향 언어의 화두로 던졌다.
두 컴포넌트 중 한쪽이 변경될 경우 다른 쪽도 변경해야 전체 시스템의 정합성이 맞는다면 이들은 커네이선스를 갖고 있는 것이다.
밀러 페이지-존스
정적 커네이선스는 소스 코드 레벨의 커플링으로, 구심/원심 커플링을 발전시킨 개념이다. 다시 말해, 아키텍트는 구심적이든, 원심적이든 다음 종류의 정적 커네이선스를 뭔가에 커플링된 정도라고 보는 것이다.
int TRUE = 1; int FALSE = 0
처럼 박아 놓고 쓰는 식이다.void updateSeat(String name, String seatLocation)
라는 메서드를 만들고 update("14D", "Ford,N")
로 호출하면 타입은 맞지만 의미는 맞지 않는다.동적 커네이선스는 런타임 호출을 분석하는, 페이지-존스가 정의한 또 다른 유형의 커네이선스다.
런타임 호출은 호출 그래프에 비해 효과적인 분석 도구가 많지 않아 아키텍트는 동적 커네이선스를 파악하기가 쉽지 않다.
강도
아키텍트는 개발자가 어떤 유형의 커네이선스를 얼마나 쉽게 리팩터링할 수 있는지에 따라 커네이선스 강도를 결정한다.
정적 커네이선스는 개발자가 간단히 소스 코드를 분석하거나 최신 도구를 활용하면 어렵잖게 개선할 수 있기 때문에 아키텍트는 동적 커네이선스보다 정적 커네이선스를 선호한다.
예를 들어, 의미 커네이선스를 생각해보면 매직 밸류(magic value) 대신 기명 상수(named constant)를 만드는 명칭 커네이선스로 리팩터링하면 의미 커네이선스가 개선될 것이다.
지역성
커네이선스의 지역성은 코드베이스의 모듈들이 서로 얼마나 가까이 있는가, 이다.
근접한 코드는 보통 더 분리된 코드보다 높은 형태의 커네이선스를 가진다. 즉, 모듈을 서로 떨어트렸을 때 커플링이 형편없는 형태의 커네이선스는 모듈을 서로 가까이 붙여 놓는 식으로 개선할 수 있다.
개발자는 강도와 지역성을 함께 고민해야 한다. 동일한 모듈에서 더 강한 형태의 커네이선스가 발견된다면 그와 동일한 커네이선스가 널리 흩어져 있는 것보다는 코드 스멜이 덜하다는 증거이다.
정도
커네이선스 정도는 커네이선스가 미치는 영향의 규모에 관한 것이다.
이 값이 작을수록 코드베이스 입장에서는 바람직하다. 모듈이 몇 개 안 된다면 동적 커네이선스가 높아도 별로 해롭지 않지만, 코드베이스는 점점 커지기 마련이니 사소한 문제도 점점 더 악화될 것이다.
페이지-존스가 제시한 커네이선스를 이용해 시스템의 모듈성을 개선하는 세 가지 방법은 아래와 같다.
전설적인 소프트웨어 아키텍처 혁신가, 짐 웨이리치(Jim Weirich)는 커네이선스 개념을 다시 대중화하면서 두 가지 위대한 조언을 남겼다.
지금까지 우리는 시기와 목표가 상이한 커플링과 커네이선스를 이야기했다. 아키텍트 관점에서는 이 두 가지 뷰가 서로 중첩된다. 페이지-존스가 정적 커네이선스라고 밝힌 것들은 유출/유입 커플링 정도를 나타낸다. 구조적 프로그래밍은 들어오고 나가는 것에만 관심이 있는 반면, 커네이선스는 여러 가지 요소가 서로 어떻게 커플링되는지에 주목한다.
구조적 프로그래밍의 커플링 개념은 왼쪽, 커네이선스 특성은 오른쪽에 표시되어 있다. 구조적 프로그래밍에서 데이터 커플링(메서드 호출)이라고 부르는 커네이선스는 커플링이 어떻게 나타나야하는지 알려준다.
아키텍트가 이런 유용한 메트릭을 적용해서 시스템을 분석/설계할 때에는 몇 가지 문제점이 있다.
첫째, 이들 메트릭은 아키텍처 구조보다는 저수준 코드의 세부분을, 코드 품질 및 정리 상태 위주로 관찰한다.
둘째, 사실 커네이선스 자체는 요즘 아키텍트가 내려야 할 근본적인 결정에 관한 문제는 다루지 않는다.
이 책에서는 연관된 코드의 묶음을 모듈이라는 일반 용어로 표현하지만, 대부분의 플랫폼은 소프트웨어 아키텍트에게 핵심 구성 요소 중 하나인 컴포넌트 형태로 지원한다. 논리적, 물리적 분리에 관한 개념과 그에 따른 분석은 컴퓨터 과학 초창기부터 존재했지만, 아직도 컴포넌트 분리에 관한 수많은 글과 의견이 쏟아져 나오고 있는 가운데 개발자와 아키텍트는 좋은 결과를 내기 위해 애쓰고 있다.