열여섯 장에 걸쳐 다중 통화 Money를 처음부터 만들었다. 이 장에는 새 코드가 없다. 대신 우리가 어떻게 여기까지 왔는지를 되돌아본다 — TDD가 실제로 설계를 어떻게 이끌었는가.
최종 설계 지도
classDiagram class Expression { <<interface>> +reduce(Bank, String) Money +plus(Expression) Expression +times(int) Expression } class Money { -int amount -String currency +reduce(Bank, String) Money +plus(Expression) Expression +times(int) Expression } class Sum { +Expression augend +Expression addend +reduce(Bank, String) Money +plus(Expression) Expression +times(int) Expression } class Bank { -Hashtable rates +reduce(Expression, String) Money +addRate(String, String, int) +rate(String, String) int } class Pair { -String from -String to } Expression <|.. Money Expression <|.. Sum Sum o-- Expression : augend, addend Bank ..> Expression : reduce Bank o-- Pair : rates key
시작은 int를 반환하던 Dollar.times 하나였다. 끝은 Money와 Sum이 대칭으로 구현하는 Expression 추상화다. 이 구조를 미리 설계해 그린 것이 아니라, 테스트가 하나씩 요구할 때마다 한 조각씩 자라난 결과라는 점이 핵심이다.
메트릭이 말해주는 것
숫자로 보면 리듬이 드러난다. 매 장은 대체로 테스트 하나를 추가하고, 억지로 통과시킨 뒤, 중복을 제거하는 한 사이클이었다. 테스트 코드와 제품 코드의 양은 엇비슷하게 자랐다 — TDD에서는 이게 정상이다. 테스트가 제품 코드의 절반도 안 된다면 충분히 촘촘하지 않다는 신호이고, 몇 배로 많다면 테스트끼리 중복됐을 가능성이 크다.
주목할 것은 되돌린 걸음이 거의 없다는 사실이다. 매 걸음 끝이 초록이었기에, 방향이 틀렸다면 바로 다음 걸음에서 알아챘다. 디버거를 붙들고 몇 시간을 헤매는 일이 없었던 이유는 실패의 범위가 언제나 “방금 바꾼 몇 줄”로 좁았기 때문이다.
걸음의 크기를 어떻게 정했나
Money 예제는 세 가지 통과 전략을 오갔다. 가짜 구현(상수를 박고 시작 — 1장의 amount = 10, 12장의 reduce가 Money.dollar(10)), 삼각측량(예제 둘로 일반화를 강제), 명백한 구현(답이 뻔하면 곧장 작성 — 16장의 times). 어느 하나가 옳은 게 아니라, 확신의 크기에 따라 걸음의 보폭을 바꾼 것이다.
확신이 서면 성큼 걷고, 불안하면 잘게 쪼갠다. 11장에서 서브클래스를 지울 때 두 팩터리를 한 번에 바꾸지 않고 하나씩 초록을 확인한 것이 후자다. TDD의 기술은 테스트를 쓰는 것 자체가 아니라, 지금 내 확신에 맞는 보폭을 고르는 감각이다.
할 일 목록이 설계를 대신했다
1장부터 17장까지, 설계 문서는 없었다. 대신 할 일 목록이 그 자리를 채웠다. 사이클 도중 발견한 문제(amount 노출, Pair.hashCode, 서브클래스 중복)는 그 자리에서 고치지 않고 목록에 적어 미뤘다. 덕분에 지금 걸음과 다음 걸음이 섞이지 않았다. 목록이 비어갈수록 설계가 드러났다.
남은 부채도 정직하게 남긴다. Pair.hashCode는 여전히 return 0이다. 성능이 문제가 되는 날 프로파일러가 그곳을 가리킬 것이고, 그때 고치면 된다. 완벽을 미루는 것은 게으름이 아니라 우선순위다.
판단 기준: 회고는 “무엇을 만들었나”가 아니라 “어떤 리듬으로 만들었나”를 본다. 되돌린 걸음의 수, 초록에서 초록까지의 간격, 미뤄둔 부채의 목록 — 이것이 다음 프로젝트의 보폭을 정하는 자료다. 함정: 예제가 끝났다고 목록의 남은 항목을 지우고 잊는 것. 부채는 갚지 않아도 기록은 남겨야 다음 사람이 안다.
1부가 남긴 것
10— 책의 첫 목표 달성Expression 추상화 완성- Pair.hashCode 개선 (의도적으로 남긴 부채)
- 다음: 테스트 프레임워크 자체를 TDD로 — 부트스트래핑
다음장으로 18장