책이 시작하며 내건 $5 + 10CHF = $10(환율 2:1). 열다섯 장 만에 그 테스트를 쓴다. 10프랑은 5달러이니 5달러와 더하면 10달러다.

지금 Sum.reduce는 두 항의 amount를 그냥 더한다 — 통화가 같을 때만 맞는 계산이다. 5와 10을 더하면 15가 나오는데, 답은 10이다. 무엇이 빠졌나. 더하기 전에 각 항을 목표 통화로 환산해야 한다. 12장에서 세운 “식을 들고 있다가 환산 시점에 계산한다”는 구조가 바로 이 순간을 위한 것이었다.

사이클: 빨강 → 각 항을 먼저 환산

Refactoring Step 빨강 — 다른 통화의 합은 15가 나온다
1/2
@Testpublic void testMixedAddition() {    Money fiveBucks = Money.dollar(5);    Money tenFrancs = Money.franc(10);    Bank bank = new Bank();    bank.addRate("CHF", "USD", 2);    Money result = bank.reduce(fiveBucks.plus(tenFrancs), "USD");    assertEquals(Money.dollar(10), result);}class Sum implements Expression {    Money augend;    Money addend;    public Money reduce(Bank bank, String to) {        int amount = augend.amount + addend.amount;        return new Money(amount, to);    }}// 5 + 10 = 15. 환율을 무시하고 amount만 더했다. 10 != 15, 빨강.

바뀐 것은 두 줄뿐이다. augend.amountaugend.reduce(bank, to).amount로, addend.amountaddend.reduce(bank, to).amount로. 하지만 이 작은 변화가 15장 내내 쌓아온 구조를 완성한다. Sum은 자기가 든 항이 Money든 또 다른 Sum이든 상관하지 않고, 각 항에게 “너를 이 통화로 환산해”라고 위임한 뒤 결과의 amount만 더한다. 다형성과 재귀가 맞물려, 통화 변환이라는 복잡함이 두 줄에 담긴다.

책의 첫 목표에 도달한 지금, 값을 즉시 계산하지 않고 식으로 미뤄둔 12장의 선택이 왜 옳았는지가 분명해진다. 만약 plus가 그 자리에서 더했다면, 통화가 다를 때 무슨 통화로 답할지 정할 수 없었을 것이다. 환산 시점을 미뤘기에 “무슨 통화로?”라는 질문에 reduce가 답할 수 있다.

판단 기준: 서로 다른 단위를 더할 때는 “공통 단위로 변환 후 연산”이 정석이다. 변환의 책임을 각 항에게 위임하면 항의 종류가 늘어도 덧셈 로직은 그대로다. 함정: plus 시점에 값을 확정하려는 조급함. 계산을 reduce까지 미뤄야 목표 통화를 그때 주입할 수 있다.

이 장이 남긴 할 일 목록

  • 10 (환율 2:1) — 책의 첫 목표 달성
  • fiveBucks/tenFrancs를 Expression 타입으로 — 일반화 → 16장
  • Sum.plus / Sum.times — 식의 합·곱도 식이어야
  • Pair.hashCode 개선

다음장으로 16장