값 객체가 되었으니 이제 값으로 비교할 수 있어야 한다. new Dollar(5).times(2)new Dollar(10)은 서로 다른 인스턴스지만 같은 값 $10이다. 자바의 기본 equals()는 참조를 비교하므로 이 둘을 다르다고 본다. 값 객체라면 값이 같을 때 같다고 답해야 한다.

삼각측량 — 예제 두 개로 일반화를 강제한다

작은 테스트 하나만으로는 return true라고 거짓말할 수 있다. 벡은 이럴 때 삼각측량(Triangulation)을 쓴다. 서로 다른 두 예제를 놓고, 둘 다 통과시키려면 어쩔 수 없이 진짜 로직을 쓸 수밖에 없게 만든다. 하나의 예제로는 방향만, 두 개의 예제로는 지점을 정한다.

Refactoring Step 빨강 — 동치성 테스트를 추가한다
1/3
@Testpublic void testEquality() {    assertTrue(new Dollar(5).equals(new Dollar(5)));}class Dollar {    int amount;    Dollar(int amount) {        this.amount = amount;    }    Dollar times(int multiplier) {        return new Dollar(amount * multiplier);    }    // equals()가 없어 Object의 참조 비교가 쓰인다 → 실패}

assertTrue 하나만 있을 때는 return true로도 초록이 된다. 하지만 assertFalse(...equals(new Dollar(6)))를 더하는 순간 그 거짓말이 무너지고, amount를 실제로 비교하는 수밖에 없어진다. 두 예제가 정답의 위치를 삼각으로 좁혀 준다.

판단 기준: 구현이 명백하면 곧장 쓰고, 어떻게 일반화할지 감이 안 오면 삼각측량으로 예제를 하나 더 놓는다. 함정: 삼각측량은 강력하지만 느리다. 일반 해가 눈에 보이는데도 예제를 두 개씩 까는 건 낭비다 — 확신이 없을 때만 쓰는 도구다. 또 하나, 지금 equals는 null과 다른 타입을 무시한다. 완벽하지 않지만 목록에 적고 넘어간다.

이 장이 남긴 할 일 목록

  • equals()
  • amount를 public으로 노출 중 → equals가 생겼으니 개선할 수 있다 → 4장
  • equals(null), equals(다른 객체) 처리
  • hashCode()
  • 10 (환율 2:1)

다음장으로 4장