LODY/정리

객체지향 분석과 설계 / UML로 설계를 소통하기

UML로 설계를 소통하기

🟣 머릿속 설계는 전달되지 않는다

앞 장에서 정보 전문가와 결합·응집으로 책임을 배분했다. 그런데 그 결정은 전부 내 머릿속에 있었다. 팀원에게 "판매가 총액을 계산하고, 각 판매 항목이 소계를 낸다"를 말로만 전하면, 듣는 쪽은 그 관계를 각자 다르게 상상한다. 리뷰 자리에서 설계 이야기가 자꾸 겉도는 이유가 여기 있었다. 같은 그림을 보고 있지 않았던 것이다.

UML은 이 문제를 푸는 공용 표기다. 여기서 오해하기 쉬운 점이 하나 있다. UML은 개발 방법론이 아니라 표기법일 뿐이다. UML을 그린다고 좋은 설계가 되는 게 아니라, 이미 내린 설계 판단을 남과 같은 기호로 나누는 것이 목적이다. Larman이 강조하는 것도 완벽한 문서가 아니라 소통 도구로서의 UML이다. 화이트보드에 빠르게 그려 합의하고 지우는 수준이면 충분한 경우가 많다.

이 장은 가장 많이 쓰는 두 그림을 본다. 정적인 구조를 담는 클래스 다이어그램과, 동적인 흐름을 담는 상호작용 다이어그램이다. 하나는 무엇이 있는지를, 다른 하나는 그것들이 시간 위에서 어떻게 협력하는지를 보여준다.


🟣 클래스 다이어그램: 관계의 종류를 구분한다

클래스 다이어그램은 클래스와 그 속성·연산, 그리고 클래스 사이의 관계를 그린다. 초보 때는 클래스들을 선으로만 이어 놓고 끝냈는데, UML의 값어치는 그 선의 종류를 구분하는 데 있다. 같은 "연결"이라도 상속인지, 잠깐 쓰는 의존인지, 오래 붙어 있는 연관인지에 따라 화살표 모양이 다르고, 그 차이가 곧 설계 의도다.

Payment(추상)금액Sale총액()CashPaymentCardPaymentSalesLineItem소계()ProductCatalog가격조회()상속상속합성 1..*1 0..1가격 조회

네 가지 관계가 각기 다른 기호로 그려졌다. 속이 빈 삼각형은 상속이다. CashPaymentCardPaymentPayment의 한 종류라는 뜻이다. 채운 마름모는 합성이다. 판매 항목은 판매에 속하고 판매가 사라지면 함께 사라진다. 실선은 단순 연관으로, 판매가 결제를 하나 가진다는 오래 유지되는 관계다. 점선 화살표는 의존이다. 판매 항목이 소계를 낼 때만 상품 목록을 잠깐 참조할 뿐, 계속 붙들고 있지는 않는다.

이 구분이 실무에서 중요한 이유는, 관계의 종류가 곧 변경의 파급 범위를 알려 주기 때문이다. 합성으로 묶인 것은 생명주기가 엮여 있으니 함께 다뤄야 하고, 의존은 느슨하니 상대를 갈아 끼우기 쉽다. 화살표 하나가 "이 둘은 얼마나 깊게 엮였나"를 말해 준다.


🟣 상호작용 다이어그램: 시간 위의 협력

클래스 다이어그램은 구조를 보여주지만 시간이 없다. "총액을 구할 때 어떤 순서로 메시지가 오가는가"는 담기지 않는다. 그 동적인 흐름을 그리는 것이 상호작용 다이어그램(시퀀스 다이어그램)이다. 앞 장에서 정한 책임 배분이 실제로 어떻게 실행되는지를 시간 순서로 펼친다.

