Rain Lag

아날로그 리팩터 워 게임: 깨지기 쉬운 코드베이스를 건드리기 전에 테이블탑 시나리오를 돌려보라

테이블탑식 워 게임, 보안 관점, 그리고 캐릭터라이제이션 테스트와 골든 마스터와 같은 레거시 코드 기법을 활용해, 망가뜨리지 않고도 깨지기 쉬운 시스템을 안전하게 리팩터링하는 방법을 다룹니다.

아날로그 리팩터 워 게임: 깨지기 쉬운 코드베이스를 건드리기 전에 테이블탑 시나리오를 돌려보라

깨지기 쉬운 레거시 코드베이스를 리팩터링하는 일은 정원을 가꾸는 것보다는, 터지지 않은 폭탄의 뇌관을 제거하는 일에 더 가깝게 느껴질 때가 많습니다. 한 번만 실수해도… 프로덕션 장애, 보안 사고, 데이터 유실, 망가진 주말이 기다립니다.

곧바로 코드 속으로 뛰어들기보다는, 먼저 테이블탑(tabletop) 스타일 워 게임을 돌린다고 상상해보세요. IDE도, 커밋도 없습니다. 오직 다이어그램, 인덱스 카드, 그리고 크로스펑셔널 팀이 모여 이런 “만약에?” 시나리오를 함께 걷습니다.

  • 이 서비스가 갑자기 타임아웃을 내기 시작하면?
  • 이 필드가 null인데 데이터베이스는 null이 아니라고 가정하고 있다면?
  • 사소한 템플릿 변경이 XSS 취약점을 열어버리면?

이런 아날로그 "워 게임" 접근법은 실제 코드를 건드리기 전에, 리스크를 탐색하고, 의존성을 지도화하며, 전략을 설계할 수 있게 해줍니다. 단 한 줄의 깨지기 쉬운 코드라도 만지기 전에요.

이 글에서는 다음 내용을 단계별로 살펴봅니다.

  • 리팩터 워 게임을 테이블탑 연습으로 운영하는 방법
  • 보안 엔지니어처럼 생각하기: 리팩터를 잠재적인 공격 표면 변화로 다루기
  • 리팩터 과정에서 추가하는 새로운 코드에는 TDD를 적용하는 방법
  • 캐릭터라이제이션 테스트, 골든 마스터, 시임(seam)을 통해 레거시 동작을 보호하는 방법
  • 본격적인 수술에 앞서 시스템을 복잡한 회로도처럼 그려보는 방법

왜 리팩터를 워 게임으로 다뤄야 할까?

워 게임은 미래에 일어날 수 있는 사건을 저위험 환경에서 구조적으로 시뮬레이션하고, 다양한 전략을 실험해보는 기법입니다. 군대와 보안 팀은 워 게임을 통해 다음을 수행합니다.

  • 보이지 않던 블라인드 스팟 찾기
  • 숨겨진 가정과 전제를 드러내기
  • 제약 조건 속에서 의사결정을 연습하기

레거시 코드베이스를 리팩터링하는 일도 비슷한 속성을 가집니다.

  • 시스템에 대한 지식이 불완전합니다.
  • 이미 프로덕션에서 돌아가고 있습니다.
  • 작은 변경이 예상 밖의 큰 효과를 낳을 수 있습니다.
  • 시간과 안정성 압박을 동시에 받고 있습니다.

그러니 “일단 리팩터링부터 시작하자”가 아니라, 이번 노력을 캠페인으로, 회의실을 **워 룸(war room)**으로 보는 편이 더 낫습니다.


1단계: 테이블탑 리팩터 워 게임 셋업하기

화려한 도구는 필요 없습니다. 필요한 것은 사람, 종이, 그리고 구조입니다.

누가 방 안에 있어야 할까

  • 시니어 개발자 / 테크 리드 – 아키텍처와 트레이드오프를 이해하는 사람
  • 해당 레거시 영역에 익숙한 엔지니어 – 기묘한 엣지 케이스를 알고 있는 사람
  • QA / 테스트 엔지니어 – 실패 패턴과 블라인드 스팟을 잘 보는 사람
  • 보안 엔지니어(또는 그 관점을 가진 사람) – 취약점을 짚어내는 사람
  • Ops / SRE(있다면) – 런타임 동작과 블라스트 레이디언스(영향 범위)를 아는 사람

