6장에서 equals를 Money로 끌어올린 대가로 버그가 생겼다. Dollar(5)와 Franc(5)가 amount만 보고 같다고 나온다. 사과와 오렌지를 같다고 우기는 셈이다. 값 객체의 동치성은 값뿐 아니라 타입까지 같아야 한다. 먼저 이 버그를 드러내는 테스트를 쓰고, 그다음 고친다.
버그를 테스트로 고정한 뒤 고친다
머릿속으로만 “이건 버그야”라고 생각하는 대신, 실패하는 테스트로 못 박는다. assertFalse(new Franc(5).equals(new Dollar(5)))가 지금은 빨강이다. 이 테스트가 초록이 되게 만들면 버그는 사라지고, 다시는 조용히 돌아오지 못한다.
@Testpublic void testEquality() { assertTrue(new Dollar(5).equals(new Dollar(5))); assertFalse(new Dollar(5).equals(new Dollar(6))); assertTrue(new Franc(5).equals(new Franc(5))); assertFalse(new Franc(5).equals(new Dollar(5))); // 지금은 실패 — 5 == 5라 true}class Money { protected int amount; public boolean equals(Object object) { Money money = (Money) object; return amount == money.amount; // 타입을 무시한다 }}
@Testpublic void testEquality() { assertTrue(new Dollar(5).equals(new Dollar(5))); assertFalse(new Dollar(5).equals(new Dollar(6))); assertTrue(new Franc(5).equals(new Franc(5))); assertFalse(new Franc(5).equals(new Dollar(5))); // 지금은 실패 — 5 == 5라 true}class Money { protected int amount; public boolean equals(Object object) { Money money = (Money) object; return amount == money.amount; // 타입을 무시한다 }}
@Testpublic void testEquality() { assertTrue(new Dollar(5).equals(new Dollar(5))); assertFalse(new Dollar(5).equals(new Dollar(6))); assertTrue(new Franc(5).equals(new Franc(5))); assertFalse(new Franc(5).equals(new Dollar(5))); // 이제 통과}class Money { protected int amount; public boolean equals(Object object) { Money money = (Money) object; return amount == money.amount && getClass().equals(money.getClass()); // 실제 클래스가 같아야 같다 }}
getClass().equals(money.getClass())는 두 객체의 실제 런타임 클래스를 비교한다. Franc(5)와 Dollar(5)는 amount가 같아도 클래스가 달라 이제 다르다고 판정된다. 사과와 오렌지가 분리됐다.
판단 기준: 값 객체의 동치성은 “값이 같고 타입도 같을 때”다. getClass 비교는 이 정의를 코드로 옮긴 것이다. 함정: getClass 비교는 상속 계층에서 서브클래스를 다르게 취급하는 문제가 있어 도메인 클래스에는 보통 부적절하다. 다만 벡은 여기서 통화를 클래스로 표현했기 때문에 자연스럽다. 사실 통화를 클래스가 아니라 데이터(문자열)로 표현하면 이 비교 자체가 필요 없어진다 — 그 방향이 9장의 복선이다.
이 장이 남긴 할 일 목록
$5 == 5CHF 인가?- Dollar/Franc을 굳이 클래스로 나눌 필요가 있나 → 통화를 데이터로
- times 중복 — Dollar와 Franc에 거의 같은 times가 있다 → 8장
equals(null),equals(다른 객체)처리- 10 (환율 2:1)
다음장으로 8장