Chapter 2

리팩터링 원칙

  • 2.1 리팩터링 정의
  • 2.2 두 개의 모자
  • 2.3 리팩터링하는 이유
  • 2.4 언제 리팩터링해야 하나
  • 2.5 리팩터링 시 고려할 문제
  • 2.6 리팩터링, 아키텍처, YAGNI
  • 2.7 리팩터링과 소프트웨어 개발 프로세스
  • 2.8 리팩터링과 성능
  • 2.9 리팩터링의 유래
  • 2.10 자동 리팩터링
  • 2.11 더 알고 싶다면

리팩터링이라는 단어, 다들 쓰잖아. 근데 진짜 뜻을 엄밀하게 아는 사람은 많지 않거든. 파울러는 이걸 칼같이 정의해. "소프트웨어의 겉보기 동작은 그대로 유지한 채, 코드를 이해하고 수정하기 쉽도록 내부 구조를 변경하는 것" — 이게 명사로서의 리팩터링이야. 핵심 키워드는 **"겉보기 동작을 유지"**한다는 거야. 사용자가 볼 때 프로그램이 달라지면 안 돼. 내부만 바뀌어야 해. 그리고 리팩터링은 코드 정리(restructuring)와 달라. 작은 단계의 연속이거든. 한 번에 하나의 기법만 적용하고, 그때마다 동작이 보존되는 걸 확인해. 대규모로 코드를 재작성하는 건 리팩터링이 아니야.

켄트 벡이 좋은 비유를 제시했어. **"두 개의 모자"**라는 거지. 소프트웨어를 개발할 때 목적이 두 가지야. 기능 추가 모자를 쓰고 있을 때는 기존 코드를 절대 건드리지 않아. 새 기능을 추가하고 테스트를 만들어. 리팩터링 모자를 쓰고 있을 때는 기능을 절대 추가하지 않아. 코드 구조만 개선해. 중요한 건 **"동시에 두 모자를 쓰지 않는다"**는 거야. 기능을 추가하다가 구조가 거슬리면, 리팩터링 모자로 갈아쓰고 구조를 고친 다음, 다시 기능 추가 모자를 써. 의식적으로 구분해야 해.

리팩터링을 왜 해야 하느냐고? 네 가지야. 첫째, 소프트웨어 설계가 좋아져. 리팩터링 안 하면 설계는 계속 부패하거든. 단기 목표만 보고 수정하다 보면 구조가 무너지고 중복이 쌓여. 둘째, 소프트웨어를 이해하기 쉬워져. 미래의 나 포함해서 다른 개발자가 이 코드를 읽게 돼. 셋째, 버그를 찾기 쉬워져. 코드를 이해하기 쉬우면 버그도 보이거든. "나는 뛰어난 프로그래머가 아니라, 뛰어난 습관을 가진 괜찮은 프로그래머다" — 켄트 벡의 명언이야. 넷째, 프로그래밍 속도를 높여줘. 직관에 반하는 것처럼 느껴질 수 있는데, 장기적으로는 빨라져. 파울러는 이걸 **"설계 지구력 가설"**이라고 불러 — 내부 설계에 투자하면 시간이 지날수록 개발 속도가 떨어지지 않는다는 거야.

그럼 언제 해야 하냐? 파울러의 원칙은 **"3의 법칙"**이야. 처음에는 그냥 해. 비슷한 일을 두 번째 하면 일단 계속해. 세 번째 하게 되면 리팩터링해. 더 구체적으로 보면, 준비를 위한 리팩터링은 기능을 추가하기 전에 구조를 먼저 정리하는 거야. **"숲을 지나가야 하는데 길이 살짝 오른쪽으로 나 있다면, 먼저 오른쪽으로 간 다음 숲을 지나가는 게 빠르다"**는 비유지. 이해를 위한 리팩터링은 코드를 읽다가 이해한 바를 코드에 반영하는 거고, 쓰레기 줍기 리팩터링은 지저분한 부분을 발견했을 때 조금씩 정리하는 거야. 파울러는 리팩터링을 별도 일정으로 잡는 걸 좋아하지 않아. 기능 추가나 버그 수정을 하면서 자연스럽게 함께 하는 게 이상적이거든. "보이스카우트 규칙 — 왔을 때보다 깨끗하게 놓고 떠나라."