가져와야 할 아티팩트

  • 고수준 아키텍처 다이어그램
  • 시퀀스 다이어그램 또는 콜 그래프(필요하다면 트레이스에서 생성)
  • 대상 영역과 관련된 최근 버그 및 인시던트 리포트
  • 기존 테스트 커버리지 리포트

목표 정의하기

한 문장으로 적어서 눈에 보이게 둡니다.

“결제 처리 모듈을 안전하게 리팩터링하여, 비즈니스 규칙을 데이터베이스 접근 로직과 분리하되, 관찰 가능한 동작은 바꾸지 않는다.”

이 한 문장이 워 게임의 미션 스테이트먼트가 됩니다.


2단계: 코드베이스를 복잡한 회로도처럼 그려보기

하드웨어 엔지니어가 복잡한 회로를 손대기 전에 하는 일은, 회로도를 뜯어보는 것입니다. 전압, 크리티컬 패스, 절대로 과부하를 걸면 안 되는 컴포넌트들을 파악하는 작업이죠.

코드베이스도 똑같이 다뤄야 합니다.

화이트보드나 온라인 보드에 다음을 그립니다.

  • 크리티컬 플로우 (예: 사용자 가입, 결제/체크아웃, 데이터 내보내기)
  • 외부 인터페이스 (API, 메시지 큐, 파일 import/export 등)
  • 데이터 저장소 (데이터베이스, 캐시, 서드파티 서비스)
  • 공유 모듈 (유틸리티, 모두가 쓰는 레거시 라이브러리)

그리고 다음을 주석으로 달아둡니다.

  • **“가능하면 건들지 말 것”**으로 표시된 영역
  • 숨겨진 결합(공유 전역 상태, 싱글톤, static 헬퍼)
  • 이미 악명 높은 위험 구역(“여기 바꿨다가 한 주 내내 빌링이 깨졌던 곳” 등)

목표는 완벽한 그림이 아닙니다. 목표는 전류가 어디로 흐르고, 어디를 잘못 자르면 시스템 전체가 암전될 수 있는지를 보여주는, 실용적인 회로도입니다.


3단계: 리팩터를 보안 워 게임처럼 다루기

리팩터는 종종 데이터 흐름, 검증 위치, 컴포넌트 간 상호작용 방식을 바꿉니다. 그리고 바로 그런 지점에서 보안 취약점이 슬그머니 생겨납니다.

워 게임 안에서, 여러분이 실수로 수정하게 될지도 모를 **공격 표면(attack surface)**을 명시적으로 지도화합니다.

  • 코드 인젝션 – 새로운 eval류 동적 실행, 템플릿 엔진, 플러그인 로딩 지점을 도입하고 있지 않은가?
  • SQL 인젝션 – 쿼리 빌더나 ORM 레이어를 수정하거나 우회하고 있지 않은가?
  • XSS(크로스 사이트 스크립팅) – 사용자 입력을 템플릿이나 UI에서 소비하는 API에 렌더링하는 방식이 바뀌지 않는가?
  • 인증/인가 경로 – 접근 제어 로직을 옮기거나 중복해서 넣으면서, 허점이 생기지 않는가?

그리고 이런 시나리오를 하나씩 걸어봅니다.

  • “데이터 접근 레이어를 새 모듈로 추출한다. 이때 정제되지 않은 입력이 SQL까지 직접 도달할 가능성이 생기지 않는가?”
  • “새로운 DTO 레이어를 도입한다. 그 과정에서 HTML이 이스케이프 없이 그대로 UI까지 통과할 수 있지 않은가?”
  • “검증 로직을 이쪽으로 옮긴다. 어떤 코드 경로가 그 검증을 건너뛸 수 있는가?”

리스크와 대응 방안을 꼭 적어둡니다. 각 고위험 영역에 대해, 앞으로 무엇이 필요한지 분명해집니다.

  • 동작을 고정해 둘 테스트
  • 추가적인 보안 리뷰나 정적 분석(static analysis) 체크

4단계: 새로운 코드에는 반드시 TDD를 적용하겠다고 약속하기

레거시 시스템 전체에 TDD를 소급 적용하겠다는 건 비현실적입니다. 하지만 대신 이렇게 최소한의 경계선을 그을 수는 있습니다.

이번 리팩터 과정에서 새로 만드는 클래스, 함수, 모듈은 전부 테스트를 먼저 쓰고 구현한다.