번호가 메시지의 순서다. 계산대가 판매에게 총액을 요청하면(1), 판매는 자기가 아는 각 판매 항목에게 소계를 묻고(2), 항목이 값을 돌려준다(3). 판매는 그것들을 합해 계산대에 총액을 반환한다(4). 실선 화살표는 요청이고 점선은 반환이다. 이 그림을 보면 2장에서 정한 책임 배분이 그대로 메시지의 흐름으로 드러난다. 총액 계산은 판매가, 소계는 판매 항목이 맡는다는 결정이 화살표의 방향으로 눈에 보인다.

여기서 두 다이어그램이 서로를 채워 준다는 점이 분명해진다. 상호작용 다이어그램에서 판매가 판매 항목에게 소계를 요청하려면, 클래스 다이어그램에 그 둘을 잇는 연관이 있어야 한다. 동적 흐름을 그리다 보면 정적 구조에 빠진 연결이 드러나고, 반대로 구조만 봐서는 몰랐던 실행 순서가 흐름에서 드러난다. 그래서 설계 리뷰에서는 대개 이 둘을 나란히 놓고 본다.


🟣 UML은 딱 필요한 만큼만

UML을 처음 배울 때는 모든 관계와 다중도를 빠짐없이 그려야 할 것 같았다. 그러나 실무에서 그렇게 그린 완벽한 다이어그램은 그리는 데 오래 걸리고, 코드가 조금만 바뀌어도 낡아 버려 아무도 보지 않게 된다. Larman과 애자일 모델링이 공통으로 강조하는 것은 반대 방향이다. 소통에 필요한 만큼만 그리고 합의가 끝나면 버린다는 태도다.

그래서 나는 UML을 두 자리에서 쓴다. 하나는 새 기능의 설계를 팀과 맞출 때다. 화이트보드에 클래스 몇 개와 화살표를 그려 책임 배분에 합의하고 나면 그 그림은 역할을 다한다. 다른 하나는 복잡한 흐름을 스스로 정리할 때다. 메시지 순서가 헷갈리면 상호작용 다이어그램을 손으로 그려 보는 것만으로 설계의 빈틈이 보인다. 두 경우 모두 문서를 남기려는 게 아니라 이해를 맞추려는 것이다.

이 태도에는 한계도 있다. 그림이 코드와 항상 일치하지는 않으니, UML을 진실의 원본으로 삼으면 안 된다. 최종 진실은 언제나 코드다. UML은 그 코드에 도달하기 전에 사람들의 머릿속을 맞추는 임시 도구로 둘 때 가장 쓸모가 있었다.


🟣 정리

  • UML은 방법론이 아니라 표기법이다. 좋은 설계를 만들어 주는 게 아니라 이미 내린 판단을 남과 같은 기호로 나누는 도구다.
  • 클래스 다이어그램의 핵심은 관계의 종류를 구분하는 데 있다. 상속·합성·연관·의존이 각기 다른 화살표로 그려지고, 그 차이가 변경의 파급 범위를 알려 준다.
  • 상호작용 다이어그램은 시간 위의 메시지 순서를 그린다. 책임 배분의 결정이 화살표 방향으로 그대로 드러난다.
  • 두 다이어그램은 서로를 채운다. 동적 흐름에서 빠진 구조가 보이고, 정적 구조에서 몰랐던 실행 순서가 흐름에서 드러난다.
  • UML은 소통에 필요한 만큼만 그리고 버린다. 진실의 원본은 언제나 코드다.

면접에서 "설계를 어떻게 공유하느냐"는 질문에, 나는 클래스 다이어그램으로 구조를, 상호작용 다이어그램으로 흐름을 나눈다고 답한다. iOS에서 MVVM 각 계층의 관계나 비동기 호출 순서를 팀과 맞출 때 실제로 쓰는 방식이다. 도메인 모델로 문제를 이해하고, GRASP로 책임을 나누고, UML로 그 설계를 나누는 세 단계가 이 책에서 밟은 전부다. 여기까지가 앞서 공부한 디자인 패턴 아래에 깔려 있던 기초였다.