화폐 예제 마무리
- 3.1 더하기와 수식 객체
- 3.2 Sum 클래스와 reduce
- 3.3 환율과 해시 테이블
- 3.4 다중 통화 덧셈 완성
- 3.5 Expression 추상화
- 3.6 1부 회고
서로 다른 통화를 더하려면 어떻게 해야 할까? "$5 + 10CHF = $10 (환율이 2:1일 때)" 같은 기능이 필요한데, 이건 단순히 숫자를 더하는 게 아니야. 환율이라는 컨텍스트가 있어야 결과가 나오거든. 켄트 벡의 접근법은 역시나 가장 간단한 경우부터 시작하는 거야.
큰 문제는 작은 문제로 쪼개지. 다중 통화 덧셈이라는 큰 기능을 "$5 + $5 = $10"이라는 같은 통화끼리의 덧셈으로 시작해. Money에 plus() 메서드를 추가하는데, 바로 완벽한 구현을 하지 않아. 우선 가짜(fake) 구현으로 테스트를 통과시키고, 점점 진짜 구현으로 바꿔나가지. 여기서 핵심 설계 아이디어가 등장해 -- 수식을 객체로 표현하자는 거야. $5 + $5를 바로 $10으로 만드는 게 아니라, "5달러와 5달러의 합"이라는 수식 자체를 객체(Expression)로 들고 있다가, 필요할 때 환율을 적용해서 결과를 구하는 구조지. Bank가 Expression을 reduce(환산)하는 거야.
Sum 클래스가 이걸 구체화해. $5 + $5의 결과를 Sum 객체로 표현하는데, Sum은 두 개의 Money(augend와 addend)를 가지고 있어. Money.plus()가 Sum을 반환하도록 바꾸고, Bank.reduce()가 Sum을 받아서 실제 Money로 변환하지. 같은 통화끼리의 합이니까 단순히 두 amount를 더하면 돼. 이 과정에서 12장의 가짜 구현이 13장의 진짜 구현으로 교체되는데, 테스트는 한 번도 빨간 막대가 되지 않았어. 항상 초록 상태를 유지하면서 내부를 바꿔나간 거지.
다음은 환율(exchange rate) 도입이야. "2프랑을 달러로 바꾸면 1달러"라는 테스트를 쓰고, Bank에 환율을 등록해 (bank.addRate("CHF", "USD", 2)). reduce할 때 환율을 적용해서 변환하지. 환율을 저장하기 위해 해시 테이블을 사용하는데, 키는 통화 쌍(from, to)이고 값은 환율이야. Pair라는 작은 객체를 만들어서 해시 테이블의 키로 쓰는데, 여기서도 equals()와 hashCode()가 필요하지. 하지만 지금은 끔찍한 구현으로 빨리 만들어. 테스트가 있으니까 나중에 고치면 되거든.
그리고 드디어 이 책의 궁극적 목표였던 **"$5 + 10CHF = $10"**을 구현해. 테스트를 쓰고, 놀랍게도 이미 만들어둔 구조(Expression, Sum, Bank.reduce(), 환율)가 있으니까 거의 바로 통과해. Sum.reduce()에서 augend와 addend를 각각 원하는 통화로 reduce한 다음 더하면 돼. 이전 장들에서 작은 단계들을 밟아온 덕분에, 마지막 퍼즐 조각을 끼우는 건 간단했지. 작은 단계들이 모여서 큰 기능이 자연스럽게 완성되는 거야.
Sum도 Expression이니까, Sum에 대해서도 plus()와 times()를 할 수 있어야 해. Expression 인터페이스에 plus()와 times()를 추가하고, Money와 Sum 모두에서 구현하지. Sum.plus()는 새로운 Sum을 만들고, Sum.times()는 augend와 addend 각각에 times를 적용해서 새로운 Sum을 반환해. 이 과정에서 Expression이 완전한 추상화가 돼. Money든 Sum이든, Expression이면 plus(), times(), reduce()를 할 수 있지. 좋은 추상화는 처음부터 설계한 게 아니라 TDD 과정에서 필요에 의해 자연스럽게 완성된 거야.
1부 전체를 되돌아보면, 테스트를 18개 작성했고, 작은 단계로 설계를 발전시켰어. 하드코딩에서 시작해서 점진적으로 일반화했고, 중복을 제거하면서 설계가 개선됐지. 복붙도 허용했지만 반드시 제거했어. 켄트 벡이 강조하는 건 TDD는 설계 기법이기도 하다는 점이야. 테스트를 먼저 작성하면 코드의 사용자 관점에서 API를 설계하게 되거든. equals(), plus(), times() 같은 메서드들이 테스트를 통해 자연스럽게 드러났지. 그리고 **"불안함을 테스트로 바꿔라"**는 메시지가 있어. 뭔가 불안하면 테스트를 작성해. 테스트가 통과하면 자신감이 생기고, 그 자신감이 더 과감한 리팩토링을 가능하게 하니까.
정리
3장 읽고 기억할 거 세 가지:
- 수식을 객체로 표현하지. Expression, Sum, Bank.reduce() 구조로 덧셈이라는 연산을 객체로 모델링하면, 환율 적용 같은 복잡한 로직을 깔끔하게 분리할 수 있어
- 작은 단계의 축적이 큰 기능을 만들어. 같은 통화 덧셈, Sum, 환율을 차례로 쌓아올리니까 다중 통화 덧셈이 자연스럽게 완성됐지
- 불안함을 테스트로 바꿔. 테스트는 자신감을 만들고, 그 자신감이 더 과감한 설계 변화를 가능하게 해. TDD는 테스트 기법이자 설계 기법이야