콘텐츠로 이동

리팩터링 실전 강의 교재

2장 — 리팩터링 원칙

원서: 마틴 파울러 『리팩터링 2판』 대상: Java/Spring·JS/TS 백엔드 입문~중급 수강생 형식: 개념 → 비유 → 현업 예제 → 함정 → 핵심 교훈 → 체크리스트 → 퀴즈


0. 이 장을 시작하기 전에

0.1 학습 목표

  • "리팩터링"이라는 단어를 정확한 의미로 쓴다(아무 코드 수정이나 리팩터링이 아니다).
  • 언제·왜 리팩터링하는지를 경제적 관점에서 설명한다.
  • 성능·아키텍처·YAGNI·테스트와 리팩터링의 관계를 정리한다.

0.2 큰 그림 — 1장의 감각을 원칙으로

1장에서 우리는 "작은 단계 + 테스트 + 보상"의 리듬을 손으로 느꼈습니다. 2장은 그 감각을 언어화합니다.

[ 무엇인가 ]        [ 왜·언제 하나 ]            [ 무엇과 부딪히나 ]
 2.1 정의           2.3 이유                     2.5 고려사항(소유권/브랜치/DB)
 2.2 두 개의 모자   2.4 시점(3의 법칙/준비)      2.6 YAGNI / 아키텍처
                    2.7 개발 프로세스           2.8 성능
                                                2.10 자동화

2.1 리팩터링의 정의 — 좁고 엄밀하게

비유 — "옷장 정리"

옷(기능)은 하나도 버리거나 새로 사지 않습니다. 단지 배치만 바꿔 찾기 쉽게 만듭니다. 정리 전후로 "어떤 옷이 있는가"(겉보기 동작)는 똑같습니다.

핵심

  • 명사 리팩터링: 겉으로 드러나는 동작은 그대로 유지한 채, 코드를 이해·수정하기 쉽게 바꾸는 정형화된(이름 붙은) 변경 한 가지.
  • 동사 리팩터링: 그런 변경들을 연달아 적용해 코드를 재구성하는 행위.
  • 가장 중요한 조건: 관찰 가능한 동작이 바뀌지 않는다.

흔한 오용 (중요)

"코드 좀 정리했어요 = 리팩터링"이 아닙니다. 동작이 바뀌면 그건 리팩터링이 아니라 기능 변경 또는 버그 수정입니다. 며칠간 코드를 뒤집어엎고 "리팩터링 중"이라고 말한다면, 십중팔구 그건 리팩터링이 아닙니다(중간에 동작이 깨져 있으므로). 진짜 리팩터링은 언제 멈춰도 코드가 동작하는 작은 단계의 연속입니다.

현업 메모: PR 제목에 "refactor"를 달았다면, 그 PR은 동작/테스트 결과를 바꾸지 않아야 합니다. 동작이 바뀐다면 별도 PR로 분리하세요. 리뷰어가 "구조만 바뀐 건지" 안심하고 볼 수 있습니다.


2.2 두 개의 모자

비유 — "요리사 모자 vs 청소부 모자"

  • 기능 추가 모자: 새 기능을 만든다. 테스트를 추가하고, 동작을 늘린다. 구조는 손대지 않는다.
  • 리팩터링 모자: 구조만 다듬는다. 새 기능도, 새 테스트도 추가하지 않는다(인터페이스가 바뀌어 기존 테스트를 고칠 때만 예외).

핵심

개발 중 두 모자를 수시로 바꿔 씁니다. 단, 동시에 두 개를 쓰지 않습니다. "지금 나는 어떤 모자를 썼나?"를 늘 자각하는 것이 규율입니다. 이 자각이 흐려지면, 리팩터링과 기능 변경이 뒤엉켜 버그가 어디서 들어왔는지 추적이 불가능해집니다.


2.3 리팩터링하는 이유

비유 — "정비된 길 vs 수풀길" (설계 지구력 가설)

새 기능을 얹는 속도를 시간에 따라 그려보면:

