[하루 한 시간] 모델링 시리즈: 측도 공부하기

이종호·2025년 10월 7일

하루 한 시간

목록 보기
9/13

출처: https://kciter.so/posts/modeling-series-measure/

코드에 의미를 부여하는 방법: '측도' 모델링에서 얻은 4가지 통찰

Introduction: 숫자에 숨겨진 의미를 찾아서

소프트웨어 개발에서 우리는 수많은 데이터를 원시 타입으로 다루곤 합니다.
예를 들어, 어떤 객체의 무게를 표현할 땨 val weight: Int = 100과 같이 코드를 작성하는 것은 매우 흔한 일입니다.

하지만 이 숫자 100은 그 자체로 아무론 의미를 가지지 못합니다.
이 모호함은 변수명에만 의존해 의미를 오츄하게 만들어 미묘한 버그의 원인이 됩니다.

이 문제를 해결하기 위해 데이터에 의미와 규칙을 직접 부여하는 강력한 모델링 기법이 있다.
바로 '측도(Measure)'입니다.
측도는 단순히 cm, kg같은 물리량 뿐만 아니라, 동물을 세는 '마리', 책을 세는 '권', 화폐 단위인 '달러'까지, 정량적으로 표현 가능한 모든 것을 아우르는 개념


1. 숫자는 숫자일 뿐, 의미를 가지지 않는다.

가장 근본적인 문제는 원시 타입이 의미를 담지 못한다는 것
val weight: Int = 100val length: Int = 100이라는 코드가 있을 때,
컴파일러는 두 변수 모두 Int 타입이므로 이 둘을 구분하지 못합니다.
오직 변수명만이 의리를 암시할 뿐이며, 이는 개발자의 실수로 무게 변수에 길이를 할당하는 오류를 막을 수 없습니다.

도메인 주도 설계에서 이 문제의 해답을 찾을 수 있습니다.
바로 추상적인 개념을 코드에서 구체적인 타입, 즉 값 객체로 만드는 것입니다.
'무게'라는 개념을 위한 전용 클래스를 만들어 봅니다.

class Weight(val value: Int){
  init {
    require(value >= 0) { "Weight must be positive" }
  }
}

이 간단한 변화는 코드의 안저성과 명확성을 극적으로 향상시킵니다.
이제 무게는 Weight라는 구체적인 타입을 갖게 되어, 실수로 다른 값을 할당하는 것을 원천적으로 차단합니다.
더 중요한 것은 init블록입니다.
이 블록은 "무게가 0이상이어야 한다"는 비즈니스 규칙을 코드 레벨에서 강제합니다.
값 객체는 단순한 레퍼 가 아니라 도메인 규칙의 수호자인 셈입니다.
이것이 바로 안전하고 읽기 쉬운 소프트웨어를 만드는 첫걸음입니다.


2. 'kg'과 '달러'는 사실 같은 개념이다.

킬로그램, 달러, 개는 현실레서 전혀 다른 개념처럼 보입니다.
하지만 모델링의 관점에서 보면 이들은 놀랍게도 하나의 추상적인 모델로 통합될 수 있습니다.

이들을 하나로 묶는 핵심 캐념은 바로 '측도'입니다. 소스코드에 제시된 범용 모델은 세 가지 핵시 요소로 구성됩니다.

  • Measure: 양을 표현하는 모든 값(ex: 5Kg, 10달러)을 나타내는 최상위 인터페이스이빈다.
  • Unit: 측정 단위를 표현하는 인터페이스입니다. kg, USD 등이 여기에 해당합니다.
  • UnitSystem: 연관된 단위들의 집합과 그들 사이의 변환 규칙을 관리합니다.

이 모델의 진정한 힘의 제네릭을 활용한 타입 안전성 확보에 있습니다. Measure 인터페이스는 다음과 같이 설계됩니다.

interface Measure<T : Measure<T>>: Comparable<T> {
  fun add(other: T): T
  //
}