하면 안 되는 경우도 있어. 지저분한 코드를 발견해도 굳이 건드릴 필요가 없다면 놔둬. API만 호출하는 거라면 캡슐화가 잘 되어 있으면 상관없거든. 처음부터 새로 작성하는 게 나을 때도 있어. 아예 동작하지 않는 코드라면 다시 쓰는 게 빠르지. 브랜치 전략도 중요한데, **"오래 유지되는 피처 브랜치는 리팩터링의 적"**이야. 브랜치를 오래 유지하면 머지 충돌이 커지고, 리팩터링으로 인한 구조 변경이 다른 브랜치와 심하게 충돌하거든. CI(지속적 통합)를 강력 추천해 — 최소 하루에 한 번은 메인라인에 합쳐라. 그리고 **"리팩터링의 핵심 전제 조건은 자가 테스트 코드"**야. 테스트가 없으면 리팩터링하기 무섭고, 테스트가 있으면 과감하게 바꿀 수 있어.

리팩터링이 있으면 아키텍처에 대한 사고방식이 완전히 바뀌어. 처음부터 완벽히 예측하려고 애쓰는 대신, **"지금 필요한 것만 만들고, 나중에 필요할 때 구조를 바꾸면 된다"**는 태도가 가능해지거든. 이게 바로 **"YAGNI — You Aren't Gonna Need It"**이야. "나중에 필요할 것 같으니까 미리 만들어두자"를 하지 말라는 거야. 미리 만들어둔 게 실제로 필요해지는 경우는 드물고, 필요해지더라도 요구사항이 달라져 있어. 하지만 YAGNI가 "설계를 안 해도 된다"는 뜻은 아니야. **"유연성 메커니즘을 미리 넣지 마라"**가 핵심이야. 당장 필요한 해법만 구현하되, 나중에 바꾸기 쉬운 형태로 만들어라.

자가 테스트 코드 + 리팩터링 + CI(지속적 통합) — 이 세 가지가 결합되면 강력해. 자가 테스트 코드가 리팩터링의 안전망이 되고, CI가 팀 전체의 리팩터링이 충돌 없이 합쳐지게 해줘. 이 세 가지를 갖추면 YAGNI 설계가 가능해지는 거야.

"리팩터링하면 성능이 나빠지지 않나?"라는 흔한 반론이 있는데, 파울러의 답은 명쾌해. "대부분의 경우 그렇다. 하지만 상관없다." 프로그램의 성능은 극히 일부 코드에 의해 결정되거든. 전체 코드의 10%가 실행 시간의 90%를 차지한다는 법칙이 있잖아. 먼저 잘 구조화된 코드를 만들고, 성능이 문제가 되면 프로파일링해서, 실제 병목 지점을 찾아서 그 부분만 최적화하는 게 맞아. 잘 구조화된 코드는 프로파일링도 쉽고, 최적화도 쉬워.

리팩터링이라는 개념은 Ward Cunningham과 Kent Beck이 Smalltalk 커뮤니티에서 시작한 거야. 파울러가 1999년에 1판을 써서 대중화했고, 2판에서는 JavaScript로 예제를 바꾸고 20년간의 경험을 반영했어. IDE의 자동 리팩터링 기능도 중요한데, IntelliJ나 VS Code 같은 도구들이 구문 트리를 이해하고 정확하게 바꿔줘. 다만 도구가 모든 걸 해주진 않으니 수동 리팩터링 능력도 중요하지.


정리

2장 읽고 기억할 거 세 가지:

  1. "리팩터링은 겉보기 동작을 유지하면서 내부 구조를 개선하는 것이다." 대규모 재작성은 리팩터링이 아니야
  2. "두 개의 모자를 동시에 쓰지 마라." 기능 추가와 리팩터링을 의식적으로 분리해야 해
  3. "YAGNI — 지금 필요한 것만 만들어라." 리팩터링 능력이 있으면 미래를 예측할 필요가 줄어들어