PUBLISHED

UML

작성일: 2024.07.22

UML

소프트웨어 시스템은 코드로 구현되지만, 코드만으로 시스템 전체를 설명하기는 어렵다. 특히 여러 명이 함께 개발하거나, 시간이 지나 다시 코드를 바라보는 상황에서는 “이 시스템이 어떤 구조를 가지고 있고, 왜 이렇게 설계되었는지”를 빠르게 파악하기가 쉽지 않다. 이런 맥락에서 설계 의도를 압축적으로 전달할 수 있는 시각적 표현은 여전히 중요한 역할을 한다.

UML(Unified Modeling Language)은 이러한 문제를 해결하기 위해 등장한 표준 모델링 언어다. UML은 특정 언어나 프레임워크에 종속되지 않고, 시스템을 구성하는 요소와 그 관계, 그리고 동작 방식을 공통된 기호와 규칙으로 표현한다. 중요한 점은 UML의 목적이 문서를 남기는 데 있지 않다는 것이다. UML은 설계를 강제하는 규칙이 아니라, 사고를 정리하고 팀 간의 이해를 맞추기 위한 도구에 가깝다.

실무에서 UML이 부담스럽게 느껴지는 이유는 종종 “모든 것을 정확히 그려야 한다”는 오해 때문이다. 하지만 좋은 UML 다이어그램은 완벽함보다 명확함을 우선한다. 설명하려는 대상과 목적에 맞는 수준으로 단순화되어 있어야 하며, 필요 이상으로 많은 정보를 담을수록 오히려 전달력은 떨어진다.

결국 UML에서 중요한 것은 정확한 묘사가 아니라 적절한 추상화다. 추상화란 세부 구현을 감추는 것이 아니라, 지금 이 순간 이해해야 할 핵심만을 드러내는 행위에 가깝다. 어떤 클래스가 내부적으로 어떤 필드를 가지는지보다, 그 클래스가 어떤 책임을 수행하고 다른 요소와 어떤 관계를 맺는지가 더 중요할 수 있다.

구조 다이어그램(Structural Diagram)

구조 다이어그램은 시스템을 구성하는 요소들과 그 사이의 관계를 표현한다. 이 다이어그램의 초점은 “무엇이 어떤 역할을 맡고 있는가”에 있으며, 코드가 실제로 어떻게 실행되는지보다는 설계 단계에서의 구조적 판단을 드러내는 데 있다. 다시 말해, 구조 다이어그램은 시스템을 한 시점에 멈춰 세워 놓고 내부를 들여다보는 도구라고 볼 수 있다.

이러한 다이어그램은 특히 시스템의 규모가 커질수록 가치가 커진다. 개별 클래스나 모듈의 구현을 모두 따라가지 않더라도, 전체적인 구성과 의존 관계를 빠르게 파악할 수 있기 때문이다. 또한 구조 다이어그램은 팀 내에서 설계에 대한 공통된 이해를 형성하는 데 중요한 역할을 하며, 변경이 발생했을 때 그 영향 범위를 가늠하는 기준점이 되기도 한다.

클래스 다이어그램(Class Diagram)

클래스 다이어그램은 구조 다이어그램 가운데서도 객체지향 설계를 설명하는 데 가장 핵심적인 역할을 한다. 이 다이어그램은 시스템에 어떤 클래스들이 존재하는지, 각 클래스가 어떤 상태와 행위를 가지는지, 그리고 클래스들 사이에 어떤 관계가 형성되어 있는지를 한눈에 보여준다.

클래스 다이어그램의 중요한 목적은 단순히 클래스 목록을 나열하는 데 있지 않다. 오히려 각 클래스가 맡고 있는 책임의 경계를 드러내고, 책임이 어떻게 분리되고 협력하는지를 설명하는 데 의미가 있다. 잘 그려진 클래스 다이어그램은 코드 없이도 도메인의 주요 개념과 객체 간 역할 분담을 이해할 수 있게 만든다.

실무에서 클래스 다이어그램은 도메인 모델을 설계할 때 특히 유용하다. 엔티티, 값 객체, 서비스와 같은 개념들이 어떻게 나뉘어야 하는지 논의할 때, 클래스 다이어그램은 추상적인 대화를 구체적인 구조로 끌어내리는 역할을 한다. 또한 설계 변경이 필요할 때, 클래스 간 관계를 기준으로 영향 범위를 빠르게 파악할 수 있다는 장점도 있다.

다만 클래스 다이어그램은 구현 상세를 모두 담아내는 문서가 아니다. 모든 필드와 메서드를 표현하려고 하기보다는, 클래스의 성격과 책임이 드러나는 핵심 요소만 남기는 것이 바람직하다. 클래스 다이어그램의 가치는 정보의 양이 아니라, 구조를 얼마나 명확하게 전달하느냐에 의해 결정된다.

기호

접근 제어자

의미

+

public

외부 접근 가능

-

private

클래스 내부 전용

#

protected

