
무언가를 다른 형식으로 바꿔주는 프로그램, 한 번쯤은 만들어 보셨을 거예요. 마크다운을 HTML로, JSON을 YAML로, PNG를 JPG로 바꾸는 그런 도구들요. 처음엔 정말 간단해 보이거든요. 그런데 지원해야 할 포맷이 하나둘 늘어나는 순간, 코드가 손쓸 수 없이 엉켜버리는 경험을 하게 됩니다. 이번에 소개할 글은 바로 그 '변환 엔진'을 어떻게 하면 우아하게 설계할 수 있는지를 다루고 있어요. 핵심은 "무질서한 조합 덩어리를 선형적인 우아함으로 바꾼다"는 거예요.
먼저, 조합 폭발이라는 함정
가장 먼저 부딪히는 문제가 뭐냐면, 변환 경우의 수가 기하급수적으로 늘어난다는 거예요. 포맷이 A, B, C 세 개라고 해볼게요. A를 B로, A를 C로, B를 A로, B를 C로... 이렇게 따지면 변환기를 6개 만들어야 해요. 공식으로 보면 N개의 포맷에 대해 N×(N-1)개가 필요하거든요. 5개면 20개, 10개면 무려 90개예요. 이걸 조합 폭발(combinatorial explosion)이라고 불러요. 포맷 하나 추가할 때마다 만들어야 할 변환기가 폭발적으로 늘어난다는 뜻이죠.
이게 왜 무섭냐면, 단순히 코드 양만 많아지는 게 아니라 유지보수가 지옥이 되거든요. JSON 파싱 규칙 하나를 고치면, JSON과 관련된 모든 변환기를 다 손봐야 해요. 버그가 숨을 곳이 90군데나 생기는 셈이죠.
핵심 아이디어: 허브 앤 스포크, 그리고 중간 표현
이 글이 제안하는 해법의 열쇠는 중간 표현(Intermediate Representation, IR)이에요. 이게 뭐냐면, 모든 포맷이 공통으로 거쳐 가는 '내부 전용 형식'을 딱 하나 만드는 거예요. 자전거 바퀴를 떠올리면 쉬워요. 가운데 축(허브)이 있고 거기서 바퀴살(스포크)이 사방으로 뻗어 나가잖아요. 바퀴살끼리는 서로 직접 연결되지 않고, 무조건 가운데 축을 거쳐서 이어지죠.
변환 엔진도 똑같아요. A를 B로 직접 바꾸지 않고, A → 중간 표현 → B 순서로 돌아가는 거예요. 그러면 각 포맷마다 "내 형식을 중간 표현으로 바꾸는 변환기(입력기)" 하나, "중간 표현을 내 형식으로 바꾸는 변환기(출력기)" 하나만 있으면 돼요. N개 포맷이면 입력기 N개 + 출력기 N개 = 2N개. 아까 N×(N-1)였던 게 2N으로 줄어든 거예요. 10개 포맷 기준으로 90개가 20개로 확 줄어드는 거죠. 이게 제목에서 말하는 '선형적 우아함'이에요.
장점이 또 하나 있어요. 새 포맷을 추가하고 싶을 때, 그 포맷의 입력기와 출력기 딱 2개만 만들면 기존의 모든 포맷과 자동으로 호환돼요. 다른 변환기는 건드릴 필요가 전혀 없거든요. 확장성이 확 좋아지는 거죠.
사실 업계에서 검증된 패턴이에요
재밌는 건 이게 새로운 발명이 아니라는 점이에요. 우리가 매일 쓰는 도구들이 이미 이 구조를 쓰고 있거든요.
대표적으로 문서 변환 도구인 Pandoc이 그래요. 마크다운, Word, LaTeX, HTML 등 수십 가지 포맷을 서로 변환해 주는데, 내부적으로 'AST(추상 구문 트리)'라는 중간 표현으로 한 번 바꾼 다음 원하는 포맷으로 내보내요. 컴파일러 쪽에서는 LLVM이 똑같은 철학이에요. C, Rust, Swift 같은 여러 언어를 LLVM IR이라는 공통 중간 언어로 바꾼 뒤, 그걸 다시 각 CPU(인텔, ARM 등)용 기계어로 내보내거든요. 영상 처리의 ffmpeg도 디코딩→내부 표현→인코딩 구조를 따르고요.
즉, 규모가 큰 변환 시스템은 거의 예외 없이 이 허브 앤 스포크 패턴으로 수렴한다는 거예요. 이 글의 가치는 그 원리를 작은 엔진 설계 단계에서부터 의식적으로 적용한다는 데 있어요.
한국 개발자에게 주는 시사점
이 패턴은 '포맷 변환'에만 쓰이는 게 아니에요. 여러 외부 시스템을 연동하는 일이라면 어디든 통해요. 예를 들어 결제 모듈을 토스, 카카오페이, 스트라이프 등 여러 PG사와 붙일 때, 각 PG사 응답을 곧장 우리 비즈니스 로직에 연결하면 조합 폭발이 일어나요. 대신 '우리 서비스 공통 결제 모델'이라는 중간 표현을 하나 두고, 각 PG사마다 어댑터만 붙이면 깔끔해지죠. 환율 변환, 다국어 처리, 여러 메신저 알림 연동도 마찬가지예요.
핵심 교훈은 이거예요. N개를 N개와 직접 연결하려는 충동이 들 때가 바로 중간 표현을 도입할 타이밍이라는 거죠. 당장은 추상 계층 하나 만드는 게 귀찮게 느껴질 수 있지만, 세 번째 포맷이 추가되는 순간 그 투자는 반드시 보상으로 돌아옵니다.
마무리
한 줄로 정리하면, "모든 변환을 거쳐 가는 공통의 중간 표현 하나가 N²의 혼돈을 2N의 질서로 바꾼다"예요.
여러분이 지금 만지고 있는 코드 중에, 사실은 중간 표현을 두면 훨씬 깔끔해질 'A를 B로 직접 변환하는 로직'이 숨어 있지 않나요? 반대로, 포맷이 영원히 2~3개뿐이라면 중간 표현이 오히려 과한 설계일 수도 있는데요. 여러분은 어느 시점에 이 추상 계층을 도입하는 게 맞다고 보시나요?
🔗 출처: Hacker News