누적
기능량
  │                         ┌──  잘 설계한 코드(리팩터링 O)
  │                    ┌────┘
  │              ┌─────┘
  │         ┌────┘ ······· 방치한 코드(리팩터링 X)
  │     ┌───┘·······
  │  ┌──┘····
  └──┴──────────────────────────▶ 시간
        (초반엔 비슷, 갈수록 격차)

초반엔 "정리 안 하는 쪽"이 약간 빨라 보입니다. 하지만 코드가 엉킬수록 수풀길은 점점 느려지고, 정비된 길은 갈수록 빨라집니다. 이 교차점이 생각보다 빨리 옵니다(몇 주 단위).

리팩터링이 주는 것

  1. 설계가 개선된다. 방치하면 코드는 엔트로피로 무너진다. 리팩터링은 구조를 되살린다.
  2. 이해하기 쉬워진다. 의도가 코드에 드러나, 6개월 뒤의 나와 동료가 빨리 파악한다.
  3. 버그를 찾기 쉬워진다. 구조가 명확하면 결함이 숨을 곳이 줄어든다.
  4. 개발이 빨라진다. 위 가설의 결론. 리팩터링은 속도를 위한 투자다.

한 줄 요약: 리팩터링은 "착해서" 하는 게 아니라 "빨라지려고" 한다. 경제적 행위입니다.


2.4 언제 리팩터링해야 할까?

수시(상시) 리팩터링이 기본

"리팩터링 주간"을 따로 잡는 것은 오히려 위험 신호입니다. 리팩터링은 평소 기능 개발 흐름에 녹여 조금씩 합니다.

세 가지 타이밍

  • 3의 법칙(Rule of Three): 같은 일을 세 번째 하게 되면 그때 리팩터링한다. (한 번은 그냥, 두 번째는 눈감고, 세 번째는 정리)
  • 준비를 위한 리팩터링: 기능을 추가하기 직전에, 추가하기 쉽도록 먼저 구조를 다듬는다. (1장의 메시지)
  • 이해를 위한 리팩터링: 낯선 코드를 파악하며, 이해한 바를 코드에 반영해 다음 사람이 덜 헤매게 한다.
  • 쓰레기 줍기(보이스카우트 규칙): 지나가다 작은 악취를 발견하면, 부담되지 않는 선에서 줍고 간다. "처음 봤을 때보다 깨끗하게 두고 떠나라."

코드 리뷰와 결합

리뷰에서 "이렇게 바꾸면 더 좋겠다"는 지점은 리팩터링 후보입니다. 가능하면 리뷰어와 함께 다듬으면 학습 효과가 큽니다.

현업 메모(차세대/유지보수): 새 요구가 들어왔을 때 곧장 기능을 욱여넣지 말고, 준비를 위한 리팩터링으로 "그 기능을 받기 좋은 모양"을 먼저 만든 뒤 추가하세요. 결과적으로 더 빠르고 사고가 적습니다.


2.5 리팩터링 시 고려할 문제

(1) "기능 개발이 느려지지 않나?"

단기적으로는 그래 보입니다. 하지만 2.3의 가설대로, 리팩터링은 미래 개발 속도를 사는 투자입니다. 경제적 판단이 필요한 것이지, 무조건/도덕적으로 하는 일이 아닙니다.

(2) 코드 소유권

함수 옮기기·이름 바꾸기는 호출하는 다른 팀 코드를 깨뜨릴 수 있습니다. 공개 API라면 기존 시그니처를 한동안 유지(deprecated)하며 점진 이전하는 배려가 필요합니다.

(3) 브랜치

오래 산 기능 브랜치는 머지 지옥을 부릅니다. 리팩터링은 대규모 구조 변경을 동반하므로 충돌이 큽니다. 짧은 브랜치 + 잦은 통합(CI)이 리팩터링과 궁합이 좋습니다.

(4) 테스트

자가 테스트가 안전망의 전부입니다. 테스트 없는 코드는 리팩터링할 수 없습니다. 4장에서 다룹니다.

(5) 레거시 / 데이터베이스

  • 테스트 없는 레거시는 먼저 특성화 테스트(characterization test)로 "현재 동작"을 고정한 뒤 손댑니다.
  • DB는 진화적(점진적) 마이그레이션으로 다룹니다(스키마도 작은 단계로 변경).

