Rain Lag

아날로그 리팩터 퍼즐 보드: 책상 위 직소 퍼즐처럼 레거시 코드 변경 조립하기

레거시 코드 리팩터링은 심장 수술처럼 느껴질 필요가 없다. 책상 위 퍼즐 맞추기처럼 기술 부채를 눈에 보이게 만들고, 시각적 맵을 활용하며, 외부 동작을 유지하는 변경을 진짜로 중요한 곳에만 선택적으로 적용하자.

아날로그 리팩터 퍼즐 보드: 책상 위 직소 퍼즐처럼 레거시 코드 변경 조립하기

레거시 시스템을 리팩터링하는 일이, 한 덩어리 스파게티 코드와 또 다른 스파게티 코드를 diff 하는 느낌이라면 당신만 그런 게 아니다. 많은 팀이 리팩터링을 혼란스럽고, 위험하며, 이해하기 어려운 작업으로 경험한다. 그렇다면 이걸, 큰 보드 위에 퍼즐 조각을 펼쳐두고 맞춰보는 느낌으로 바꿀 수는 없을까? 전체 그림을 보고, 조각을 이리저리 옮겨 보며, 실제로 커밋하기 전에 어떻게 맞물리는지 시험해 볼 수 있는 시각적인 작업 공간으로 말이다.

이 글에서는 리팩터링을 책상 위의 아날로그 퍼즐 보드처럼 다루는 방법을 살펴본다. 이를 통해 다음을 배우게 될 것이다.

  • 끝없는 완벽주의 리팩터 머신이 되지 않고도, 읽기 좋은 코드를 쓰는 법
  • 기술 부채를 눈에 보이게, 의도적으로 관리하는 법
  • 리팩터링을 "동작을 유지하는 구조 변경"으로 다루는 관점
  • 시스템을 건드리기 전에 Codemaps 같은 시각적 코드 맵핑 도구로 전체 구조를 보는 법
  • 다이어그램과 문서를 변화하는 아키텍처와 동기화하는 법
  • 하나의 다이어그램 스타일에 시스템을 억지로 끼워 넣지 않고, 모델링을 유연하게 가져가는 법

클린 코드 vs 리팩터 집착: 언제 중요한지 구분하기

가능한 범위 안에서 최대한 깔끔한 코드를 쓰는 것은 언제나 옳다. 가독성, 좋은 네이밍, 명확한 경계는 늘 투자 대비 효과가 크다. 하지만 리팩터링은 금방, 실제 가치 전달과 경쟁하는 "취미"가 되어버릴 수 있다.

리팩터링을 작업실 재정비에 비유해 보자.

  • 중요한 작업 한가운데인데, 선반 전체를 새로 짜겠다고 멈추지는 않는다.
  • 대신 지금 나를 가장 느리게 만드는 부분부터 정리한다.

코드베이스에도 같은 태도를 적용하자.

  • 기회가 올 때 리팩터링하되, 강박적으로 하지 않는다. 새 기능 개발이나 버그 수정을 위해 모듈을 건드릴 때, 비용이 저렴하고 도움이 된다면 주변 코드를 같이 개선한다.
  • 실질적인 결과를 여는 리팩터링에 우선순위를 둔다. 배포 속도 향상, 변경 시 안전성 증가, 온보딩 개선, 성능/신뢰성 향상과 직접 연결되는 변경을 고른다.
  • 목적 없는 리팩터링은 피하라. 그 리팩터링이 비즈니스나 엔지니어링 관점에서 어떤 이득이 있는지 설명하지 못한다면, 그건 임팩트가 아니라 자존심을 위한 광택 내기일 가능성이 크다.

머릿속 퍼즐 보드에, 매 스프린트마다 퍼즐 상자 전체를 쏟아 붓지 마라. 지금 실제로 다루고 있는 그림의 일부를 골라, 그 부분만 제대로 정리하라.


기술 부채를 드러내기: 이상한 조각에 라벨 붙이기

기술 부채는 피할 수 없다. 진짜 문제는 조용한, 문서화되지 않은 해킹이 나중에 지뢰밭이 되는 것이다.

모든 우회로와 꼼수를 라벨이 크게 붙은 퍼즐 조각처럼 다뤄라.

  • 해킹(Hack)이나 임시 코드는 주석으로 분명하게 남긴다.
    • 무엇이 해킹인지?
    • 왜 이런 방식으로 구현했는지?
    • 어떤 조건에서 제거하거나 개선할 수 있는지?
  • 이런 단축로를 아키텍처 노트나 ADR(Architecture Decision Record)에 기록한다.
    • "여기서는 Y 때문에 X를 우회했다.
    • Z 시점/조건에서 다시 검토할 예정이다."
  • 코드 리뷰에서 트레이드오프를 명시적으로 짚는다.
    "이 해결책은 모듈 A에 부채를 추가하지만, 모듈 B에서 큰 회귀(risk)를 피하게 해준다"처럼.

