리팩터링: 첫 번째 예시
- 1.1 자, 시작해보자
- 1.2 예시 프로그램을 본 소감
- 1.3 리팩터링의 첫 단계
- 1.4 statement() 함수 쪼개기
- 1.5 중간 점검: 난무하는 중첩 함수
- 1.6 계산 단계와 포맷팅 단계 분리하기
- 1.7 중간 점검: 두 파일로 분리됨
- 1.8 다형성을 활용해 계산 코드 재구성하기
- 1.9 상태 점검: 다형성을 활용하여 데이터 생성하기
- 1.10 마치며
리팩터링이 뭔지 말로 설명하는 건 쉽거든. 근데 마틴 파울러는 "직접 보여주겠다"는 자세로 1장을 시작해. 이 책 전체의 축소판이야. 공연료 청구서를 출력하는 간단한 프로그램 하나를 처음부터 끝까지 리팩터링하면서, 몸으로 느끼게 해주는 챕터지.
예제는 극단이 공연 요금을 청구하는 프로그램이야. 공연 종류(비극/희극)에 따라 요금 계산이 다르고, 포인트 적립도 있어. 처음 나오는 statement() 함수는 전형적인 "일단 동작하는 코드"거든. 요금 계산, 포인트 적립, 문자열 포맷팅이 한 함수 안에 전부 뒤섞여 있어. 코드가 짧으니까 "이 정도면 괜찮지 않나?" 싶을 수 있는데, 파울러는 이렇게 말해 — "프로그램이 새로운 기능을 추가하기에 편한 구조가 아니라면, 먼저 기능을 추가하기 쉬운 형태로 리팩터링하고 나서 원하는 기능을 추가한다".
파울러가 이 코드를 보고 가장 먼저 느낀 건 **"함수가 너무 길다"**는 거야. 코드가 동작은 하지만, 변경 요청이 들어오면 골치 아파지거든. HTML로도 청구서를 출력하고 싶다면? 지금 구조에선 statement() 함수를 통째로 복사해서 수정해야 해. 공연 종류가 추가되면? switch문을 찾아서 케이스를 추가해야 하고. "컴파일러는 코드가 지저분하든 깔끔하든 신경 안 써. 하지만 사람이 코드를 바꿀 때는 미적 상태가 중요하다." 설계가 나쁜 코드는 수정하기 어렵고, 수정하기 어려운 코드는 버그를 낳아.
리팩터링하기 전에 반드시 해야 할 일이 있어. "리팩터링하기 전에 제대로 된 테스트부터 마련한다. 테스트는 반드시 자가 검증하도록 만든다." 이게 리팩터링의 전제 조건이야. 테스트 없이 리팩터링하면 실수해도 몰라. 테스트가 있으면 한 번 바꾸고 테스트 돌리고, 또 바꾸고 테스트 돌리는 리듬이 가능해져. 작은 단계로 나눠서, 매 단계마다 테스트하는 방식으로 진행하는 거야. 한 번에 크게 바꾸지 않아. 한 걸음씩, 안전하게.
본격적인 리팩터링의 시작은 **함수 추출하기(Extract Function)**야. statement() 안에 들어 있는 switch문을 amountFor()라는 별도 함수로 빼내는 거지. switch문을 새 함수로 추출하고, 변수 이름을 더 명확하게 바꾸고, 매개변수 이름도 다듬고, 테스트를 돌려서 깨진 게 없는지 확인해. 다음으로 포인트 적립 계산도 volumeCreditsFor()로, 포맷팅 로직도 usd()로 추출해. 여기서 파울러가 강조하는 게 **"임시 변수를 질의 함수로 바꾸기"**야. totalAmount나 volumeCredits 같은 임시 변수가 함수 바깥에서 값이 누적되고 있으면, 이걸 함수 호출로 바꿔버려. 변수가 사라지면 추출하기가 훨씬 쉬워지거든. 매 단계마다: 바꾸고, 컴파일하고, 테스트하고, 커밋. 이 리듬이 핵심이야.
1차 리팩터링이 끝나면 원래 하나였던 statement() 함수가 amountFor(), volumeCreditsFor(), totalAmount(), totalVolumeCredits(), usd() 같은 여러 개의 작은 함수로 쪼개져 있어. 코드의 전체 줄 수는 오히려 늘었지만, 각 함수의 역할이 명확해졌지. **"좋은 코드를 검증하는 확실한 방법은 '얼마나 수정하기 쉬운가'를 보는 것"**이라고 파울러는 말해.
다음 목표는 HTML 청구서를 지원하는 거야. 이를 위해 **단계 쪼개기(Split Phase)**를 적용해. 계산 로직과 출력 포맷팅을 분리하는 거지. 중간 데이터 구조를 만들어서, 계산 단계에서 이 구조를 채우고, 포맷팅 단계에서 이 구조를 읽어서 텍스트를 만들어. 이렇게 하면 renderPlainText()와 renderHtml() 두 함수를 만들 수 있어. 계산은 한 번만 하고, 출력 형식만 바꾸면 되니까. 이 시점에서 코드는 세 파일로 나뉘어 — statement.js(진입점 함수들), createStatementData.js(계산 로직), 출력 함수들(텍스트/HTML 렌더링). 각 부분이 명확한 책임을 가지고 있고, 새로운 출력 형식을 추가하려면 렌더링 함수만 새로 만들면 돼.
마지막 리팩터링은 가장 큰 변화야. 조건부 로직을 **다형성(polymorphism)**으로 바꾸는 거거든. 공연 종류별로 다른 계산을 하는 switch문을 서브클래스로 대체해. PerformanceCalculator라는 부모 클래스를 만들고, TragedyCalculator와 ComedyCalculator가 이를 상속하지. 생성자를 팩터리 함수로 바꾸기를 적용해서 적절한 서브클래스를 생성하고, 각 서브클래스에서 amount()와 volumeCredits()를 오버라이드해. 이제 새로운 공연 종류가 추가되면 서브클래스만 하나 더 만들면 돼. 기존 코드를 건드릴 필요가 없어.
최종 결과물을 보면, 처음의 statement() 함수 하나가 여러 파일의 여러 클래스와 함수로 분해됐어. 코드량은 확실히 늘었지. 하지만 "간결함이 지혜의 정수일지 몰라도, 프로그래밍에서만큼은 명료함이 진화할 수 있는 소프트웨어의 정수다." 각 함수가 하나의 일만 하고, 이름이 그 일을 설명해주고, 변경이 필요할 때 정확히 어디를 바꿔야 하는지 명확해. 파울러가 1장에서 보여준 핵심 기법은 세 가지야 — 함수 추출하기, 단계 쪼개기, 다형성을 활용한 조건부 로직 제거. 그리고 방법론적으로는 작은 단계로 나눠서 진행하고, 매 단계마다 테스트하고, 리팩터링 중에는 기능을 추가하지 않아. 이건 "두 개의 모자" 개념의 예고편이야. 2장에서 본격적으로 다뤄.
정리
1장 읽고 기억할 거 세 가지:
- "리팩터링 전에 테스트부터 만들어라." 안전망 없이 코드를 바꾸는 건 줄 없이 번지점프하는 거야
- "작은 단계로, 자주 테스트하며 진행하라." 한 번에 크게 바꾸면 버그를 찾기 어려워
- "좋은 코드는 수정하기 쉬운 코드다." 줄 수가 아니라 변경 용이성이 기준이야