아날로그 리팩터 틸트‑시프트: 종이 위에 코드베이스를 축소해 숨겨진 의존성을 드러내기
시스템을 종이 위로 ‘축소’해 그려 보고, 의존성을 시각화하고, 코너생스(connascence)를 활용해 유해한 결합을 찾아내며 더 안전하고 점진적인 리팩터링을 설계하는 방법을 다룹니다.
아날로그 리팩터 틸트‑시프트: 종이 위에 코드베이스를 축소해 숨겨진 의존성을 드러내기
틸트‑시프트(tilt‑shift) 사진을 본 적이 있다면 그 느낌을 알 겁니다. 실제 크기의 도시가 갑자기 미니어처 모형처럼 보이죠. 관점을 바꾸면 복잡함이 다뤄질 만한 크기로 작아집니다. 그냥 봐서는 보이지 않던 패턴이 눈에 들어옵니다.
소프트웨어 아키텍처도 똑같이 할 수 있습니다.
클라우드 규모의 시스템이 수십, 수백 개의 서비스로 뻗어 나가면, 머릿속이나 IDE 화면만으로는 리팩터링을 제대로 사고하기가 거의 불가능해집니다. 이때 필요한 건 시스템을 축소해서 보고, 의존성을 시각화하고, 그 새로운 관점으로 더 안전하고 효과적인 변경을 설계하는 일입니다.
이 글에서 말하는 아날로그 리팩터 틸트‑시프트란, 코드베이스 전체를 종이(또는 그에 준하는 디지털 캔버스) 위에 ‘축소 모형’처럼 펼쳐 놓아, 숨어 있던 의존성과 위험한 결합 관계를 눈에 띄게 만드는 기법입니다.
코드를 읽는 것만으로는 부족하다: 시스템을 ‘봐야’ 하는 이유
현대 아키텍처—특히 마이크로서비스나 이벤트 기반 시스템—는 빽빽한 의존성 네트워크를 형성합니다.
- 서비스 A는 B를 동기 호출한다.
- B는 이벤트를 발행하고, C와 D가 이를 구독한다.
- E는 B도 변경하는 공용 DB 테이블에 의존한다.
- F는 A가 만들어낸 아티팩트를 정리하는 스케줄 잡을 가진다.
하나씩 떼어 놓고 보면 각 상호작용은 그럴듯합니다. 하지만 모두 합쳐 보면 불투명해집니다.
서비스 의존성을 시각화하기
트레이스, 로그, 매니페스트, 코드 스캐닝 등에서 의존성 그래프를 생성해 주는 도구들은 이런 틸트‑시프트 관점을 제공합니다. 노드는 서로 밀어내고, 엣지는 관련 서비스들을 끌어당기는 force‑directed graph(강제 지향 그래프) 같은 형태로 보면, 막연한 “서비스가 많다”는 감상이 사고 가능한 지도로 바뀝니다.
- 촘촘히 얽힌 서비스들은 하나의 “동네”처럼 클러스터로 보입니다.
- 인증, 결제 같은 횡단 관심사가 되는 서비스는 허브 노드로 드러납니다.
- 고립되었거나 거의 건드리지 않는 서비스는 그래프 가장자리에서 눈에 띕니다.
이 그래프를 출력하거나 직접 스케치해 두면, 문자 그대로 펜을 들고 아키텍처 위를 걸어 다닐 수 있게 됩니다. 그러면 이렇게 할 수 있습니다.
- 복잡도가 높은 구역을 동그라미로 표시한다.
- 엣지에 “동기 REST 호출”, “비동기 pub/sub”, “직접 DB 접근” 같은 메모를 단다.
- 통합, 추출, 폐기(deprecation) 후보를 표시한다.
이건 그저 예쁜 그림이 아닙니다. 의미 있는 리팩터링을 위한 선행 작업입니다.
보기 좋은 그림에서 실질적인 결정으로
의존성 시각화가 빛을 발하는 순간은 구체적인 의사결정과 연결될 때입니다.
- 서비스 오너를 돕기: 서비스 오너는 자기 서비스가 누구에게 의존하고, 누가 자기에게 의존하는지 한눈에 볼 수 있습니다. “우린 중요해요” 같은 막연한 인식이 *“이 여섯 서비스가 우리를 호출하고, 이 세 개가 우리가 발행하는 이벤트를 구독하며, 이 하나는 우리 DB를 몰래 폴링하고 있다”*는 구체적인 지도으로 바뀝니다.
- 마이그레이션 계획하기: 사용자 인증, 결제, 재고 같은 핵심 기능을 바꾸려 할 때, 그래프는 영향받는 모든 경로를 드러냅니다. 감으로 ‘한 방에’ 가는 대신, 단계별 마이그레이션을 설계할 수 있습니다.
- 숨겨진 결합 관계 찾아내기: 예를 들면 이런 것들을 발견합니다.
- 다른 서비스의 API를 우회해 그 서비스의 데이터베이스를 직접 읽는 서비스
- 여러 서비스가 각각 비슷하지만 조금씩 다른 버전의 동일한 비즈니스 로직을 구현한 경우
- 임시로 넣었지만 영원히 남아 버린 feature flag나 라우팅 레이어
하지만 그래프만으로는 각 의존성이 얼마나 심각한지는 알 수 없습니다. 그럴 때 필요한 것이 결합을 설명하는 언어입니다.
코너생스(Connascence): 결합을 위한 유용한 언어
**코너생스(connascence)**는 소프트웨어 설계에서 결합을 구조적으로 이야기할 수 있게 해 주는 개념입니다. 실무적으로는 세 가지 축에 집중하면 됩니다.
- 강도(Strength) – 이 의존성을 바꾸는 게 얼마나 어려운가?
- 위치(Locality) – 의존하는 요소들이 코드, 아키텍처, 조직에서 얼마나 떨어져 있는가?
- 차수(Degree) – 이 의존성으로 함께 묶여 있는 요소가 몇 개나 되는가?
강도: 변경이 얼마나 고통스러운가?
코너생스의 강도가 셀수록, 무언가를 바꿀 때 요구되는 리팩터링 노력도 커집니다. 예를 들어:
- 두 함수가 같은 매직 문자열에 의존해서 항상 함께 바뀌어야 한다: 중간 정도 강도.
- 열 개의 서비스가 동일한 JSON 스키마의 특정 필드 이름에 의존한다: 더 높은 강도.
- 여러 서비스가 공유 테이블에 의존하면서 동일한, 문서화되지 않은 불변 조건에 기대고 있다: 매우 높은 강도.
그래프를 볼 때, 엣지에 **변경 시 통증(pain of change)**을 메모해 보세요.
- “이 필드를 바꾸면 7개 서비스가 깨진다.”
- “이 엔드포인트는 내부 관리자 도구 1개만 쓴다.”
코너생스 강도가 높은 엣지는 리팩터링 우선순위 후보입니다.
위치: 의존성이 얼마나 멀리 뻗어 있는가?
하나의 클래스나 모듈 안의 결합은, 서비스나 팀을 넘어서는 결합에 비해 훨씬 덜 문제입니다.
- 로컬 코너생스(한 파일·한 서비스 내부)는 관리하기 쉽습니다.
- 비로컬 코너생스(서비스, 팀, 리포지터리를 가로지르는 결합)는 조정 비용과 장애 영향 범위를 증폭시킵니다.
그래프는 비로컬 의존성을 눈에 보이게 드러내고, 코너생스는 그중 어떤 것이 특히 위험한지 판단하는 기준을 제공합니다.
차수: 얼마나 많은 것이 함께 묶여 있는가?
- 1:1 의존성: 낮은 차수.
- 스키마 변경이 30개의 다운스트림 소비자에 영향을 준다: 매우 높은 차수.
축소된 아키텍처 위에서는 차수가 노드에서 뻗어 나가는 **엣지의 개수(팬아웃)**로 드러납니다. user-profile 서비스에서 화살표가 사방으로 튀어나와 있다면, 그 주변은 고차수 결합 구역입니다.
코너생스로 리팩터링 우선순위 정하기
모든 문제를 한 번에 해결할 수는 없습니다. 코너생스는 제한된 리팩터링 에너지를 가장 영향력이 큰 문제에 집중하도록 도와줍니다.
실전 워크플로는 대략 이렇습니다.
-
시스템을 그려 본다
- 서비스 간 고수준 의존성 그래프를 생성하거나 손으로 그립니다.
- 연결 타입(HTTP, gRPC, 메시지 큐, DB, 파일 시스템 등)을 표시합니다.
-
엣지를 대략 점수화한다
- 강도(Strength): 1–5 (바꾸기 얼마나 고통스러운가?)
- 위치(Locality): 1–5 (동일 파일 → 동일 서비스 → 동일 팀 → 조직 간)
- 차수(Degree): 의존자의 수 (너무 크면 로그 스케일로 표시)
-
핫존(hot zone)을 찾는다
- 강도 높고, 비로컬이며, 차수도 높은 의존성을 찾습니다.
- 예: 5개 서비스가 쓰고 8개가 읽는 공유 테이블인데, 깨지기 쉬운 불변 조건이 문서화도 안 되어 있는 경우.
-
리팩터 타깃을 고른다 (다음 기준으로)
- 리스크 감소: 이 의존성을 끊으면 앞으로의 많은 변경이 더 안전해진다.
- 속도 향상: 이 영역이 반복적으로 작업을 막고 있다.
- 전략적 가치: 앞으로의 핵심 기능 개발이나 마이그레이션 경로 위에 있다.
이렇게 하면 “오늘 나를 짜증나게 하는 것부터 고친다”식 리팩터링에서 벗어나, 아키텍처 수준의 의도적인 투자로 전환할 수 있습니다.
리팩터링의 공동 책임 모델
무엇을 리팩터링해야 할지 안다고 해도, 실제로 리팩터링을 실행하는 데 팀이 어려움을 겪는 경우가 많습니다. 일감과 리스크가 너무 커 보여서, 한 명의 개발자나 한 번의 스프린트로는 감당하기 어렵기 때문입니다.
현실적인 접근법 하나는 리팩터링의 공동 책임(shared responsibility) 모델을 도입하는 것입니다. 이를 두 단계로 나눌 수 있습니다.
-
준비(Preparation, 원 작성자 책임)
- 손댄 동작 주변에 테스트를 추가합니다.
- 알고 있는 불변 조건과 가정을 문서화합니다.
- 시임(seam)을 만듭니다: feature flag, 어댑터 레이어, anti‑corruption layer 등.
- 현재 사용 패턴을 측정할 수 있도록 텔레메트리(로그·메트릭·트레이스)를 추가합니다.
-
실행(Execution, 이후 개발자 책임)
- 준비된 시임과 테스트를 활용해 내부 구현을 안전하게 바꿉니다.
- 호출자들을 새 API나 데이터 모델로 점진적으로 마이그레이션합니다.
- 메트릭과 로그를 통해 더 이상 사용되지 않음이 확인되면, 기존 경로를 제거합니다.
이 모델에서는, 이미 딜리버리 압박을 받고 있을 때 완전한 리팩터링을 끝내야 할 필요가 없습니다. 대신 “다음 사람이 바꾸기 조금 더 쉽게”만 만들어 두면 됩니다.
아날로그 틸트‑시프트 산출물—주석이 붙은 그래프, 코너생스 점수, 메모—는 팀 전체의 공유 컨텍스트가 됩니다. 다음에 오는 사람은 커밋 내용뿐 아니라, 당신이 무엇을 의도했는지도 볼 수 있습니다.
바꾸기 전에 구조부터 추출하라
의미 있는 리팩터링은 시스템의 실제 구조와 데이터 모델을 이해하는 것 위에 성립합니다.
의존성을 이리저리 옮기기 전에, 먼저 해야 할 일은 다음과 같습니다.
- User, Order, Invoice 같은 정준(canonical) 데이터 모델과 그 변종을 파악합니다.
- 중복되었거나, 미묘하게 달라진 스키마를 찾아냅니다.
- 어떤 서비스가 어떤 개념의 진짜 소스 오브 트루스(source of truth)인지 이해합니다.
구체적인 방법은 예를 들면 이렇습니다.
- 데이터베이스 인트로스펙션과 코드 생성 도구를 이용한 스키마 마이닝
- 이벤트 페이로드와 그 진화 이력 분석
- API 계약서와 실제 런타임 사용 패턴(예: 트레이스)을 함께 검토
그다음, 이렇게 추출한 데이터 모델을 서비스 그래프 위에 겹쳐서 볼 수 있습니다.
- User 데이터가 어디에 존재하고, 어디로 흘러가는지 그립니다.
- 어떤 서비스가 어떤 엔티티를 변경하는지 표시합니다.
- 소유권과 데이터 경계를 명확히 표시합니다.
이렇게 나온 구조 지도는 곧 리팩터링 설계도입니다. 이 작업 없이 상자를 이리저리 옮기기만 하면, 안에 든 게 뭔지도 모른 채 아키텍처를 재배치하는 셈입니다.
개방 루프 vs 반복(폐쇄 루프) 리팩터링 계획
리팩터링을 계획하는 방식은 제어 시스템 설계와 닮아 있습니다.
- 개방 루프(open‑loop) 계획: 한 번 계획을 세우고, 전부 실행한 뒤, 잘 되길 바란다.
- 아날로그: 수십 개 서비스에 걸친 대규모 변경을 한 번의 거대한 PR로 밀어넣는 ‘빅뱅’ 리팩터링.
- 폐쇄 루프(피드백 기반) 반복 계획: 작은 변경을 하고, 시스템을 관찰하며, 계획을 조정한 뒤, 다시 반복한다.
- 아날로그: 텔레메트리와 피드백에 기반한 점진적 리팩터링.
복잡하고 분산된 시스템에서 개방 루프 계획은 매우 취약합니다.
- 보지 못했던 의존성이 뒤늦게 튀어나옵니다.
- 실제 트래픽 패턴이 우리가 가정한 것과 다르게 나타납니다.
- 엣지 케이스는 오직 프로덕션에서만 드러나기도 합니다.
대신, 축소된 아키텍처와 코너생스 지도를 이용해 작고 피드백 주도적인 단계로 계획을 세워야 합니다.
- 약화시키면 가치가 큰 의존성을 하나 고릅니다.
- 시임(어댑터, 파사드, 호환 레이어 등)을 추가합니다.
- 트래픽의 일부만 새 경로로 라우팅합니다.
- 메트릭, 에러, 트레이스를 관찰합니다.
- 결과를 보고 조정·확대·롤백합니다.
그래프는 이 과정에서 함께 진화합니다. 어떤 엣지는 가늘어지고, 어떤 것은 사라지며, 더 나은 위치에 새로운 엣지가 생겨납니다. 틸트‑시프트 관점 덕분에, 단순 코드 diff를 넘어 아키텍처가 시간이 지나면서 어떻게 바뀌는지를 볼 수 있습니다.
정리: 관점을 바꾸면 리팩터링이 달라진다
아날로그 리팩터 틸트‑시프트는 종이에 대한 향수 이야기가 아닙니다. 관점을 바꾸는 방법에 관한 이야기입니다.
- 시스템을 축소해 두고, 뇌가 패턴을 인식할 수 있게 하세요.
- 의존성 시각화로, 상상 속이 아닌 실제 결합 관계를 드러내세요.
- 코너생스를 적용해, 어떤 의존성이 정말로 아픈지 판단하세요.
- 준비와 실행을 분리해, 리팩터링 책임을 팀이 함께 나누세요.
- 구조와 데이터 모델을 먼저 추출·지도화한 다음에 옮기세요.
- ‘한 방에 끝내기’보다는 피드백을 활용한 반복적 리팩터링을 선택하세요.
이렇게 하면, 리팩터링은 더 이상 두렵고 올인해야 하는 이벤트가 아니라, 루틴하면서도 방향성 있는 실천이 됩니다. 프로덕션의 시스템은 여전히 거대하겠지만, 화이트보드나 인쇄물 위에서는 펜으로 얼마든지 모양을 바꿔 볼 수 있는 작은 모형이 됩니다.
거대한 것을 안전하게 바꾸고 싶다면, 먼저 눈에 들어올 만큼 작게 만드는 일부터 시작해야 할 때가 많습니다.