한 장짜리 리팩터 스크립트: 코드베이스 부패를 막는 작은 체크리스트
단 한 장짜리 리팩터 체크리스트로 코드베이스의 느린 부패를 막고, 팀 전체에 리팩터링 책임을 분산시키며, 장기적인 품질을 희생하지 않고도 기능을 배포하는 방법을 살펴봅니다.
소개
대부분의 팀이 리팩터링을 무시하는 이유는 코드를 신경 쓰지 않아서가 아니다. 바빠서다.
급한 신규 기능, 운영 장애, 촉박한 마감일은 항상 “나중에 정리하자”보다 더 중요해 보인다. 하지만 그 “나중에”는 거의 오지 않고, 코드베이스는 조용히 썩어 들어간다. 급한 땜질이 쌓이고, 추상화는 조금씩 무너지고, 새로운 변경이 생길 때마다 점점 더 어렵고 위험해진다.
이 문제는 분기마다 한 번씩 하는 영웅적인 대청소로 해결되지 않는다. 작고, 지루하지만, 강제로 지키는 습관으로 해결한다.
이 글에서는 한 장짜리 리팩터 스크립트를 설계해 본다. 어떤 기능이나 버그 수정을 마치기 전에 실행하는 체크리스트다. 매일 써도 부담 없을 만큼 작지만, 코드베이스가 자기 무게에 짓눌려 붕괴되는 것을 막을 만큼 강력하다.
좋은 팀에서도 코드베이스가 썩는 이유
코드 부패(code rot)는 도덕성의 문제가 아니다. 거의 물리 법칙에 가깝다.
시간이 지날수록, 잘 작성된 코드조차 자연스럽게 품질이 떨어진다:
- 요구사항이 변한다 – 처음 설계가 더 이상 새로운 사용 사례에 맞지 않는다.
- 빠른 땜질이 쌓인다 – 핫픽스와 “임시 해킹”은 거의 정리되지 않는다.
- 스타일이 충돌한다 – 여러 사람이 각기 다른 패턴과 추상화를 섞어 넣는다.
- 기술 부채가 복리로 쌓인다 – 자주 손대는 핫스팟의 작은 문제들이 반복·복제·우회된다.
지속적인 리팩터링 메커니즘이 없으면, 이런 부패는 커다란 “정리 프로젝트”를 할 때만 다루게 된다. 하지만 이런 프로젝트는:
- 비용과 위험이 크고
- 우선순위에서 밀리기 쉽고
- 늘 시간 부족으로 어중간하게 끝난다
필요한 것은 거창한 프로젝트가 아니라, 일상적인 개발 흐름에 붙어 있는 내장형 리팩터 루틴이다.
그 역할을 하는 것이 바로 한 장짜리 체크리스트다.
왜 작은 체크리스트가 거대한 의지보다 강력한가
이미 리팩터링을 해야 한다는 사실은 다 알고 있다. 문제는 지식이 아니라, 압박 속에서도 꾸준히 하는 것이다.
짧고 문서화된 체크리스트는 다음과 같은 점에서 도움이 된다:
- 결정 피로를 줄인다 – 매번 무엇을 할지 기억할 필요 없이, 단계가 눈앞에 있다.
- 좋은 습관을 표준화한다 – 팀 모두가 비슷한 리팩터링 기준과 감각을 적용한다.
- 스트레스 상황에서도 버틴다 – “일단 내자”는 압박 속에서도, 막연한 원칙보다 짧은 체크리스트가 훨씬 따르기 쉽다.
- 품질을 가시화한다 – 코드 리뷰에서 “리팩터 스크립트 돌렸나요?”라고 물을 수 있다.
항공기의 체크리스트를 떠올려 보자. 조종사는 비행하는 법을 안다. 하지만 피곤하고 급한 상황에서도 실수를 줄이게 해 주는 것이 체크리스트다.
좋은 리팩터 스크립트는 한 페이지에 들어가야 하고, 시간이 지나면 자연스럽게 외울 수 있을 정도로 간단하며, 거의 모든 변경에서 빠르게 실행할 수 있어야 한다.
리팩터링 베스트 프랙티스는 고정되어 있지 않다
또 하나 중요한 점: 리팩터링 베스트 프랙티스는 시간이 지나며 바뀐다는 것이다.
새로운 언어 기능(패턴 매칭, async/await, records 등)이 등장하고, 새로운 아키텍처 패턴이 유행하며, 도구는 좋아지고, 라이브러리는 사라진다.
리팩터링 습관이 5년 전에 읽은 책에만 기반한다면, 지금은 이미 구식일 가능성이 크다.
즉, 한 장짜리 스크립트는 신성불가침 문서가 아니라는 뜻이다. 살아 있는 문서여야 한다. 몇 달 간격으로 이렇게 돌아보자:
- PR에서 반복해서 보이는 코드 스멜은 무엇인가?
- 어떤 리팩터가 꾸준히 버그를 줄이고, 리뷰 충돌을 줄이는가?
- 어떤 새로운 언어 기능이 우리의 패턴을 더 단순하게 만들 수 있는가?
- 신뢰할 만한 자료(리팩터링 책, 기술 블로그, 언어 공식 문서 등)에서 최근에 제안하는 것은 무엇인가?
이런 정보들을 반영해 체크리스트를 가볍게 업데이트하자. 짧게 유지하되, 현재성을 잃지 않게 만들면 된다.
책임 분담: 준비하는 사람 vs. 실행하는 사람
지속 가능한 리팩터링을 위한 강력한 모델은 책임 분담이다:
- 코드를 처음 작성하는 사람은 코드를 리팩터링 가능하게 만드는 책임이 있다.
- 나중에 그 코드를 수정하는 사람은 스멜을 발견했을 때 실제 리팩터링을 수행하는 책임이 있다.
이 구분은 미묘하지만 매우 중요하다.
처음 작성자의 역할
새 코드를 도입할 때, 당신의 책임은 다음과 같다:
- 나중에 안전하게 모양을 바꿀 수 있도록 함수와 클래스를 충분히 작게 유지한다.
- 불안정하거나 지저분한 영역에 새 코드를 과하게 결합시키지 않는다.
- 앞으로 자주 바뀔 가능성이 있는 동작에는 테스트를 추가한다.
- 명확한 이름과 단순한 제어 흐름을 사용한다.
모든 미래 변경을 예측할 필요는 없다. 다만, 미래의 변경이 덜 위험해지도록 만들어 두면 된다.
미래 개발자의 역할
기존 코드를 건드릴 때(심지어 전혀 다른 기능이나 버그 수정을 하더라도) 당신은:
- 중복, 과도하게 긴 함수, 뒤엉킨 조건문, 매직 값 같은 스멜을 발견한다.
- 이미 그 파일이나 모듈을 열어둔 김에, 작고 안전한 리팩터를 적용한다.
- 코드를 처음보다 조금이라도 더 나은 상태로 두고 간다.
한 장짜리 스크립트는 이 과정을 구체화한다. 팀 모든 개발자에게 “더 나아진 상태”가 정확히 어떤 모습인지, 그리고 이상한 코드를 발견했을 때 무엇을 해야 할지 알려 준다.
한 장짜리 리팩터 스크립트 설계하기
아래는 바로 가져다 쓸 수 있는 샘플 한 페이지 체크리스트다. 목표는 완벽함이 아니라, 가볍지만 반복 가능한 개선이다.
1. 가드레일: 테스트와 안전장치
어떤 리팩터를 하든, 전후로 다음을 확인한다:
- 테스트를 실행한다. 테스트가 없다면, 당신이 변경하는 동작을 감싸는 작은 테스트를 최소 하나라도 작성한다.
- 동작 변경은 피한다. 반드시 동작을 바꿔야 한다면, 해당 변경을 별도의, 문서화된 커밋으로 분리한다.
2. 손댄 부분의 로컬 정리
수정한 모든 파일에서, 짧게 한 번 훑어본다:
-
이름의 명확성
- 변수/함수/클래스 하나라도 의도가 더 잘 드러나게 이름을 바꿀 수 있는가?
data,item,flag같은 모호한 이름을 더 구체적인 이름으로 바꾼다.
-
중복
- 방금 로직을 복붙했다면, 공통 부분을 추출한다.
- 근처에 비슷한 코드가 보인다면: 지금 안전하게 합칠 수 있는가? 어렵다면 TODO와 이슈 링크라도 남긴다.
-
함수 크기와 책임
- 한 함수가 너무 많은 일을 하고 있는가? 작은 헬퍼 함수 하나만이라도 추출해 본다.
- 한 가지 명확한 변경 이유(single responsibility)를 가지는 함수를 목표로 한다.
-
제어 흐름 단순화
- 가능하다면 깊게 중첩된
if/else를 평탄하게 만든다. - 매직 넘버/매직 스트링을 의미 있는 상수나 enum으로 교체한다.
- 가능하다면 깊게 중첩된
3. 모듈 차원의 구조적 건강 상태
조금 더 높은 수준(모듈 / 클래스 / 컴포넌트)에서 살펴본다:
-
의존성 방향
- 사소한 작업에 너무 무거운 의존성을 끌어다 쓰고 있지는 않은가? 분리하거나 의존성을 역전(invert)할 수 있는지 본다.
-
죽은 코드나 구식 코드
- 사용되지 않는 함수, 더 이상 필요 없는 기능 플래그, 주석 처리된 코드 블록을 제거한다.
-
경계(boundary)의 명확성
- 이 모듈이 서로 다른 관심사(비즈니스 로직 + HTTP + DB 등)를 뒤섞고 있는가? 작더라도 경계를 한 번 나눠본다.
4. 미래 리팩터를 위한 준비
지금 당장 안전하게 정리하기 어렵다면:
- 핫스팟을 명확히 표시한다 (주석, 이슈, 코드 어노테이션 등).
- 다음 사람이 안전망을 가질 수 있도록, 해당 불안정한 동작 주위에 **특성화 테스트(characterization test)**를 추가한다.
- 무엇을 왜 리팩터링해야 하는지 짧은 메모를 남긴다.
이 스크립트는 의도적으로 소박하게 설계되어 있다. 이름 몇 개 변경, 작은 추출, 사소한 단순화, 그리고 테스트 실행. 이 정도만 꾸준히 해도, 코드베이스의 장기적인 방향은 ‘부패’가 아니라 ‘개선’ 쪽으로 서서히 꺾인다.
프로세스 설계: “준비되기 전엔 끝난 게 아니다”
체크리스트는 전달(딜리버리) 프로세스에 녹아 있을 때만 효과가 있다.
리팩터 스크립트를 실행하기 전에는 기능을 완료할 수 없도록 워크플로를 설계하자. 예를 들어:
- Pull Request 템플릿에 “리팩터 스크립트 완료 여부”를 필수 체크 항목으로 추가한다.
- 코드 리뷰어가 반드시 이렇게 확인하게 한다: “여기 손보면서 어떤 리팩터를 했나요?”
- Definition of Done(완료 정의)에 다음을 포함한다:
- 테스트 추가/수정 완료
- 손댄 모든 파일에서 리팩터 스크립트 실행
이렇게 하면 두 가지 효과가 생긴다:
- 사소한 리팩터가 정상이 된다 – 더 이상 딱히 ‘추가 작업’이 아니라, 기본 규칙이 된다.
- 외부 압박에 버틴다 – 누군가 “그냥 빨리 내자”고 하더라도, 팀이 합의한 최소 기준선은 쉽게 무너지지 않는다.
여기서 목표는 완벽을 강요해 배포를 막는 것이 아니다. 목표는 모든 변경에서 최소한의 위생 수준을 보장하는 것이다.
“지금 당장 내야 해요” 압박 속에서 살아남기
외부 압박은 장인 정신의 가장 큰 적이다. 마감이 다가오면, 좋은 의도는 가장 먼저 사라진다.
가볍고, 프로세스로 강제되는 체크리스트는 이런 상황에서 유용하다. 이유는:
- 협상의 여지를 줄인다 – “이거 정말 정리해야 하나요?”를 토론하는 대신, 미리 합의한 스크립트를 따를 뿐이다.
- 개선 범위가 작다 – 2주짜리 리팩터 프로젝트를 설득하는 게 아니라, 몇 분짜리 로컬 정리만 요구한다.
- 트레이드오프를 눈에 보이게 만든다 – 정말 스크립트를 건너뛰어야 하는 상황(예: 심각한 운영 장애)이라면, 그 사실을 기록하고 의식적으로 나중에 돌아오기로 결정한다.
조직이 리팩터 스크립트를 지키는 것이 배포 속도를 유의미하게 늦추지 않으면서, 안정성과 리뷰 속도를 개선해 준다는 사실을 체감하게 되면, 압박 속에서도 이를 지키기가 훨씬 쉬워진다.
스크립트를 살려 두기: 주기적인 점검
마지막으로, 한 장짜리 리팩터 스크립트를 살아 있는 문서로 대하자:
- 3–6개월마다 한 번씩 검토한다.
- 아무도 쓰지 않는 항목은 과감히 제거한다.
- 팀이 자주 마주치는 스멜에 대해 구체적인 체크 항목을 추가한다.
- 사후 회고(postmortem)나 장애 분석에서 얻은 교훈을 반영한다.
아예 “리팩터 스크립트 회고(Retrospective)”를 열어 볼 수도 있다:
- 우리가 가장 자주 무시하는 단계는 무엇인가? 왜 그런가?
- 시간 대비 효과가 가장 큰 단계는 무엇인가?
- 일부 항목을 자동화해 줄 새로운 도구(린터, 포매터, 정적 분석기 등)가 있는가?
목표는 문서를 화려하게 만드는 것이 아니다. 실제로 쓰이게 만드는 것이다.
결론
리팩터링이 남는 시간과 특별한 의지에만 의존한다면, 코드 부패는 필연적이다. 이 상황을 바꾸는 해법은 더 큰 의지나 더 거대한 정리 프로젝트가 아니다. 일상적인 작업 흐름에 구워 넣은, 작고 습관적인 스크립트다.
이런 한 장짜리 리팩터 체크리스트를 설계해 보자:
- 작고, 안전하고, 로컬한 개선에 초점을 맞추고
- 코드를 처음 쓰는 사람(준비)과 나중에 수정하는 사람(실행)의 책임을 나누며
- Definition of Done과 리뷰 프로세스를 통해 강제되고
- 베스트 프랙티스, 도구, 코드베이스가 변함에 따라 같이 진화하는 체크리스트.
모든 기능과 버그 수정에 이 스크립트를 사용해 보라. 몇 달이 지나면 눈에 띄는 변화가 생긴다. 지뢰밭이 줄고, 온보딩이 빨라지고, 변경이 단순해지고, 두려움이 줄어든다.
코드베이스는 어차피 나이를 먹는다. 하지만 한 장짜리 리팩터 스크립트가 있다면, 그것은 썩어 가지 않고 성숙해 간다.