아날로그 리팩터링 플레이셋: 코드를 바꾸기 전에 책상 위에 ‘토이 모델’ 아키텍처부터 만들기
프로덕션 코드를 건드리기 전에, 가볍고 유연한 아키텍처 모델링—즉 ‘아날로그 리팩터링 플레이셋’—을 사용해 대규모 코드 변경을 안전하게 탐색하는 방법을 소개합니다.
아날로그 리팩터링 플레이셋: 코드를 바꾸기 전에 책상 위에 ‘토이 모델’ 아키텍처부터 만들기
큰 리팩터링이 무서운 데는 이유가 있다. 한 번 대형 코드베이스의 구조를 흔들기 시작하면, 의존 관계나 숨은 부작용, 의도치 않은 복잡성을 금방 놓치기 쉽다. 그렇다면 애초에 실제 시스템에서 시작하지 않아도 된다면 어떨까?
먼저 아키텍처의 책상 위 토이 모델을 하나 만든다고 생각해 보자. 작고, 손으로 만질 수 있고, 안전해서 프로덕션 브랜치를 건드리기 전에 여러 선택지를 마음껏 시도해볼 수 있는 모델이다.
이게 바로 아날로그 리팩터링 플레이셋(Analog Refactor Playset) 의 아이디어다. 리팩터링을 ‘코드 편집’이 아니라 모델링과 설계 활동으로 다루는 것이다. 곧바로 대규모 변경에 들어가기보다는, 먼저 다이어그램과 스케치, 단순화된 코드를 이용해 시스템 아키텍처의 토이 모델을 만들고, 그 설계가 책상 위에서 충분히 단단해졌다고 느껴질 때 실제 코드베이스에 옮겨 담는 식이다.
이 글에서는 이 플레이셋이 어떤 모습인지, 어떻게 만드는지, 그리고 다음 리팩터링에서 리스크를 줄이는 데 어떻게 사용하는지 살펴본다.
아키텍처는 여러 관점(Viewpoint)의 모음이다
물리 건축이든 소프트웨어 아키텍처든, 세상을 설명하는 ‘단 하나의 진짜 다이어그램’ 같은 건 없다. 대신 서로 다른 관심사를 강조하는 여러 개의 다이어그램을 사용한다.
- 정적 구조(Static structure): 컴포넌트, 모듈, 데이터 구조가 어떻게 연결되어 있는지
- 동적 동작(Dynamic behavior): 요청, 이벤트, 데이터가 시간에 따라 시스템을 어떻게 흘러가는지
소프트웨어에서 아키텍처 모델은 보통 이런 여러 가지 뷰(view) 로 이뤄진다.
- 개발자를 위한 논리 뷰(logical view) — 모듈, 레이어, 도메인
- 프로세스 / 런타임 뷰(process/runtime view) — 서비스, 스레드, 큐, 워크플로우
- 배포 뷰(deployment view) — 서버, 컨테이너, 리전
- 데이터 뷰(data view) — 스키마, 핵심 엔티티, 데이터 소유권
각 뷰는 특정 분석 범위나 이해관계자의 관심사에 맞춰진 서로 다른 관점(viewpoint) 이다. 예를 들어:
- 백엔드 개발자는 도메인 모듈들이 서로 어떻게 의존하는지에 관심이 있을 수 있다.
- 운영/ SRE는 서비스 경계와 호출 그래프에 더 관심이 많을 수 있다.
- 프로덕트/UX 팀은 엔드투엔드 플로우와 지연(latency)이나 장애가 어디서 발생할 수 있는지에 관심이 있다.
대부분의 리팩터링은 이들 중 둘 이상의 뷰에 영향을 준다. 그래서 다이어그램 하나만 보거나, 그냥 “코드만 들여다보는 것”으로는 부족하다.
아날로그 리팩터링 플레이셋은 이런 뷰들을 가볍고 유연하게 빠르게 구성해서, 비싼 코드 변경 전에 설계 아이디어를 실험해보는 데 초점을 둔다.
비유: 실제 공사 전에 만드는 토이 모델
건축가는 건축 현장에서 바로 철골 구조를 옮겨다 놓는 것부터 시작하지 않는다. 먼저 축소 모형(scale model) 과 도면부터 만든다.
- 책상 위에 올려놓는 종이·골판지 건물
- 3D 프린트한 컴포넌트
- 하중 경로, 환기, 설비 배치를 그린 스케치
이런 모델은 싸고, 되돌리기 쉽고, 탐색 가능하다. 벽 하나를 옮기거나, 모형을 돌려보거나, 평면도를 몇 초 만에 다시 그릴 수 있다.
코드베이스의 토이 모델을 만드는 건 소프트웨어 세계의 동등한 작업이다.
- 화이트보드에 컴포넌트를 스케치한다.
- 모듈이나 서비스를 포스트잇으로 책상 위에 배치한다.
- 핵심 플로우를 시퀀스 다이어그램으로 그린다.
- 원하는 구조를 흉내 내는 작은 샘플/샌드박스 코드 프로젝트를 만들어 본다.
이 모든 작업은 기존 프로덕션 코드를 전혀 건드리지 않는다. 완전히 안전한 샌드박스다. 여기에서 당신은:
- 새로운 경계와 추상화를 여러 버전으로 탐색해 보고
- 의존 관계와 복잡도가 집중된 지점을 시각화하고
- 새 모듈/서비스 설계를 장난감처럼 조립해 보고
- 데이터와 동작이 새 구조에서 어떻게 흐를지 그려볼 수 있다.
이 토이 모델이 충분히 말이 된다고 느껴졌을 때, 비로소 그 변경을 실제 시스템으로 체계적으로 옮긴다.
리팩터링을 ‘청소’가 아니라 ‘설계’로 보기
리팩터링을 흔히 “코드를 좀 정리하는 것” 정도로만 취급하는 경우가 많다. 하지만 그건 리팩터링을 너무 좁게 보는 관점이다.
리팩터링은 외부 동작을 바꾸지 않으면서, 기존 코드의 내부 설계를 체계적으로 개선하는 과정이다.
Martin Fowler의 책, Refactoring: Improving the Design of Existing Code 는 이 분야의 고전이다. 여기에는 다음과 같은 구체적인 리팩터링 기법들이 정리되어 있다.
- 메서드 추출(Extract Method) / 클래스 추출(Extract Class)
- 파라미터 객체 도입(Introduce Parameter Object)
- 상속을 위임으로 대체(Replace Inheritance with Delegation)
- 메서드 이동(Move Method) / 필드 이동(Move Field)
- 컬렉션 캡슐화(Encapsulate Collection)
이런 것들은 작고 기계적인 움직임이며, 연쇄적으로 이어 붙일 수 있다. 하지만 어떤 리팩터링을 어떤 순서로 적용해야 할지는 누가 결정할까? 그걸 안내해 주는 것이 바로 아키텍처 모델과 토이 시스템이다.
리팩터링을 모델링·설계 활동으로 대할 때는 이렇게 진행한다.
- 먼저 원하는 아키텍처의 모습을 그린다.
- 현재 상태에서 목표 상태로 가는 마이그레이션 경로를 찾는다.
- 그 설계를 실현하기 위해 작은 리팩터링들을 어떤 순서로 수행할지 계획한다.
“일단 정리 좀 하다 보면 어디로 가야 할지 보이겠지”가 아니라, 이런 식이 된다.
“이게 목표 아키텍처다. 필요한 모듈/서비스는 이거고, 끊어야 할 핵심 의존성은 이거다. 이걸 이 순서로 잘라 나갈 것이다.”
아날로그 리팩터링 플레이셋은 이 목표 상태와 마이그레이션 경로를 설계할 때 사용하는 도구·연습법의 묶음이다.
아날로그 리팩터링 플레이셋에 들어있는 것
화려한 툴은 필요 없다. 이 플레이셋은 의도적으로 저기술(low-tech) 이고, 손으로 만질 수 있으며, 쉽게 바꿀 수 있는 것들로 구성된다.
핵심 구성 요소:
-
정적 구조 맵(Static structure map)
- 화이트보드, 종이, 디지털 보드 어디든 박스와 화살표로 다음을 표현한다.
- 모듈, 서비스, 레이어
- 이들 사이의 핵심 의존 관계
- 여기서 핫스팟(hotspot) 을 표시한다.
- 변경이 잦은 곳(고 churn)
- 버그가 많이 나는 곳(고 defect)
- 결합도가 높은 곳(고 coupling)
- 화이트보드, 종이, 디지털 보드 어디든 박스와 화살표로 다음을 표현한다.
-
동적 동작 다이어그램(Dynamic behavior diagrams)
- 다음에 해당하는 몇 가지 시퀀스 다이어그램 또는 플로우 다이어그램
- 가장 중요한 유스케이스
- 가장 자주 깨지는(취약한) 플로우
- 성능에 가장 민감한 경로
- 다음에 해당하는 몇 가지 시퀀스 다이어그램 또는 플로우 다이어그램
-
책임/소유권 메모(Responsibility / ownership notes)
- 어떤 모듈·컴포넌트를 누가 소유하는가?
- 도메인 개념이 어디에 흩어져 있는가?
- 데이터와 동작이 뒤섞여 혼란스럽게 섞여 있는 곳은 어디인가?
-
실험 공간(Experiment space)
- 움직일 수 있는 포스트잇이나 인덱스 카드로 다음을 표현한다.
- 새로 만들 수 있는 모듈/서비스 후보
- 도메인 사이의 새로운 경계
- 컴포넌트 사이의 대안 호출 패턴
- 움직일 수 있는 포스트잇이나 인덱스 카드로 다음을 표현한다.
-
(옵션) 토이 코드 프로젝트(Toy code project)
- 새 아키텍처의 형태(shape) 만 구현한 아주 작은 레포(또는 패키지)
- 가짜 인터페이스, 스텁 메서드, 최소한의 로직만 포함
- 실제 코드의 무게 없이 새 추상화·패키지 구조를 시험해 보는 용도
핵심은 모든 요소가 쉽게 옮기고, 지우고, 버릴 수 있어야 한다는 점이다. 바꾸기 쉬울수록 실험이 쉬워진다.
코드베이스의 토이 모델을 만드는 방법
특정 리팩터링을 위해 아날로그 리팩터링 플레이셋을 만드는 과정은 몇 단계면 충분하다.
1. 기존 구조를 (대략적으로) 캡처하기
IDE에서 시작하지 말고, 책상에서 시작하자.
- 서비스나 주요 모듈을 박스로 그린다.
- 누가 누구를 호출하는지 화살표로 연결한다.
- 여기에 다음 같은 메모를 붙인다.
- “책임이 너무 많음”
- “X에 대해 너무 많은 걸 알고 있음”
- “테스트하기 어려움”
목표는 정밀함이 아니라 명료함 이다. 여기서 보고 싶은 건:
- 순환 의존(cycle)
- 거대 객체/거대 서비스(god object/service)
- 추상이 새어 나오는(leaky) 레이어
2. 몇 가지 핵심 플로우를 맵핑하기
2~4개 정도의 핵심 플로우를 고른다.
- 회원가입 / 로그인
- 체크아웃
- 데이터 수집(ingestion) 파이프라인
- 리포트 생성
각 플로우마다 간단한 시퀀스 다이어그램을 그린다.
- 어떤 컴포넌트들이 어떤 순서로 참여하는가?
- 분기·조건(conditional)은 어디에 있는가?
- 데이터가 어디서 변환·검증되는가?
이런 동적 뷰를 통해 동작 이 어색한 경계를 어떻게 가로지르는지가 보인다.
3. 종이 위에서 목표 아키텍처 제안하기
이제 포스트잇이나 박스를 가지고:
- 관련 책임들을 묶어서 후보 모듈로 그룹화하고,
- 데이터를 소유한 쪽으로 로직을 더 가깝게 옮기고,
- 관심사를 분리한다(예: 도메인 로직 vs 인프라스트럭처).
- 새로운 경계를 고려한다(예: 거대 서비스를 둘로 나누거나, 떼려야 뗄 수 없는 둘을 합치기 등).
지금은 거기까지 어떻게 가는지(마이그레이션) 는 걱정하지 말고, 다음을 만족하는 설계를 그려본다.
- 책임이 더 명확하고,
- 불필요한 결합이 줄고,
- 중요한 플로우가 더 직관적이고 직선적으로 흐르는 구조.
4. 기존 플로우를 새 설계 위에서 다시 걸어 보기
앞에서 만든 시퀀스 다이어그램을 가져와서, 새 구조 위에서 다시 재현해 본다.
- 플로우가 더 단순해졌는가, 아니면 더 복잡해졌는가?
- 불필요한 간접(indirection)을 너무 많이 만든 건 아닌가?
- 새로운 경계 때문에 어색한 교차 호출이나 순환 논리가 생기지는 않았는가?
플로우가 예전보다 더 깔끔하고 명시적 으로 느껴질 때까지 모델을 조정한다.
5. 모델에서 리팩터링 계획 뽑아내기
이제 모델을 구체적인 리팩터링 단계들의 시퀀스 로 바꾼다. 이때 Fowler의 Refactoring 에 나오는 기법과 이름들을 빌려와, 각 단계를 명확하게 정의한다.
예를 들면 이렇게 할 수 있다.
OrderService에 있는 핵심 도메인 로직을OrderDomain클래스로 추출(Extract Class) 한다.OrderDomain과 결제 인프라 사이에 인터페이스를 도입(Introduce Interface) 한다.- 결제 관련 메서드를
OrderService에서PaymentGatewayAdapter로 이동(Move Method) 한다. - 호출자들이 새 추상화를 통해서만 접근하도록 업데이트한다.
- 이제 사용되지 않는, 죽은 코드 경로를 제거한다.
토이 모델은 다음을 알려준다.
- 무엇이 새로 생겨야 하는지
- 어떤 의존성을 끊어야 하는지
- 어떤 추상화를 도입해야 하는지
그 다음 실제 구현은 외부 동작을 유지하면서 작은 리팩터링들을 순차적으로 적용하는 과정 이 된다.
왜 대규모 리팩터링의 리스크가 줄어드는가
코드를 건드리기 전에, 작고 손으로 만질 수 있는 아키텍처 표현을 만드는 데는 다음과 같은 이점이 있다.
-
의존 관계와 핫스팟이 선명해진다
복잡성과 결합도가 실제로 어디에 모여 있는지 미리 보고 시작할 수 있다. 리팩터링 도중에 “여기가 이렇게 꼬여 있었네” 하고 뒤늦게 알게 되는 일을 줄인다. -
실수 비용이 싸다
포스트잇을 옮기는 건, 반쯤 머지된 코드 변경을 되돌리는 것보다 훨씬 쉽고 싸다. -
공유된 멘탈 모델
팀 전체가 같은 다이어그램을 보면서 토론하고, 대안을 비교하고, 공통된 설계 방향에 합의할 수 있다. -
체계적인 실행
아키텍처와 마이그레이션 경로가 분명해지면, 이미 알려진 작은 리팩터링 기법들을 자신 있게 적용해 나갈 수 있다. -
더 나은 추상화
리팩터링을 단순한 코드 정리가 아니라 설계 활동으로 보면, 도메인 경계, 소유권, 장기적인 유지보수성을 우선하게 된다. 단순히 “코드를 예쁘게” 만드는 수준을 넘어서게 된다.
이 방법이 테스트, 코드 리뷰, 점진적 롤아웃을 대체하는 것은 아니다. 대신 이것들은 우리가 방향성 있는 설계를 향해 코드를 바꾸고 있는지, 아니면 그냥 코드를 이리저리 재배치하고 있는지를 가르는 안전장치 역할을 한다.
다음 리팩터링에 적용해 보기
다음에 규모 있는 리팩터링을 할 때, 다음과 같이 시도해 보자.
- 코드를 건드리기 전에 팀과 1–2시간 정도 블로킹 해 둔다.
- 현재 아키텍처 와 2–3개의 핵심 플로우를 그림으로 그린다.
- 포스트잇과 다이어그램으로 대안 구조 를 제안해 본다.
- 플로우를 새 모델 위에서 걸어 보며 구조를 다듬는다.
- 그 모델에 기반한 단계별 리팩터링 계획 을 글로 정리한다.
- 그제야 비로소 IDE를 열고 계획 실행을 시작한다.
여전히 꾸준한 태도, 테스트, 리뷰가 필요하겠지만, 작업 전반이 훨씬 더 안내받는 느낌이 들 것이다.
마무리
리팩터링은 코드를 예쁘게 만드는 작업이 아니다. 외부 동작을 바꾸지 않고, 살아 있는 시스템의 내부 설계를 개선하는 일 이다. 그걸 잘하려면, 에디터 안에서의 즉흥적인 수정만으로는 부족하다.
아날로그 리팩터링 플레이셋은 다음을 제안한다.
- 단순한 다이어그램과 스케치로 아키텍처의 토이 모델 을 만든다.
- 정적·동적 뷰를 포함한 여러 관점(viewpoint) 으로 구조와 동작을 함께 이해한다.
- 리팩터링을 먼저 설계 작업, 그 다음 코드 작업 으로 여긴다.
그렇게 하면 프로덕션 코드를 건드릴 즈음에는, 가장 큰 설계 결정들은 이미 책상 위에서, 싸고 되돌리기 쉬운 상태에서 끝나 있다. 그래서 큰 변경도 훨씬 덜 위험하게 느껴지고, 결과물인 시스템은 더 단순하고, 명확하며, 진화시키기 쉬워진다.