상속 관계에서 접근 가능

__

static

클래스 레벨 멤버

{final}

final 메서드

재정의 불가

<<final>>

final 클래스

상속 불가

{readOnly}

읽기 전용

값 변경 불가

{leaf}

리프 클래스

더 이상 확장 불가

untitled
TS
+------------------------------------+
| <<interface>>                      |
| Payable                            |
+------------------------------------+
| + pay(amount): void                |
+------------------------------------+


+------------------------------------+
| <<abstract>>                       |
| AbstractPayment                    |
+------------------------------------+
| # id: string                       |
| __# DEFAULT_FEE: number__          |
+------------------------------------+
| + pay(amount): void {final}        |
| # calculateFee(): number           |
| __+ generateId(): string__         |
+------------------------------------+


+------------------------------------+
| <<final>> {leaf}                   |
| CreditCardPayment                  |
+------------------------------------+
| - cardNumber: string {readOnly}    |
+------------------------------------+
| + mask(): string                   |
+------------------------------------+

행위 다이어그램(Behavioral Diagram)

앞에서 살펴본 구조 다이어그램이 시스템의 정적인 모습을 설명한다면, 행위 다이어그램은 시스템이 어떻게 동작하는지, 즉 시간의 흐름 속에서 어떤 상호작용이 발생하는지를 표현한다. 구조 다이어그램이 설계의 뼈대를 보여준다면, 행위 다이어그램은 그 뼈대 위에서 실제로 벌어지는 움직임을 설명하는 도구라고 볼 수 있다.

행위 다이어그램의 핵심 관점은 “이 코드가 어떤 순서로 실행되는가”가 아니다. 대신 객체나 사용자, 시스템 요소들이 어떤 역할로 참여하고, 어떤 메시지를 주고받는가에 초점을 둔다. 그래서 행위 다이어그램은 구현 상세를 설명하기보다는, 흐름과 책임의 분리를 명확히 하는 데 더 큰 가치를 가진다. 특히 요구사항 분석이나 설계 리뷰 단계에서 행위 다이어그램은 큰 힘을 발휘한다. 구조 다이어그램만으로는 드러나지 않는 실행 흐름, 조건 분기, 사용자와 시스템 간의 상호작용을 한 눈에 보여주기 때문이다.

시퀀스 다이어그램(Sequence Diagram)

시퀀스 다이어그램은 객체나 시스템 구성 요소들이 시간 순서에 따라 어떤 메시지를 주고받는지를 표현하는 다이어그램이다. 이름 그대로 “순서(sequence)”가 가장 중요한 요소이며, 위에서 아래로 시간이 흐른다.

이 다이어그램의 목적은 로직을 상세히 설명하는 데 있지 않다. 대신 하나의 시나리오를 기준으로, 누가 시작하고, 누가 응답하며, 책임이 어디로 이동하는지를 명확히 보여준다. 그래서 시퀀스 다이어그램은 API 호출 흐름이나 요청–응답 구조를 설명할 때 특히 자주 사용된다.

아래는 결제 요청 흐름을 단순화한 시퀀스 다이어그램 예시다.

untitled
User        Controller        Service        Repository
 |              |               |               |
 |-- request -->|               |               |
 |              |-- pay() ----->|               |
 |              |               |-- save() ---->|
 |              |               |<-- result ----|
 |              |<-- response --|               |
 |<-- result ---|               |               |

이 다이어그램에서 읽어야 할 정보는 다음과 같다.

시퀀스 다이어그램은 하나의 유스케이스, 하나의 시나리오를 기준으로 그리는 것이 중요하다. 여러 흐름을 한 장에 억지로 담기 시작하면, 다이어그램은 설명 도구가 아니라 해석이 필요한 퍼즐이 된다.

유스케이스 다이어그램(Use Case Diagram)

유스케이스 다이어그램은 시스템을 사용자 관점에서 바라본다. 이 다이어그램은 내부 구조나 구현 방식에는 관심이 없고, 오직 “이 시스템이 사용자에게 어떤 기능을 제공하는가”에만 집중한다.

그래서 유스케이스 다이어그램은 개발자뿐 아니라 기획자, 디자이너, 이해관계자와 소통할 때 특히 유용하다. 시스템의 범위를 명확히 하고, 무엇을 제공하고 무엇을 제공하지 않는지를 선명하게 드러내기 때문이다.

아래는 간단한 유스케이스 다이어그램 예시다.

untitled
CSS
[사용자]
   |
   |-- (로그인)
   |-- (결제하기)
   |-- (주문 조회)

이 다이어그램에서 중요한 점은 다음과 같다.

유스케이스 다이어그램을 화면 흐름도나 기능 명세서처럼 사용하는 경우가 있는데, 이는 흔한 오해다. 유스케이스 다이어그램의 목적은 상세 설명이 아니라 합의다. 이 시스템이 어떤 책임을 가지는지에 대해 공통된 인식을 만드는 것이 핵심이다.