첫 페이지의 목표 $5 + 10CHF = $10을 이제 마주한다. 하지만 통화가 다른 덧셈은 크다. 더 작은 첫걸음은 같은 통화의 덧셈 $5 + $5 = $10이다.
핵심 통찰은 벡이 여기서 도입하는 은유다. $5 + $5의 결과를 즉시 $10으로 계산하지 않는다. 대신 “5달러와 5달러의 합”이라는 **식(Expression)**을 만들어 들고 있다가, 나중에 특정 통화로 **환산(reduce)**할 때 비로소 값을 구한다. 수식을 세우는 것과 계산하는 것을 분리하는 것 — 이것이 나머지 장들을 관통하는 설계다.
사이클: 큰 테스트 → 껍데기 → 가짜 초록 → Sum 도입
@Testpublic void testSimpleAddition() { Money five = Money.dollar(5); Expression sum = five.plus(five); Bank bank = new Bank(); Money reduced = bank.reduce(sum, "USD"); assertEquals(Money.dollar(10), reduced);}// Expression, plus(), Bank, reduce() — 아무것도 없다. 컴파일 실패.
@Testpublic void testSimpleAddition() { Money five = Money.dollar(5); Expression sum = five.plus(five); Bank bank = new Bank(); Money reduced = bank.reduce(sum, "USD"); assertEquals(Money.dollar(10), reduced);}// Expression, plus(), Bank, reduce() — 아무것도 없다. 컴파일 실패.
interface Expression {}class Money implements Expression { Expression plus(Money addend) { return new Money(amount + addend.amount, currency); } // amount, currency, times, equals 는 그대로}class Bank { Money reduce(Expression source, String to) { return null; }}// 컴파일된다. reduce가 null이라 assertEquals에서 실패 — 빨강.
interface Expression {}class Money implements Expression { Expression plus(Money addend) { return new Money(amount + addend.amount, currency); } // amount, currency, times, equals 는 그대로}class Bank { Money reduce(Expression source, String to) { return Money.dollar(10); }}// 부끄러운 10. 하지만 초록이다. 이제 마음 놓고 구조를 다듬는다.
interface Expression {}class Money implements Expression { Expression plus(Money addend) { return new Sum(this, addend); } // amount, currency, times, equals 는 그대로}class Bank { Money reduce(Expression source, String to) { return Money.dollar(10); }}class Sum implements Expression { Money augend; Money addend; Sum(Money augend, Money addend) { this.augend = augend; this.addend = addend; }}// plus는 이제 값을 계산하지 않고 "합"이라는 식을 만들어 넘긴다.// Bank.reduce는 아직 Money.dollar(10) 가짜 — 13장에서 진짜로.
plus가 new Money(10, ...)을 바로 만들지 않고 new Sum(five, five)를 반환하는 전환이 이 장의 전부다. 지금은 Bank.reduce가 상수를 돌려주니 Sum이 하는 일이 없어 보인다. 하지만 구조가 먼저 서고 계산은 나중에 채운다 — 가짜 초록은 그 순서를 가능하게 하는 발판이다. Expression은 아직 메서드 하나 없는 표식 인터페이스지만, Money와 Sum을 한 지붕 아래 묶어 “reduce의 대상”이라는 역할을 부여한다.
판단 기준: $5 + 10CHF가 목표라도 첫 테스트는 $5 + $5다. 통화 변환이라는 어려움을 잠시 빼두면 “식 → 환산” 구조에만 집중할 수 있다. 함정: Expression에 지금 필요 없는 메서드를 미리 넣지 않는다. 인터페이스는 테스트가 요구할 때 자란다.
이 장이 남긴 할 일 목록
5 = $10 (컴파일·구조)- Bank.reduce(Sum) 진짜로 만들기 → 13장
- Bank.reduce(Money) — 단일 Money도 환산 대상
- 10 (환율 2:1)
다음장으로 13장