여기서 사용된 Measure<T: Measure<T>>는 F-Bounded polymorphism이라는 패턴입니다.
이 패턴은 add와 같은 연산의 인자 타입을 자기 자신으로 제한합니다.
즉, Weight 클래스가 Measure<Weight>를 구현하면, add 메서드는 오직 다른 Weight 객체만 받을 수 있게 됩니다.
Weight와 Distance를 더하는 논리적 오류가 컴파일 시점에 차단되는 것입니다.
이처럼 하나의 추상 모델로 물리량부터 화폐까지 모든 정량적 데이터를 타입-안전하게 다루는 시스템을 구축할 수 있습니다.


3. 하지만 화폐는 특별하다: 고정되지 않는 단위의 세계

범용적인 '측도'모델은 매우 강력하지만, 모든 축도를 동일하게 취급할 수는 없습니다.
그중 가장 중요하고 특별한 사례가 바로 화폐입니다.

화폐가 물리적 수량과 근본적으로 다른 점은 단위간 변환 규칙에 있습니다.
1Kg은 언제나 1,000g이라는 고정된 변환 계수를 갖습니다.
하지만 화폐의 단위 변환, 즉 환율은 시간에 따라 계속해서 변동합니다.
이 문제를 해결하기 위해 실무에서는 모든 통화 쌍의 환율을 관리하는 대신, 하나의 기준 통화(baseUnit, ex: USD)를 정하고 다른 모든 통화는 이 기준 통화를 통해 변환하는 전략을 사용합니다.

이 외에도 화폐는 USD, KRW와 같은 ISO 코드, 국가별 문화에 맞는 포멧팅, 세금이나 할인 계산을 위한 퍼센트연산 등 고유한 요구사항을 가집니다.
화폐뿐만 아니라 데이터 크기, 시간, 온도와 같은 특수한 규칙을 가진 도메인은 많습니다.
화폐를 단순히 또 다른 물리량처럼 취급하려는 시도는 흔하지만 치명적인 실수로 이어질 수 있습니다.


4. 타입 스스로 계산하게 하라: 거리 % 시간 = 속도

잘 설계된 측도 모델은 단순히 값을 표현하는 것을 넘어, 타입 시스템 자체가 션실 세계의 물리 법칙을 강제하는 수준까지 나아갈 수 있습니다.
이는 복합 측도와 파생 측도를 통해 실현됩니다.

  • **복합 측도(Composite Measures)**: 두 개 이상의 기본 측도를 조합하여 새로운 개념의 측도를 만들 수 있습니다. 대표적인 예가 '속도'입니다. 속도는 거리와 시간을 조합하여 km/s와 같은 새로운 단위를 만듭니다. 여기서 핵심은 타입 간의 연산이 새로운 타입을 반환하도록 설계하는 것입니다.
  • 속도 * 시간 = 거리라는 물리 법칙이 Velocity타입의 multiply 메서드 시그니처에 명확하게 표현됩니다.
  • **파생 측도(Deruved Measure)**: 기존 측도에 특정 연산을 적용하여 새로운 차원의 측도를 파생시킬 수 있습니다. 가장 직관적인 예는 거리와 거리를 곱하여 면적을 구하는 것입니다.
  • m * m = m^2라는 법칙이 코드의 타입 연산으로 그대로 구현됩니다. Distance 객체 2개를 연산하면 Area라는 전혀 다른 타입이 생성되는 것입니다.

이 접근법의 진정한 가치는 안전성과 표현혁에 있습니다.
타입 시스템이 스스로 미터와 초는 더할 수 없지만, 나누면 속도가 된다는 규칙을 강제합니다.


Conlusion: 단신의 코드는 무엇을 '측정'하고 있나요?
단순한 원시 타입을 넘어, '측도'라는 풍부한 모델을 도입하는 것은 코드에 명확성, 안전성, 그리고 표현력을 불어넣는 과정입니다.
우리는 숫자가 가진 의미의 부재 문제에서 시작하여, 값 객체로 도메인 규칙을 보호하고, 제네릭을 통해 타입-안전한 추상 모델을 구축했으며, 나아가 속도나 면적과 같은 새로운 개념을 타입 연산으로 파생시키는 여정을 살펴보았습니다.

profile
코딩은 해봐야 아는 것

0개의 댓글