책이 시작하며 내건 $5 + 10CHF = $10(환율 2:1). 열다섯 장 만에 그 테스트를 쓴다. 10프랑은 5달러이니 5달러와 더하면 10달러다.
지금 Sum.reduce는 두 항의 amount를 그냥 더한다 — 통화가 같을 때만 맞는 계산이다. 5와 10을 더하면 15가 나오는데, 답은 10이다. 무엇이 빠졌나. 더하기 전에 각 항을 목표 통화로 환산해야 한다. 12장에서 세운 “식을 들고 있다가 환산 시점에 계산한다”는 구조가 바로 이 순간을 위한 것이었다.
사이클: 빨강 → 각 항을 먼저 환산
@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.amount가 augend.reduce(bank, to).amount로, addend.amount가 addend.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장