단위 변환기

Prev Next

::: (정보) ()
'단위 변환기: 질량'을 다운로드하려면 다음을 방문하십시오: [tulip](https://<span translate=).co/library/apps/unit-converter--mass">Library

‘단위 변환기: 부피’를 다운로드하려면 다음을 방문하세요: [tulip.co/library/apps/unit-converter--volume">Library](https://<span translate=)
:::

이 글에서는 저희 라이브러리 개발자 중 한 명인 타마스가 라이브러리에서 제공되는 단위 변환기 빌딩 블록 앱의 개발 배경에 대해 설명합니다.

테이블 대신 객체 목록을 사용한 이유

안녕하세요, 튤립(Tulip)의 타마스입니다.

이 단위 변환기 컴포넌트를 구축할 때 제가 내린 첫 번째 아키텍처 결정 중 하나는 테이블을 사용하지 않기로 한 것이었습니다. 대신, 모든 데이터를 기본 컴포넌트 변수로 초기화된 객체 목록 내에 저장하기로 했습니다 .

그 이유는 간단했습니다. 이 맥락에서 단위 변환은 운영 데이터가 아니라 수학적 상수처럼 작동하기 때문입니다. 관계는 순전히 곱셈적이며, 거의 변하지 않고, 런타임 편집, 집계, 필터링 또는 관계형 쿼리가 필요하지 않습니다. 테이블을 도입했다면 다음을 의미했을 것입니다: * 추가 인프라를 생성하고 유지 관리해야 함, * 쿼리 및 aggregations 구성, * 컴포넌트를 사용하는 모든 앱에 또 다른 의존성 계층을 추가해야 함.

이는 기능을 향상시키지 못한 채 복잡성만 증가시켰을 것입니다.

그래서 대신, 데이터셋을 컴포넌트 내부에 직접 내장했습니다:* 표준 기본 데이터셋: * _lengthToBaseDefaults * _massToBaseDefaults * _volumeToBaseDefaults

  • 동의어 사전:
    • _lengthUnitAliases
    • _massUnitAliases
    • _volumeUnitAliases

이 둘은 모두 기본 변수 값으로 존재합니다. 이를 통해 컴포넌트는 다음과 같은 특성을 유지합니다:* 이식성* 독립성* 결정론적 특성* 앱 간 복사가 용이함

데이터셋이 상수처럼 작동한다면, 이를 외부로 노출하기보다는 컴포넌트 내부에서 보호하는 것을 선호합니다.

왜 테이블을 사용하지 않는가?

테이블 기반 솔루션은 이 문제가 정당화할 수 없는 구조적 의존성을 초래할 것입니다.

단위 정의는 트랜잭션 데이터가 아닙니다. 구조적 정의일 뿐입니다. 이를 운영 레코드처럼 취급하는 것은 문제의 규모에 비해 과도한 조치입니다.

여기서 제 목표는 아키텍처적 비례성이었습니다. 즉, 문제가 진정으로 요구하는 만큼만 인프라를 사용하는 것입니다.

설계 원칙: 보호된 로직, 유연한 구성

이 컴포넌트는 다음 두 가지 핵심 개념을 따릅니다:1. 정규화된 기본 데이터 세트 2. 동의어 해결을 통한 입력 허용 범위

저는 의도적으로 수학적 진리와 사용자 유연성을 분리했습니다.* 정규화된 기본 데이터셋은 수학적 관계를 정의합니다.* 동의어 사전은 유연한 사용자 입력이 어떻게 해석될지를 정의합니다.

두 계층 모두 설계의 일부이며, 사후에 추가된 요소가 아닙니다.

정규 기반 모델에서 각 단위는 해당 도메인 내의 단일 기준 단위에 매핑됩니다. 모든 변환은 이 기준을 통해 이루어집니다. 이를 통해 하나의 정규화 경로가 생성되며, 모든 단위가 동일한 수학적 기준점을 공유하도록 보장합니다.

도메인 기본 단위

도메인별로 기본 단위를 명시적으로 고정했습니다:* 길이 → m* 질량 → kg* 부피 → L

이는 신중한 선택이었습니다:* 업계 표준입니다.* 반올림 오차를 최소화합니다.* 엔지니어에게 직관적인 시스템을 유지합니다.

정규 단위 키 (지원되는 단위)

모든 별칭 매핑은 다음 정규 키 중 하나로 해결되어야 합니다.

  • 질량 (기본: kg) µg, mg, g, kg, t, lb, oz
  • 길이 (기본: m) mm, cm, m, km, in, ft, yd, mi
  • 부피 (기본: L) ml, cl, µl, mm3, cm3, l, m3, fl_oz_us, pt_us, qt_us, gal_us

각 도메인에는 고유한 기본 데이터셋 객체 목록이 있습니다. 각 항목은 단위와 해당 factorToBase를 정의합니다.

변환은 항상 동일한 패턴을 따릅니다:* 입력 → 기준* 기준 → 대상

분기 처리 없음. 쌍 관리 없음.

보호된 내부 인터페이스

의도하지 않은 논리 수정을 방지하기 위해 핵심 변수들을 분리했습니다:* _inputValue* _inputUnit* _targetUnit* _conversionResult

핵심 엔진은 오직 이 변수들만을 의존합니다.

이 엔진을 둘러싸고 두 개의 얇은 구성 계층이 있습니다:* 01 – 사용자 구성: 입력 단계 외부 소스를 내부 변수로 매핑합니다.* 03 – 사용자 구성: 출력 단계 계산된 결과를 외부로 전달합니다.

이를 통해 유연한 통합을 허용하면서도 계산 코어를 안정적으로 유지합니다.

트리거 분석 – MAGIC의 작동 원리

보호된 MAGIC 트리거는 두 가지 논리 연산을 수행합니다: 1. 단위 동의어 해결 2. 표준 기반 정규화 적용

별칭 해결 계층

각 도메인에는 동의어 사전이 있습니다. 예를 들면 다음과 같습니다:* _lengthUnitAliases

각 객체에는 다음이 포함됩니다: * 원시 값 * 표준화된 값

예시:{ "raw": "mm", "canonical": "mm" }{ "raw": "millimeter", "canonical": "mm" }{ "raw": "m³", "canonical": "m3" }이를 통해 여러 표기 형식을 단일 정규화된 키에 매핑할 수 있습니다. 중요한 점은, 기본 데이터셋 내에서 항목을 중복 생성하지 않았다는 것입니다. 동의어 계층은 수학적 정의를 증식시키지 않으면서 입력의 변동성을 흡수하기 위한 목적으로만 존재합니다.

정규화된 기본 공식

정규화된 단위가 결정되면 공식은 다음과 같습니다.결과 = 입력값 × factorToBase(정규화된 입력값) ÷ factorToBase(정규화된 목표값)

트리거 예시 (길이 도메인)

(@Variable._inputValue * array_value_at_index( map_to_number_list(@Variable._lengthToBaseDefaults , 'factorToBase'),      array_index_of( map_to_text_list(@Variable._lengthToBaseDefaults , 'unit'), array_value_at_index( map_to_text_list(@Variable._lengthUnitAliases , 'canonical'),          array_index_of( map_to_text_list(@Variable._lengthUnitAliases , 'raw'), @Variable._inputUnit ) ) )    ))/array_value_at_index( map_to_number_list(@Variable._lengthToBaseDefaults , 'factorToBase'), array_index_of( map_to_text_list(@Variable._lengthToBaseDefaults , 'unit'),    array_value_at_index( map_to_text_list(@Variable._lengthUnitAliases , 'canonical'),      array_index_of( map_to_text_list(@Variable._lengthUnitAliases , 'raw'), @Variable._targetUnit ) ) ))구조적으로 보면, 이는 계층화된 논리입니다:* raw → canonical 변환* canonical → factor 변환* base를 통해 정규화

복잡해 보이지만, 엔진을 독립적으로 유지하고 중간 상태를 제거하기 위해 의도적으로 인라인으로 작성되었습니다.

이 구조를 선택한 이유

다음 세 가지 보장을 원했습니다:* 쌍의 폭발(pair explosion) 없음* 여러 경로로 인한 반올림 오차 없음* 단위 확장 시 트리거 재작성 없음

정규화된 기본 모델은 이 세 가지를 모두 충족합니다. 새로운 단위를 추가할 때는 정확히 하나의 factorToBase 항목을 추가합니다. 엔진은 변경되지 않습니다. 새로운 작성 형식을 지원할 때는 정확히 하나의 별칭 항목을 추가합니다. 수학적 계산은 변경되지 않습니다. 이러한 분리가 핵심 아키텍처 결정입니다.

다중 도메인 재사용: 동일한 엔진, 서로 다른 데이터셋

또 다른 의도적인 결정: 엔진 로직은 도메인에 구애받지 않습니다. 길이, 질량, 부피는 동일한 구조적 트리거를 공유합니다. 데이터셋 변수만 달라질 뿐입니다. 세 가지 서로 다른 변환기를 만드는 대신, 하나의 표준 엔진을 구축하고 데이터셋이 도메인을 정의하도록 했습니다. 이를 통해 인지 부하를 줄이고 구성 요소의 동작을 예측 가능하게 유지합니다.

확장성이 뛰어난 이유

확장은 데이터셋 수준에서 이루어집니다. 단위를 추가할 때 다음이 필요하지 않습니다: * 트리거 표현식 수정, * 조건부 분기 추가, * 로직 중복.

알고리즘은 안정적으로 유지되며, 데이터셋만 진화합니다.

컴포넌트 확장

새로운 표준 단위 추가

  • 관련된 _ToBaseDefaults 데이터셋을 확장합니다.
  • 단위와 factorToBase를 추가합니다.
  • 트리거는 변경하지 마십시오.

새로운 표기 형식 추가

  • 관련된 _UnitAliases 사전(dictionary)을 확장합니다.
  • raw 및 해당 표준 키를 추가합니다.
  • 기본 데이터셋은 변경하지 마십시오.

이는 의도된 것입니다: 수학적 정의를 불필요하게 늘리지 않으면서도 사용자 친화적인 유연성을 확보하고 싶었기 때문입니다.

오류 처리 (안전한 조회 패턴)

두 가지 오류 발생 지점이 있습니다: 1. 별칭 사전에서 'Raw' 단위를 찾을 수 없는 경우 2. 기본 데이터 세트에서 'canonical' 단위를 찾을 수 없는 경우

두 조회 중 하나라도 실패하면 계산이 실행되지 않습니다.

이를 통해 다음이 보장됩니다: * 오래된 값 없음 * 눈에 띄지 않는 계산 오류 없음 * 결정론적 동작

왜 단위와 계수를 별도의 배열로 분리하지 않았을까요?

단위와 요인을 별도의 배열에 저장하면 위치 의존성 위험이 발생합니다. 한 목록의 순서가 바뀌고 다른 목록의 순서가 바뀌지 않으면 시스템이 눈에 띄지 않게 오작동합니다. 구조화된 객체 내에 단위와 factorToBase를 함께 저장함으로써, 이러한 구조적 취약성을 제거했습니다.

핵심 철학

이 컴포넌트는 의도적으로 구조는 단순하고 동작은 엄격하게 설계되었습니다. * 수학적 연산은 중앙 집중화되어 있습니다. * 엔진은 보호됩니다. * 유연성은 주변부에서 발휘됩니다.

지원되는 단위 집합은 안전하게 진화할 수 있으면서도 논리가 시간이 지나도 안정적으로 유지되도록 설계했습니다. 엔진은 변하지 않습니다. 데이터 세트만 확장됩니다.

참여하기

커뮤니티에 참여하여 개선 사항을 공유하고, 추가 단위를 제안하며, 문제를 보고하거나 아키텍처 결정 사항에 대해 논의해 보세요.