90분 리팩터링: 작은 코드 정리 세션이 지저분한 프로젝트를 어떻게 서서히 바꾸는가
짧고 집중된 리팩터링 세션으로, 지저분한 레거시 코드를 일정과 충돌하지 않으면서도 유지보수 가능한 고품질 시스템으로 꾸준히 바꿔 나가는 방법.
90분 리팩터링: 작은 코드 정리 세션이 지저분한 프로젝트를 어떻게 서서히 바꾸는가
지저분한 시스템을 고치려고 석 달짜리 “올인 리라이트”가 꼭 필요한 건 아니다.
필요한 건 90분이다.
단 한 번이 아니라, 반복되는 90분이다.
짧고 명확하게 시간 제한을 둔 리팩터링 세션만으로도, 고통스러운 레거시 코드베이스를 팀이 이해하고 확장하고 자신 있게 배포할 수 있는 상태로 조용히 바꿔 갈 수 있다. 잘만 하면, 이는 일정에 부담을 주지 않는다. 오히려 배송 속도를 높인다.
이 글에서는 90분 리팩터링 세션이 어떻게 돌아가는지, 왜 효과적인지, 심지어 아주 취약한 시스템에서도 안전하게 적용할 수 있는 실용적인 패턴들을 살펴본다.
왜 시간 제한(refactoring time‑box)이 통하는가
리팩터링은 대개 “크고 위험한 일”처럼 느껴져서 미뤄지기 일쑤다.
매니저는 이렇게 걱정한다. “개발자들한테 리팩터링을 허용하면, 배포는 영영 못 하는 거 아냐?”
개발자는 또 이렇게 걱정한다. “이걸 건드리면, 뭔가 다 터질 것 같은데…”
여기서 90분 같은 시간 제한(time‑boxing)이 양쪽의 불안을 줄여 준다.
1. 품질 작업이 딜리버리를 잡아먹지 않게 해준다
90분이라는 시간 제한은, 의미 있는 일을 하기엔 충분히 길지만, 스프린트를 망가뜨리기엔 충분히 짧다.
이를 통해 다음과 같이 할 수 있다.
- 주 1~2회씩 90분 리팩터링 세션을 캘린더에 고정
- 기존 업무(기능 개발, 버그 수정)에 리팩터링을 붙여서 처리
- 보드에 따로 “Refactor” 태스크로 명시적으로 관리
이렇게 하면 리팩터링이 “우리가 다 고칠 때까지 끝나지 않는” 마라톤으로 변질되는 걸 막을 수 있다. 타이머가 끝나면, 작업도 거기서 멈춘다. 만약 변경이 완전히 끝나지 않았다면:
- 작고 안전한 개선분까지만 커밋하고
- 나머지는
TODO나 별도 티켓으로 남겨 두면 된다.
몇 주, 몇 달이 지나면 이 작은 세션들이 쌓이면서 구조적인 개선으로 이어진다. 그것도 거대한, 위험한 리팩터링 프로젝트를 이해관계자에게 따로 설득할 필요 없이 말이다.
2. 시작할 때의 심리적 장벽을 낮춰 준다
“결제 시스템을 덜 끔찍하게 만들자”라는 목표는 막막하다.
“이 메서드 하나만 90분 동안 덜 끔찍하게 만들어 보자”는 목표는 할 만하다.
시간 제한을 두면 리팩터링이 거창한 이벤트가 아니라, 정기적인 유지보수 활동처럼 느껴진다. 이 인식 전환이 실제로 리팩터링을 하게 만드는 핵심이다.
점진적 리팩터링: 레거시 코드를 안전하게 진화시키기
처음부터 다 갈아엎는 리라이트는 유혹적이지만 위험하다. 반대로, 점진적 리팩터링은 지루해 보이지만 강력하다.
점진적 리팩터링이란 다음을 뜻한다.
- 아주 작은 변화들을, 의도적으로 여러 번 쌓아 가기
- 각 단계마다 시스템은 계속 동작해야 함
- “빅뱅” 방식의 한 번에 갈아엎기 없음
즉, 이렇게 말하는 대신:
“이 모듈 전체를 3개월 안에 새로 만들겠습니다.”
이렇게 접근한다:
“이 모듈을 매주 조금씩 덜 고통스럽게 만들겠습니다. 대신, 그동안 배포는 계속합니다.”
좋은 점진적 변경의 특징
90분 세션에서 “좋은” 리팩터링은 대략 다음과 같다.
- 동작 보존(behavior‑preserving) – 리팩터링 전후에 시스템이 하는 일은 같아야 한다.
- 범위 제한(scoped) – 작은 영역에만 손을 대고, 보이는 모든 문제를 다 쫓아가지 않는다.
- 되돌리기 쉬움(reversible) – 테스트나 운영에서 문제 발견 시, 롤백하기 쉬워야 한다.
안전한 점진적 단계 예시:
- 헷갈리는 메서드 이름을 명확하게 바꾸고 호출부 전체를 업데이트
- 200줄짜리 메서드를 작고 이름이 분명한 헬퍼 메서드들로 나누기
- 매직 숫자나 문자열을 이름 있는 상수나 설정값으로 치환
- 책임이 뒤섞인 부분을 작은, 포커스된 클래스로 빼내어 책임을 캡슐화
이런 자잘한 변화들이 쌓이다 보면, 정말 지독한 레거시 코드도 시간이 지날수록 점점 이해 가능한 구조로 정리된다.
테스트: 리팩터링을 위한 안전망
테스트 없이 리팩터링하는 건, 검사 없이 수술하는 것과 비슷하다.
탄탄한 테스트 관행은 리팩터링을 ‘도박’에서 ‘통제 가능한 반복 가능한 작업’으로 바꿔 준다.
이 문맥에서 말하는 “강한 테스트”란
100% 커버리지가 꼭 필요하진 않다. 하지만 다음은 필요하다.
- 빠른 자동화 테스트 – 90분 세션 동안 여러 번 돌릴 수 있을 정도의 속도
- 중요 비즈니스 동작에 대한 커버리지 – 문제가 비싸게 치밀 수 있는 흐름: 결제, 인증, 데이터 무결성 등
- 신뢰 지표 – 테스트가 통과하면, 핵심 동작을 깨뜨리지 않았다는 합리적인 확신을 줄 것
엉망인 테스트 상태에서 시작해야 한다면
진짜 지저분한 시스템에서는, 리팩터링보다 먼저 해야 할 일이… 테스트 추가인 경우가 많다.
좋은 패턴은 다음과 같다.
- 특정 동작 또는 버그 하나를 골라낸다.
- 지금의(비록 못생겼더라도) 동작을 그대로 캡처하는 테스트를 작성한다.
- 테스트를 실행해 통과/실패 여부를 확인한다.
- 아주 작은 단계로 리팩터링하면서, 테스트를 계속 돌려 본다.
첫 번째 90분 세션 전체를 “특성화 테스트(characterization test)”만 작성하는 데 쓸 수도 있다. 즉, 코드를 정리하기 전에 현재 코드가 실제로 무엇을 하는지 문서화하는 테스트들이다.
이 테스트들이 이후 세션에서 코드를 재구성할 때 안전망 역할을 해 준다.
지저분한 코드를 위한 실용적인 리팩터링 패턴들
다음은 90분 리팩터링 블록에 특히 잘 맞는 간단한 패턴들이다.
1. 이름을 명확하게 바꾸기 (Rename for clarity)
목표: 코드를 읽는 것만으로 도메인 설명을 읽는 듯한 느낌을 주기.
예시:
doStuff()→calculateOrderTotal()flag→isEligibleForDiscountdataMap→customerIdToSubscription
이름 바꾸기는 리스크는 낮지만, 이해도 측면에서 효과는 크다. 현대 IDE들은 리네임 리팩터링을 매우 안전하고 기계적으로 처리해 준다.
2. 메서드 추출 (Extract Method)
목표: 긴 함수를, 이름이 붙은 재사용 가능한 작은 블록들로 쪼개기.
Before:
void processOrder(Order order) { // validate // apply discounts // calculate totals // send confirmation email }
After (몇 번의 세션에 걸쳐):
void processOrder(Order order) { validate(order); applyDiscounts(order); calculateTotals(order); sendConfirmation(order); }
추출된 각 메서드는 별도의 테스트를 두거나, 기존 상위 레벨 테스트에 의해 간접적으로 커버되도록 한다.
3. 클래스 추출 (Extract Class)
목표: 서로 관련된 데이터와 동작의 묶음을 하나의 타입으로 빼내기.
“별걸 다 하는” 클래스나 모듈에서는 다음과 같은 경계(seam)를 찾아볼 수 있다.
- 결제 로직
- 이메일 알림 로직
- 가격 계산 로직
예를 들어, OrderService 안에 뒤섞여 있는 가격 계산 로직을 PriceCalculator라는 클래스로 분리할 수 있다. 90분 블록 하나에서는 메서드 한두 개만 옮기고, 이후 세션에서 나머지를 천천히 옮겨 가도 된다.
4. 파라미터/설정 도입 (Introduce Parameter or Configuration)
목표: 매직 값과 하드코딩된 의사결정을 제거하기.
예시:
- 여기저기 흩어져 있는
if (country == "US")를 전략 패턴이나 설정 조회 로직으로 대체 - 여러 곳에 등장하는 숫자
3대신MAX_RETRY_ATTEMPTS상수 사용
이 작업도 점진적으로 할 수 있다. 우선 상수나 설정을 하나 도입하고, 호출부를 조금씩 그 값으로 교체해 가면 된다.
5. 외부 의존성 감싸기 (Wrap External Dependencies)
목표: 외부 API나 라이브러리 주변에 안정적인 경계(seam)를 만드는 것.
다음과 같이 직접 라이브러리를 호출하던 코드를:
axios.post("https://service/endpoint", data)
로컬 추상화로 감싼다:
paymentClient.chargeCustomer(customerId, amount)
이렇게 해 두면, 이후 세션에서 paymentClient 구현을 마음껏 리팩터링하더라도 나머지 코드베이스는 거의 손댈 필요가 없다. 테스트에서도 이 추상화를 쉽게 mock 할 수 있다.
“속도를 잃지 않는 품질”로 가는 길로서의 리팩터링
현대 소프트웨어 팀은 끊임없이 압박을 받는다. 더 빠르게 출시해야 하고, 더 빨리 적응해야 하고, 더 빨리 복구해야 한다.
지저분한 코드는 이 목표와 정반대 방향으로 작동한다. 온보딩을 느리게 만들고, 디버깅을 복잡하게 하고, 변경을 위험하게 만든다. 바로 이 지점에서, 규칙적이고 훈련된 리팩터링이 빛을 발한다.
90분 리팩터링이 속도를 올리는 방식
시간이 지나면, 코드 품질과 리팩터링 습관을 우선순위에 두는 팀은 생산성이 25% 이상 향상되는 경우도 흔하다.
- 버그 감소 – 더 명확하고 잘 구조화된 코드는 의도치 않은 부작용을 줄인다.
- 빠른 변경 – 잘 분리된 모듈은 안전하게 고치기 더 쉽다.
- 인지 부하 감소 – 개발자가 코드를 해석하는 데 쓰는 시간이 줄고, 실제로 만드는 데 쓰는 시간이 늘어난다.
- 온보딩 용이 – 새 팀원이 “코드 자체가 설명서인” 시스템에서는 더 빨리 생산성이 오른다.
이게 바로 “품질과 속도를 동시에” 구현하는 실제 방법이다. 외부 기능 딜리버리와 싸우지 않고, 오히려 뒷받침해 주는 방식으로 내부 코드 품질을 꾸준히 개선해 가는 것이다.
지속적인 리팩터링 문화를 만드는 법
“히어로 한 명의 투혼”이나, 일회성의 대규모 정리 프로젝트에 기대면 안 된다. 진짜 효과는 팀 전체의 표준적인 관행으로 리팩터링이 자리 잡을 때 나온다.
1. 워크플로 안에 리팩터링을 정상화하기
- 리팩터링 작업을 백로그에 정식 태스크로 올린다.
- 기능 작업 중에 작은 기회성(opportunistic) 리팩터링을 허용한다. “캠핑장을 떠날 땐, 올 때보다 조금 더 깨끗이.”
- 적절하다면 Definition of Done에 리팩터링을 포함한다. (예: “이번에 건드린 코드는 테스트로 커버되고, 적당히 깔끔한 상태여야 한다.”)
2. 시간을 명시적으로 보호하기
“시간 남으면 정리하자”는 말은 곧 “영원히 안 한다”는 뜻이다.
- 스프린트나 주 단위로 반복되는 90분 블록을 캘린더에 잡고 보호한다.
- 실제로 가장 고통스러운 핫스팟(에러율, 변경 빈도, 팀이 자주 불평하는 부분)을 우선순위 삼아 이 시간에 집중적으로 개선한다.
3. 품질을 보이게 만들기
- 복잡도 감소, 버그 수 감소, 빌드 시간 단축 같은 개선 지표를 추적하고 공유하며 축하한다.
- 코드 리뷰에서 작은, 지속적인 개선을 장려한다.
- 팀 미팅에서 리팩터링 전/후 예시를 공유해, 눈에 보이는 변화를 함께 본다.
4. 리더가 모범을 보이기
테크 리드와 시니어 엔지니어는 다음을 해야 한다.
- 자신들도 90분 블록을 사용하고, 그 시간에 무엇을 했는지 공유한다.
- 단기 이득을 위해 장기적인 혼란을 남기는 지름길에 대해 적절히 제동을 건다.
- 안전한 리팩터링 패턴과 테스트 주도 변경 방법을 다른 팀원에게 멘토링한다.
“우리 팀은 원래 이렇게 일한다” 수준으로 리팩터링 문화가 자리 잡으면, 그 효과는 팀 전체에서 증폭된다.
90분 리팩터링, 이렇게 시작해 보기
이번 주부터 바로 시작할 수 있다.
- 고통 지점을 하나 고른다. 모두가 피하거나 불평하는 파일/모듈을 선택한다.
- 90분 블록 하나를 잡는다. 회의처럼 캘린더에 박아 두고, 방해받지 않도록 한다.
- 테스트부터 쓰거나 개선한다. 몇 개의 핵심 테스트라도 없는 것보단 낫다.
- 작은 패턴 하나를 고른다. 이름 바꾸기, 메서드 추출, 의존성 감싸기 같은 것 중 하나.
- 안전한, 점진적 개선 하나를 커밋한다. 완벽을 쫓지 말고, 분명 좋아진 한 걸음만 남긴다.
- 다음 주에 반복한다. 이번에 배운 걸 반영해 방식을 조금씩 다듬는다.
한 달쯤 지나면, 이런 변화가 느껴지기 시작할 것이다.
- 가장 심각한 핫스팟이 예전만큼 무섭지 않다.
- 코드 리뷰가 더 수월하고 빠르게 느껴진다.
- 배포할 때의 “예상 못 한 사고”가 줄어든다.
6개월쯤 지나면, 시스템은 여전히 “레거시”일 수 있다. 하지만 이제는 이해 가능하고, 테스트 가능하고, 훨씬 덜 고통스럽게 다룰 수 있을 것이다.
맺음말
지저분한 프로젝트는 한 번의 영웅적인 리라이트로 해결되지 않는다. 수십, 어쩌면 수백 번의 작고 안전한 90분 리팩터링으로 해결된다.
시간을 정해 둔 점진적 리팩터링을, 탄탄한 테스트와 실용적인 패턴들로 뒷받침하면, 기능 딜리버리를 멈추지 않고도 내부 품질을 꾸준히 끌어올릴 수 있다. 시간이 지나면 그 보상은 분명해진다. 더 높은 생산성, 더 적은 버그, 그리고 팀이 두려워하지 않고 손댈 수 있는 코드베이스.
작게 시작하라. 90분을 지켜내라. 거친 모서리 하나를 다듬어라.
그리고 다음 주에도 똑같이 해라.
그렇게 해서, 지저분한 프로젝트는 어느새 조용히 유지보수 가능한 시스템으로 변해 간다. 한 번에 하나씩, 아주 작은 리팩터링으로.