이 말은 곧 다음을 의미합니다.

  1. 새로 추가할 코드의 동작을 설명하는 테스트를 먼저 작성한다.
  2. 그 테스트를 통과하는 데 필요한 최소한의 구현만 작성한다.
  3. 테스트를 안전망으로 삼아 새 코드 내부를 마음 편히 리팩터링한다.

이렇게 하면 얻는 이점은 다음과 같습니다.

  • 새 코드는 바꾸기 쉽고 이해하기 쉬운 상태로 남습니다.
  • 레거시 늪 한가운데에, 점점 테스트 가능한 신뢰 구역(작은 섬)들을 만들어 갑니다.
  • 테스트 없는 레거시 표면적을 더 넓히지 않게 됩니다.

TDD가 과거의 죄를 씻어주진 않습니다. 다만, 앞으로 새로운 죄를 짓지 않게 도와줍니다.


5단계: 캐릭터라이제이션 테스트로 레거시 동작 보호하기

레거시 코드는 신뢰할 만한 테스트가 거의 없는 경우가 많습니다. 깨지기 쉬운 영역을 리팩터하기 전에, 최소한 이렇게 말할 수 있는 수단이 필요합니다.

“이 부분을 바꾸면, 현재 동작을 깨뜨렸는지 알 수 있다.”

여기서 등장하는 것이 **캐릭터라이제이션 테스트(characterization test)**입니다.

캐릭터라이제이션 테스트는 “어떻게 동작해야 하는가(should)”가 아니라, “지금 이렇게 동작한다(is)”를 고정해 두는 테스트입니다.

워크플로우는 다음과 같습니다.

  1. 수정이 필요한 레거시 함수나 클래스를 하나 정합니다.
  2. 로그, 프로덕션과 유사한 데이터, 준비된 픽스처 등으로부터 실제에 가까운 입력값들을 모읍니다.
  3. 지금 코드가 내놓는 출력과 사이드 이펙트를 그대로 캡처합니다.
  4. “입력 X를 주면 현재는 출력 Y를 돌려준다”는 사실을 단언(assert)하는 테스트를 작성합니다.

동작이 이상하거나 마음에 안 들어도 괜찮습니다. 지금은 “현실”을 문서화하는 단계입니다. 이런 테스트가 있으면, 리팩터를 진행하면서 의도치 않게 현실을 바꿨는지 테스트가 알려줍니다.

시간이 지나면서 다음을 할 수 있게 됩니다.

  • 동작을 점진적으로 개선하고
  • 단언을 점점 엄격하게 만들며
  • 레거시 함수를 더 깔끔한 구현으로 교체하는 작업

그러나 이 모든 걸 안전망 안에서 진행하게 됩니다.


6단계: 복잡한 동작에는 골든 마스터 테스트 사용하기

때로는 동작이 너무 복잡해서, 몇 개의 캐릭터라이제이션 테스트로는 다 표현하기 어렵습니다.

  • 출력이 아주 많은 입력 조합에 따라 달라지고
  • 엣지 케이스가 산더미처럼 많으며
  • 코드 경로가 길고 복잡하게 얽혀 있을 때

이럴 때 **골든 마스터 테스트(golden master testing)**가 빛을 발합니다.

골든 마스터의 작동 방식

  1. Record(기록): 현실적인 입력 세트를 대량으로 모으고, 기존 시스템이 내놓는 출력을 그대로 캡처합니다.
  2. Freeze(동결): 이 출력들을 “골든 마스터”로 저장합니다.
  3. Refactor(변경): 내부 구조, 알고리즘, 모듈 배치를 바꿉니다.
  4. Compare(비교): 변경 후에 같은 입력 세트를 새 구현에 흘려보내고, 출력이 골든 마스터와 일치하는지 비교합니다.

어디선가 예상치 못한 변화가 생기면, 어디를 들여다봐야 할지 감이 잡히게 됩니다.

골든 마스터는 특히 다음과 같은 영역에 유용합니다.

  • 복잡한 리포트 생성 로직
  • 대규모 데이터 변환 및 ETL 프로세스
  • 오래된 비즈니스 룰 엔진

워 게임 중에, 어떤 모듈이 골든 마스터 후보인지, 그리고 대표적인 데이터를 어떻게 캡처할지 미리 계획해 둡니다.


7단계: 변화를 통제하기 위한 시임(Seam) 만들기