부채를 이렇게 드러내면 다음과 같은 이점이 있다.

  • 시스템이 실제보다 깨끗하다는 착각을 피할 수 있다.
  • 향후 리팩터링이 이미 알려진 핫스팟을 정확히 겨냥할 수 있다.
  • "우리는 나쁜 코드가 많아"라는 막연한 죄책감을, 제약과 트레이드오프가 정리된 관리 가능한 백로그로 바꿀 수 있다.

아날로그 퍼즐 보드에서 이런 조각들은 포스트잇이 붙어 있는 조각이다.
“여기 이상함 있음 – 맥락 없이 ‘깔끔하게’ 고치지 말 것” 같은 메모가 붙어 있다.


리팩터링은 동작을 유지한 채 구조만 바꾸는 것

진짜 리팩터링은 구조를 바꾸는 것이지, 동작을 바꾸는 게 아니다. 외부에서 보이는 행동은 그대로 두고, 내부 코드 조직 방식만 변경하는 일이다.

이건 아날로그 하드웨어 기술(HDL)이나 모델을 자동 리팩터링할 때의 원칙과도 같다. 내부 구조는 바꾸더라도, 외부 신호와 동작은 동일하게 유지해야 한다.

리팩터링을 이렇게 정의하자.

동작을 보존하는 구조적 변경.

실무에서 이 말은 다음을 의미한다.

  • 리팩터 단계에서는 공개된 계약(Contract)은 최대한 안정적으로 유지한다.
    • 공개 API의 시그니처나 의미(시맨틱)는 바뀌지 않아야 한다.
    • 외부 관점에서의 동작은 테스트를 통해 검증해야 한다.
  • 단위 테스트와 통합 테스트는 소프트웨어용 "오실로스코프"라고 생각하라. 동작이 바뀌었다면 그건 리팩터링이 아니라, 새로운 기능이거나 버그다.
  • 가능하다면 리팩터링 커밋과 기능 개발 커밋을 분리한다.
    • 리뷰가 훨씬 수월해진다.
    • 롤백이 안전해진다.

퍼즐 보드 관점에서 리팩터링은, 최종 그림(박스에 인쇄된 이미지)은 그대로 둔 채, 조각들을 더 깔끔한 클러스터로 재배열하는 것이다.


자르기 전에 먼저 보기: 시각적 코드 맵핑 도구

레거시 시스템은 텍스트 파일과 IDE 검색만으로는 도저히 전체를 이해하기 어려울 만큼 크고 서로 얽혀 있는 경우가 많다. 퍼즐 조각을 재배열하기 전에, 먼저 전체 그림을 봐야 한다.

Codemaps 같은 코드 시각화 도구(혹은 유사 도구)는 다음과 같은 것들을 제공한다.

  • 구조 맵: 모듈, 패키지, 서비스가 어떻게 연결되어 있는지
  • 의존성 그래프: 누가 누구에게 의존하는지, 그리고 의존 강도는 어떤지
  • 핫스팟 오버레이: 변경이 잦거나, 버그가 많거나, 복잡도가 높은 영역

이 도구들을 사용해 다음 질문에 답해 보자.

  • 리팩터링을 하기 좋은 자연스러운 경계는 어디인가?
  • 가장 중심적인 컴포넌트(fan-in / fan-out이 높은 것)는 어디이며, 따라서 변경 위험이 큰 부분은 어디인가?
  • 실선 대신 도입할 수 있는 시점은 어디인가? (인터페이스, 어댑터, 안티코럽션 레이어 등)

요약하자면, 이런 시각적 코드 맵은 테이블 전체를 덮는 퍼즐 배치도에 가깝다. 가장자리, 큰 덩어리, 어중간하게 떨어진 섬들을, 리팩터링 메스를 들기 훨씬 전에 미리 확인할 수 있다.


아키텍처와 디테일 사이를 줌인/줌아웃하기: 인터랙티브 다이어그램

리팩터링은 보통 하나의 추상화 레벨에만 머물지 않는다. 예를 들어 다음과 같은 흐름을 자주 경험한다.

  • 먼저 상위 수준 모듈 경계를 다시 생각해 본다.
  • 그러고 나서 개별 클래스나 함수 수준으로 내려가 보면, 그 깔끔한 경계를 실제로 만들려면 더 작은 보조 변경들이 필요하다는 걸 깨닫게 된다.

그래서 계층적으로 확대/축소 가능한 인터랙티브 다이어그램이 중요하다.

  • 줌 아웃 뷰에서는 서비스, 모듈, 도메인 등 상위 구조를 본다.
  • 줌 인 뷰에서는 클래스, 함수, 호출 그래프까지 내려가 본다.

이 계층 구조를 통해 다음을 할 수 있다.

  1. 아키텍처 수준에서 의도를 정의한다.
    "Service A가 DB B에 직접 의존하지 않도록, 리포지토리 레이어를 도입하자"와 같은 수준의 목표 설정.
  2. 그 영향 범위를 아래로 추적한다.
    어떤 파일이 영향을 받는지, 어떤 쿼리와 어떤 테스트가 수정 대상인지.
  3. 마이그레이션 경로를 설계한다.
    기능 플래그, 어댑터 패턴, 점진적 추출 단계 등을 어떻게 가져갈지.