현업 메모: WebLogic/레거시 시스템처럼 테스트가 빈약한 코드는, 리팩터링 전에 "지금 이 함수가 무엇을 출력하는가"를 스냅샷처럼 박제하는 테스트부터 만드세요. 그게 안전망이 됩니다.


2.6 리팩터링, 아키텍처, 그리고 YAGNI

비유 — "안 쓸 방을 미리 짓지 마라"

"나중에 손님이 올지도 모르니" 방 다섯 개를 미리 지으면, 청소·난방 비용만 늘고 정작 안 씁니다. 필요해지면 그때 증축하면 됩니다.

핵심

  • YAGNI(You Aren't Gonna Need It): 지금 필요 없는 유연성을 미리 넣지 마라(추측성 일반화는 악취 — 3.15).
  • 리팩터링이 있기에 YAGNI가 가능합니다. "지금은 단순하게, 정말 필요해지면 그때 리팩터링으로 유연성을 추가"할 수 있으니까요.
  • 아키텍처를 처음에 다 확정(big design up front)하지 않고, 점진적으로 진화(evolutionary architecture)시킵니다.

현업 메모: 1장 단계 7의 다형성 도입도, "장르가 늘어난다"는 실제 압력이 있었기에 정당했습니다. 압력 없이 미리 추상화부터 깔면 YAGNI 위반입니다.


2.7 리팩터링과 소프트웨어 개발 프로세스

세 가지가 한 세트로 맞물려 애자일의 기술적 토대를 이룹니다.

[ 자가 테스트 ] ── 안전망 ──▶ [ 리팩터링 ] ── 깨끗한 코드 ──▶ [ 지속적 통합(CI) ]
        ▲                                                        │
        └──────────────── 빠른 피드백 ◀──────────────────────────┘
  • 자가 테스트가 있어야 리팩터링이 안전하고,
  • 리팩터링으로 코드가 늘 깨끗해야 통합이 쉽고,
  • CI로 자주 합쳐야 브랜치 충돌·머지 지옥을 피합니다.

2.8 리팩터링과 성능

비유 — "90/10 법칙: 대부분의 시간은 일부 코드에서"

프로그램 실행 시간의 대부분은 코드의 좁은 일부(핫스팟)에서 소비됩니다. 모든 코드를 미리 최적화하면, 90%는 헛수고이고 가독성만 잃습니다.

핵심

  • 리팩터링이 가끔 성능을 떨어뜨릴 수 있습니다(함수 호출 증가 등). 그래도 일단 잘 구조화하라.
  • 잘 구조화된 코드는 오히려 최적화하기 쉽습니다. 핫스팟을 찾기 쉽고, 그 부분만 정교하게 손볼 수 있으니까요.
  • 절차: ① 명료하게 만든다 → ② 측정해 핫스팟을 찾는다 → ③ 그 부분만 최적화한다.

이펙티브 자바 연결: 이 절은 EJ 아이템 67(최적화는 신중히)과 같은 메시지입니다. "추측하지 말고 측정하라."


2.9 리팩터링의 유래 (요약)

리팩터링이라는 개념과 용어는 1980~90년대 스몰토크/패턴 공동체(워드 커닝햄, 켄트 벡 등)와 객체지향 연구자들의 실천에서 자라났고, 파울러가 이를 카탈로그로 체계화했습니다. 핵심은 "작고 동작을 보존하는 변환의 누적"이라는 아이디어가 그때 이미 자리잡았다는 점입니다.

2.10 리팩터링 자동화

현대 IDE는 안전한 리팩터링을 자동으로 해줍니다. 손으로 하면 실수할 변경을, 도구가 의미를 보존하며 정확히 수행합니다.

  • IntelliJ: Extract Method / Variable, Rename(전 코드베이스 일괄), Inline, Change Signature, Move 등.
  • 단축키 한 번으로 1장의 "함수 추출"을 안전하게 적용할 수 있습니다.

현업 메모: 자동 Rename/Extract는 수동 검색치환보다 훨씬 안전합니다. 단, 자동 도구가 닿지 못하는 동적 참조(리플렉션 문자열, JSON 키, SpEL 등)는 사람이 확인해야 합니다.

2.11 더 알고 싶다면

이후 장들(3장 악취, 4장 테스트, 6장~ 카탈로그)이 2장의 원칙을 구체적 기법으로 풀어냅니다. 원칙만으로는 손이 안 움직이니, 카탈로그에서 "이름 붙은 동작"을 익히는 것이 실전의 핵심입니다.


핵심 교훈

  1. 리팩터링 = 동작 보존 구조 개선. 동작이 바뀌면 리팩터링이 아니다.
  2. 두 개의 모자를 동시에 쓰지 않는다.
  3. 리팩터링은 도덕이 아니라 경제다 — 미래 속도를 사는 투자.
  4. 수시로, 기능 추가 직전(준비)·이해하며·지나가며(쓰레기 줍기) 한다. 3의 법칙.
  5. YAGNI — 추측성 유연성은 넣지 않는다. 리팩터링이 그것을 가능케 한다.
  6. 성능은 측정 후 핫스팟만. 우선은 명료함.
  7. 자가 테스트 + 리팩터링 + CI는 한 세트.

체크리스트 (팀 규율로)

  • "refactor" PR이 정말 동작/테스트 결과를 안 바꾸는가
  • 지금 쓴 모자(기능/리팩터링)를 자각하는가
  • 기능 추가 전에 준비를 위한 리팩터링을 고려했는가
  • 손대는 코드에 자가 테스트가 있는가 (없으면 특성화 테스트부터)
  • 추측성 일반화(YAGNI 위반)를 넣고 있지 않은가
  • 성능 변경을 측정 없이 추측으로 하고 있지 않은가
  • Rename/Extract 등 IDE 자동 리팩터링을 활용하는가

퀴즈

Q1. "이번 주 내내 리팩터링했어요"가 위험 신호인 이유는?

A. 진짜 리팩터링은 언제 멈춰도 동작하는 작은 단계의 연속입니다. 며칠씩 동작이 깨진 상태로 코드를 뒤엎었다면 그건 리팩터링이 아니라 재작성/구조 변경에 가깝고, 안전망 밖에서 위험을 키운 것입니다.

Q2. "설계 지구력 가설"을 한 문장으로 설명하라.

A. 초반엔 정리 안 한 코드가 약간 빠를 수 있지만, 시간이 지날수록 잘 설계된 코드가 기능 추가 속도에서 앞선다는 것. 그래서 리팩터링은 속도를 위한 투자다.

Q3. "3의 법칙"이란?

A. 비슷한 작업을 세 번째 마주쳤을 때 리팩터링(중복 제거/추출)을 하라는 경험칙. 한 번은 그냥, 두 번째는 참고, 세 번째에 정리한다.

Q4. 리팩터링과 성능 최적화의 올바른 순서는?

A. 먼저 명료하게 구조화하고, 측정으로 핫스팟을 찾은 뒤, 그 부분만 최적화한다. 잘 구조화된 코드가 오히려 최적화하기 쉽다.

Q5. YAGNI와 리팩터링은 어떻게 서로를 돕는가?

A. 리팩터링이 있기에 "지금은 단순하게, 필요해지면 그때 유연성 추가"가 가능합니다. 즉 리팩터링이 YAGNI를 실행 가능한 전략으로 만들어 줍니다.


다음 장 예고 — 3장: 코드에서 나는 악취

"언제 리팩터링하나?"의 냄새 맡는 법을 24가지 카탈로그로 배웁니다. 중복 코드, 긴 함수, 기본형 집착, 반복되는 switch, 데이터 뭉치… 현업 코드 리뷰에서 곧장 쓸 수 있는 진단 사전입니다. (분량이 커서, 원하시면 전반부/후반부로 나눠 제작합니다.)

이어서 만들까요? (3장으로 진행 / 4장 테스트 구축으로 점프 / 지금까지의 리팩터링 1~2장을 통합 교재로 묶기)