레거시 코드는 대개 이런저런 이유로 테스트를 거부합니다.

  • 전역 상태
  • 하드코딩된 의존성
  • static 싱글톤

**시임(seam)**은 기존 코드를 전부 뒤엎지 않고도, 동작을 바꿀 수 있는 연결부를 뜻합니다.

예를 들면 다음과 같습니다.

  • 외부 API 주변에 인터페이스와 어댑터를 도입하기
  • static 호출을 얇은 래퍼 클래스로 감싸서, 테스트에서는 그 래퍼를 목(mock)으로 대체하기
  • 환경 변수 직접 읽기 대신 설정 객체를 주입하도록 바꾸기

워 게임에서, 앞서 그려둔 회로도를 보며 질문해보세요.

  • 어디에 시임을 심으면 이 영역이 테스트 가능해지는가?
  • 어디에 간단한 간접층을 추가해도 동작을 바꾸지 않을 수 있는가?

대부분의 리팩터 캠페인에서 첫 단계는 “예쁜 설계”가 아니라, **“안전하게 테스트할 수 있도록 시임을 추가하는 것”**입니다.


워 게임 운영 예시: 샘플 아젠다

90–120분짜리 워 게임 세션은 대략 이렇게 진행할 수 있습니다.

  1. 10분 – 미션 & 제약 정리
    리팩터 목표, 데드라인, 절대 양보할 수 없는 기준(가용성, 보안 등)을 정합니다.

  2. 20분 – 시스템 맵 그리기
    회로도, 크리티컬 플로우, 의존성을 함께 그립니다.

  3. 20분 – 공격 표면 리뷰
    계획 중인 변경으로 인해 생길 보안·신뢰성 리스크를 식별합니다.

  4. 20분 – 테스트 전략 수립
    다음을 결정합니다.

    • 새 코드에 어디까지 TDD를 적용할지
    • 어느 영역에 캐릭터라이제이션 테스트가 필요한지
    • 어떤 컴포넌트가 골든 마스터 대상인지
    • 어디에 시임을 먼저 추가해야 하는지
  5. 20–30분 – 시나리오 워크스루
    구체적인 “만약에…” 시나리오를 플레이합니다.

    • 이 호출이 null을 반환하기 시작한다면?
    • 이 DB 쿼리가 2배 느려진다면?
    • 이 템플릿의 이스케이프 방식이 달라진다면?
  6. 10분 – 액션 플랜 정리
    다음에 대한 구체적인 작업 항목과 담당자를 정리합니다.

    • 테스트 하네스 구축
    • 골든 마스터용 데이터셋 수집
    • 시임 추가
    • 고위험 코드 리뷰 일정 잡기

세션을 마칠 때쯤이면, 막연한 자신감이 아니라 지도와 플레이북을 손에 쥐고 있게 됩니다.


결론: 코드를 쓰기 전에 리팩터에서 이겨라

가장 성공적인 리팩터는 종종 첫 번째 풀 리퀘스트를 올리기 전에 이미 절반 이상 승부가 난 상태입니다.

  • 시스템을 복잡한 회로처럼 지도화했고,
  • 아키텍처 변경을 단순한 설계 개선이 아니라, 보안·신뢰성 위험으로 인식했으며,
  • 새로운 코드에는 반드시 TDD를 적용하겠다고 선을 그었고,
  • 깨지기 쉬운 영역을 캐릭터라이제이션 테스트와 골든 마스터로 감싸 두었으며,
  • 시임을 도입해 안전하게 동작을 바꿀 수 있는 여지를 만들었습니다.

테이블탑 스타일 워 게임은 “일단 스테이징에 올려보고 뭐가 깨지는지 보자” 식의 도박성 리팩터를, 치밀하게 계획된 캠페인으로 바꿔줍니다. 먼저 시뮬레이션하고, 그다음에 실행하는 흐름이 됩니다.

깨지기 쉬운 코드베이스를 열기 전에, IDE부터 켜지 말고, 화이트보드와 동료들을 먼저 모으세요. 워 게임을 돌리십시오. 먼저 종이 위에서 리팩터에 승리한 다음에, 그 전략을 코드로 옮기면 됩니다.

아날로그 리팩터 워 게임: 깨지기 쉬운 코드베이스를 건드리기 전에 테이블탑 시나리오를 돌려보라 | Rain Lag