이건 퍼즐 보드 위의 돋보기와 같다. 전체 그림을 보다가, 필요할 때 개별 조각의 미세한 패턴까지 들여다볼 수 있다.


다이어그램과 문서는 코드와 함께 움직이게 하라

코드만 바꾸고 다이어그램은 내버려 두는 리팩터링은 시간이 지나면서 폭탄이 된다. 아키텍처 문서는 그저 벽에 걸린, 현실과 다른 그림이 되어버린다.

모든 관점에서 구조를 일관되게 유지하려면 다음처럼 하자.

  • 다이어그램과 모델을 부가물이 아니라 1급 산출물로 취급한다.
  • 모듈 경계나 책임이 바뀌면:
    • 아키텍처 다이어그램(시스템 컨텍스트, 컨테이너, 컴포넌트 등)을 함께 업데이트한다.
    • 그 결정의 이유를 ADR이나 유사한 기록에 남긴다.
  • 가능하다면, 도구에서 사용하는 동일한 모델로부터 다이어그램을 생성/지원하도록 해서, 변경 시 업데이트를 반자동으로 처리한다.

목표는 시각적 맵, 문서, 코드가 모두 같은 이야기를 하게 만드는 것이다. 새 팀원이 합류했을 때, 그들의 머릿속 퍼즐 보드가 기존 팀원들이 쓰는 보드와 최대한 일치해야 한다.


지나치게 엄격한 모델링을 경계하라: C4만으로는 부족할 때

C4 다이어그램(Context, Container, Component, Code)은 매우 유용하지만, 너무 교조적으로 적용하면 오히려 구속복이 될 수 있다.

리팩터링 팀이 이를 체감하는 상황은 대개 다음과 같다.

  • 특정 워크플로에 초점을 맞춘 변경을 모델링하고 싶다. 예: 사기 탐지 단계가 동기 처리에서 비동기 처리로 이동하는 과정.
  • 아주 저수준의 관심사를 논의해야 한다. 예: 이벤트 순서, 트랜잭션 경계, 데이터 마이그레이션 단계 등.
  • 엄격한 C4 레이어링만으로는 프로세스 흐름, 데이터 라이프사이클, 시간적(Temporal) 동작을 충분히 표현하기 어렵다.

이 한계를 피하려면 다음과 같이 하라.

  • C4나 유사한 접근을 출발점이지, 감옥으로 생각하지 말라.
  • 정적인 구조 다이어그램을 다음과 함께 보완하라.
    • 호출 및 워크플로를 위한 시퀀스 다이어그램
    • 라이프사이클 전이를 위한 상태 다이어그램
    • 단계적 리팩터링을 위한 마이그레이션/타임라인 다이어그램

가끔은 아날로그 리팩터 퍼즐 보드에 여러 겹의 오버레이가 필요하다. 구조, 동작, 시간 축을 각각 따로 표현해야 할 때가 있다. 모든 관점을 하나의 표기법 안에 억지로 욱여넣지 말라.


모두 합치기: 나만의 아날로그 리팩터 퍼즐 보드

현대적인, 잘 다듬어진 리팩터링 실천법은, 하나의 시스템용 아날로그 퍼즐 보드를 유지하는 일에 가깝다.

  • 라벨이 붙은 조각들 = 명시적으로 드러나 있고 문서화된 기술 부채와 해킹
  • 적절히 묶인 덩어리들 = 잘 정의된 모듈과 서비스
  • 가장자리와 경계선 = 분명한 시스템 경계와 공개 계약(API, 프로토콜 등)
  • 돋보기와 오버레이 = 인터랙티브 다이어그램, 코드 맵, 워크플로 시각화

그리고 이 보드를 다루는 운영 원칙은 다음과 같다.

  1. 그 순간에 할 수 있는 한, 가장 깔끔한 코드를 작성한다.
  2. 결과에 의미가 있을 때, 선택적으로 리팩터링한다.
  3. 외부 동작은 유지하고, 테스트를 가드레일로 삼는다.
  4. 구조를 바꾸기 전에, 시각적 도구로 먼저 전체 구조를 이해한다.
  5. 다이어그램과 코드를 함께 갱신해 모두가 같은 시스템을 보게 한다.
  6. 리팩터링 목적에 맞게 모델링 기법을 유연하게 조합한다.

이렇게 일하기 시작하면, 리팩터링은 더 이상 신비롭고 고위험인 장인의 작업이 아니다. 눈에 보이고, 탐색 가능하며, 설명 가능한 프로세스가 된다. 책상 위에 펼쳐놓고, 살펴보고, 맞춰볼 수 있는 하나의 퍼즐이 된다.

그리고 조각 하나, 릴리스 하나씩 쌓이다 보면, 흐릿하던 레거시 시스템의 그림이 점점 또렷하고 일관된 형태를 갖추게 된다. 결국엔 훨씬 더 바꾸기 쉬운 시스템으로 변해 있을 것이다.

아날로그 리팩터 퍼즐 보드: 책상 위 직소 퍼즐처럼 레거시 코드 변경 조립하기 | Rain Lag