들어가면서

이전 글에서 했던 결론은 이거였다.

공유 UI 패키지가 진짜로 공유한 건 컴포넌트가 아니라 동작·로깅·약속이었다.

그 글을 쓰고 나서 며칠, 같은 패키지를 다시 열어 호출 지점을 세고 부품을 비교해 봤다. 합쳐도 깨끗해 보이는 짝이 한두 개 있었다. 그리고 결국, 하나도 합치지 않았다.

이번 글은 그 데이터와, 합치지 않기로 한 이유에 대한 글이다.

가명으로 두자. promo-nudging-ui 라는 작은 공유 UI 패키지가 있고, 외부로 export 하는 메인 컴포넌트가 8개(N1 ~ N8) 있다고 하자. 호출 화면도 A ~ E 다섯 곳이라고 하자.

데이터 (1) — 호출 지점 분포

8개 컴포넌트가 앱/패키지 외부에서 import 되는 위치 수를 그대로 세었다.

컴포넌트 외부 호출 지점 수 사용 화면
N1 (이미지 + 텍스트 + CTA, 큰 카드) 2 (같은 화면 안) A
N2 (한 줄 컴팩트) 1 B
N3 (인라인 행, 분기 두 가지) 1 (한 파일에서 분기 2회) B
N4 (우측 적용가, 두 줄) 1 C
N5 (모션 + 한 줄, 데스크톱 분기) 1 D
N6 (모션 + 비율 + 가격 강조) 1 D
N7 (요약, 모션) 1 E
N8 (요약 + CTA) 1 E

핵심 관찰은 셋이다.

  • 8개 컴포넌트 → 외부 호출 파일은 5개.
  • 한 컴포넌트가 2개 이상의 서로 다른 화면에서 재사용된 사례: 0건.
  • 같은 화면 안의 인접 분기에 함께 쓰이는 케이스만 일부.

이 패키지가 “여러 화면에서 재사용” 한 횟수는 사실상 0회 였다.

데이터 (2) — 무엇이 공유되고, 무엇이 갈라졌나

같은 8개 컴포넌트의 본체를 한 줄씩 줄여 비교해 보면, 공유된 결갈라진 결이 분명히 갈린다.

✅ 공유된 것 (실질적 재사용)

공유 자산 사용 컴포넌트 수
라우팅 + 클릭 트래킹 컨테이너 6 / 8 동작
임프레션 / 클릭 로깅 훅 다수 로깅
placement data-attribute 부착 7 / 8 약속(placement)
식별 코드 → 이름/이미지 매핑 7 / 8 데이터 매핑
금액 포매팅 함수 6 / 8 포매팅 약속
렌더 가드 (값 ≤ 0 이면 null) 5 / 8 렌더 가드
색상 / 배경 토큰 4 / 8 토큰

❌ 공유되지 못한 것 (시각·구성)

  • 레이아웃: 한 줄 / 두 줄 / 우측 배치 / 이미지+텍스트+버튼 / 모션 / 데스크톱 분기 — 6가지
  • 이미지: 없음 / 페어 모션 / 직사각형 / 원형 — 4가지
  • 텍스트 위계: m, s, l, *-medium, *-bold 조합 — 6가지
  • 외곽 스타일: 라운드 4 / 라운드 6 / 그라데이션 보더 / 하단 보더 — 4가지
  • 액션: 없음 / Chevron / 두 가지 라벨의 텍스트 버튼 / “바로 적용” — 5가지
  • 분기 props: 모드 플래그, 적용 가능 여부, 총가, 포인트, 비율, 비활성, 최소 금액 — 각자 다른 슬롯

8개 모두가 공유하는 props 는 단 3개뿐이었다 — 식별 코드, placement, className. 나머지는 컴포넌트마다 자기 모양이었다.

데이터 (3) — 컴포넌트 간 유사도

여기서 한 단계 더 들어가, 각 컴포넌트가 어떤 부품을 가지고 있는지를 매트릭스로 정리해 봤다.

부품 / 컴포넌트 N1 N2 N3 N4 N5 N6 N7 N8
라우팅 컨테이너 (이동)        
페어 모션          
직사각형 카드 이미지            
원형(소) 카드 이미지              
우측 Chevron        
Action 버튼   (분기)        
비율(%) 표시    
할인액 표시  
우측 적용가 박스              
데스크톱 분기              

이 매트릭스로 어림 짝-유사도(부품 일치 비율)를 매겨 보면, 가장 닮은 짝은 다음과 같다.

유사도 차이
N2 ↔ N5 ★★★★★ 컨테이너 분기 · 텍스트 크기 · 패딩 정도
N7 ↔ N8 ★★★★ Action 버튼 유무 · 데스크톱 분기 유무
N5 ↔ N6 ★★★ 모션 강도(간단 vs shimmer)
N1 ↔ N8 ★★ Action 버튼은 공유, 시각 다름
N3 ↔ N4 같은 흐름의 한 줄짜리지만 본질 다름

보였지만 안 합쳤다 — 그리고 그게 맞았다

여기까지 보면 1·2 순위 짝(N2↔N5, N7↔N8)은 합쳐도 깨끗해 보였다. 사실 합쳤어야 했나 싶기도 했다. 그런데 시간이 지난 지금, 하나도 합치지 않았다. 처음에는 “시간이 없어서” 라고 생각했는데, 한참 뒤에 다시 보니 이유는 세 개였다.

1) 합칠 시간을 따로 빼기 어렵다

새 화면, 새 분기, 새 실험은 계속 들어온다. 합치는 작업은 “지금 안 해도 죽지 않는” 일이라 항상 뒤로 밀린다. 한 번 밀리고 나면, 그 사이에 한쪽에만 작은 분기 prop 이 또 들어가서 합치는 비용은 더 커져 있다. 밀린 리팩토링은, 다음 주가 되면 같은 일이 아니다.

2) 합쳐도, 한쪽만 바뀌면 다시 갈라야 한다

N2 ↔ N5 를 합쳤다고 치자. 그 다음 분기 중 하나만 디자인 정책이 살짝 바뀌면 — 모션이 바뀌거나, 버튼 라벨이 한쪽만 바뀌거나, 패딩이 어긋나면 — variant prop 이 둘에서 셋, 셋에서 넷으로 늘어나거나, 결국 다시 두 개로 갈라야 한다.

합치는 비용은 한 번이 아니라 두 번 든다 — 합칠 때 한 번, 갈라낼 때 또 한 번.

그리고 보통 갈라낼 때 더 비싸다. 이미 합쳐진 props 와 분기 로직을 다시 풀어 두 컴포넌트로 되돌리는 일은, 처음부터 따로 두는 것보다 항상 손이 더 간다.

3) 호출 지점이 작아 합친 효과도 작다

호출 지점이 5개다. 8 → 6 으로 줄어들어도, 호출 쪽 코드가 짧아지는 효과는 본질적으로 작다. 공유의 ROI 는 호출 지점 수에 비례한다. N=1 인 영역에서 합친다고 해 봐야 패키지 안의 이름 두 개가 하나로 되는 정도다 — 호출하는 사람 입장에서 달라지는 건 거의 없다.

그래서, UI 추상화는 쓸데 없는 일이었나

며칠 전 이 패키지를 다시 보면서 — “UI 추상화는 정말 쓸데 없다” 는 생각이 잠깐 들었다. 데이터를 한 번 더 보고 나니, 정확한 표현은 이쪽이었다.

쓸데 없는 게 아니라, 보통 너무 일찍 한다. 그리고 UI 가 아니라 동작을 추상화했어야 했다.

이 패키지가 6개월간 진짜로 절약해 준 건 — 데이터 (2) 의 “공유된 것” 표 — 컴포넌트가 아니라 동작·로깅·약속이었다.

  • 라우팅 + 클릭 트래킹 컨테이너
  • 임프레션 / 클릭 로깅 훅
  • 식별 코드 → 이름·이미지 매핑
  • 금액 포매팅 함수
  • 렌더 가드

이 자산들은 6개월간 화면이 변해도 같이 변하지 않았다. 새 화면이 추가될 때마다 useMoveTo*useTracking* 가 그대로 다시 쓰였다. 이쪽은 추상화가 잘 된 사례다.

반대로 합쳐 둔 UI 는 — 합쳤다면 — 6개월 동안 분기 props 만 늘어 있었을 가능성이 더 크다. 결과적으로 UI 는 합치지 않은 것이 옳았다.

추상화 전 체크리스트 (개정판)

다음에 같은 결의 영역을 만나면, 이 표를 한 장 떠올려 보려 한다.

질문 “예” 면 “아니오” 면
정말로 동일한 한 가지 모양이 3 화면 이상에서 쓰이는가? UI 컴포넌트로 끌어 올린다 UI 는 화면에 둔다
디자인 정책이 향후 6 ~ 12 개월 한 가지 모양으로 굳어 있을 가능성이 높은가? 컴포넌트로 둔다 동작만 분리, UI 는 화면에
진짜 공유 가치가 뷰가 아니라 동작·로깅인가? hook 으로 분리한다 컴포넌트 통째로 공유
합친 뒤 한쪽만 변할 가능성이 있는가? 합치지 않는다 (갈라낼 비용이 더 비싸다) 합쳐도 안전하다
호출 지점이 3개 이상인가? 합친 효과가 있다 ROI 가 거의 없다
변경 시 패키지만 수정하고 앱은 안 건드릴 만큼 격리 되는가? 외부 패키지로 둘 가치가 있다 같은 워크스페이스에 둔다

마치며 — 한 줄

변하는 건 화면에 두고, 변하지 않는 건 패키지에 둔다.

N = 1 인 컴포넌트는 합치지 말고(만들지도 말고), 그 안의 동작만 hook 으로 분리한다.

UI 를 추상화하지 않는 결정은, 추상화 능력의 부재가 아니다. 오히려 그 결정 옆에 useMoveTo..., useTracking..., useFormat... 같은 작은 훅을 정확히 빼 두었느냐 — 그게 진짜 능력이다.

이번에 배운 한 줄을 적어 두자.

UI 추상화는 쓸데 없는 게 아니라, 보통 너무 일찍 한다. 그리고 한 번 늦게 시작해도, 보통 늦은 게 아니다.