Eternity's Chit-Chat

aeternum.egloos.com



유연한 설계를 위한 패턴과 원리 - 2.의도를 명확하게 1부 Supple Design

의도를 명확하게

Money 클래스의 인터페이스를 명확하게 개선할 있는 여지가 없을까? CREATION METHOD 장점 하나는 메소드의 이름을 통해 생성할 객체의 종류를 자세히 설명할 있다는 점이다. 핸드폰 과금 시스템에서는 원화와 달러화를 사용하는 비중이 높기 때문에 통화를 사용할 있는 CREATION METHOD 추가하는 것이 좋을 같다. 테스트에 사용한 valueOf() 메소드를 wons() dollars() 메소드로 교체하자.

 

MoneyTest.java
@Test

public void equals() {

  Money won200 = Money.wons(200);

  assertEquals(Money.wons(200), won200);

 

  Money dollar200 = Money.dollars(200);

  assertFalse(won200.equals(dollar200));

}

 

깔끔하다. TDD 테스트 기법이라기 보다는 프로그래밍과 설계 기법이라고 했던 사실을 기억하자. 테스트를 작성하는 동안에는 클래스의 사용자 입장에서 생각해볼 있는 기회를 가질 있다. “이건 너무 매개변수가 많군. 매개변수의 순서를 기억하기도 어렵고 실수할 확률도 높겠어. INTRODUCE PARAMETER OBJECT 리팩터링을 사용해서 매개변수의 수를 줄이면 사용이 용이하겠어.”, “ 메소드는 의도가 명확하지 않아. INTENTION-REVEALING NAME 패턴에 맞게 클라이언트의 사용 의도를 표현할 있도록 RENAME METHOD 리팩토링을 사용해야겠어.”

 

클라이언트 개발자 입장에서 생각하도록 코드를 작성하기 전에 행위에 대한 테스트를 작성하라.

-Eric Evan, Domain-Driven Design

 

이제 테스트를 통과시키기 위해 wons() dollars() 메소드를 추가하자.

 

Money.java

public static final Currency KRW = Currency.getInstance("KRW");

public static final Currency USD = Currency.getInstance("USD");

 

public static Money wons(long amount) {

  return valueOf(amount, KRW);

}

 

public static Money wons(double amount) {

  return valueOf(amount, KRW);

}

 

public static Money wons(double amount, RoundingMode rounding) {

  return valueOf(amount, KRW, rounding);

}

 

public static Money dollars(long amount) {

  return valueOf(amount, USD);

}

 

public static Money dollars(double amount) {

  return valueOf(amount, USD);

}

 

public static Money dollars(double amount, RoundingMode rounding) {

  return valueOf(amount, USD, rounding);

}

 

테스트가 통과한다. 다른 테스트 케이스에서도 valueOf() 대신 wons() dollars() 메소드를 사용하도록 수정하자. 전체 회귀 테스트를 실행해서 수정된 코드가 다른 부분에 영향을 미치지 않았는지 확인하도록 하자. 현재까지 작성된 MoneyTest 클래스 코드는 다음과 같다.

 

MoneyTest.java

public class MoneyTest {

  @Test

  public void equals() {

    Money won200 = Money.wons(200);

    assertEquals(Money.wons(200), won200);

 

    Money dollar200 = Money.dollars(200);

    assertFalse(won200.equals(dollar200));

  }

 

  @Test

  public void dollarEquals() {

    Money won1000 = Money.wons(1000.1231);

    assertEquals(Money.wons(1000), won1000);

 

    Money dollar2_50 = Money.dollars(2.5012);

    assertEquals(Money.dollars(2.50), dollar2_50);

  }

 

  @Test

  public void roundingDollarEquals() {

    Money dollar20_75 = Money.dollars(20.7576, RoundingMode.DOWN);

    assertEquals(Money.dollars(20.75), dollar20_75);

 

    Money dollar20_76 = Money.dollars(20.7512, RoundingMode.UP);

    assertEquals(Money.dollars(20.76), dollar20_76);     

  }

}

 

작성된 테스트 케이스는 클래스 사용법에 관한 문서화 용도로도 사용할 있다. 테스트 케이스에는 클래스를 사용하는 예가 설명되어 있기 때문에 클래스나 메소드의 주석에서 전달할 없는 실행 시의 동적인 측면을 쉽게 파악할 있다. 테스트는 실행 가능한 문서이기 때문에 테스트 실행을 통해 언제라도 코드와의 동기화 여부를 판단할 있다. 따라서 테스트 케이스의 실행을 지속적인 통합의 일부로 포함시켜 빌드마다 실행되도록 한다면 테스트 케이스가 항상 최신 소스 코드의 내용을 반영하고 있다고 확신할 있다.

테스트 코드를 읽어 보자. 테스트 케이스를 통해 Money 클래스의 사용 방법을 있을 것이다. 테스트 케이스를 작성할 때에는 테스트 대상 코드뿐만 아니라 테스트 케이스 자체도 읽기 쉽고 이해하기 쉽도록 작성해야 한다는 사실을 기억하자.

 

작동하는 깔끔한 코드”. 이것이 TDD로부터 얻어낸 결과물이다. 코드를 작성하기 전에 테스트 케이스를 먼저 만들고 테스트 케이스를 통과하도록 코드를 작성하여 정상적으로 수행된다는 사실을 확인했다. 그리고 리팩토링을 통해 코드를 깔끔하게 다듬었다. 결과로 의도가 명확하게 드러난 인터페이스를 가진 이해하기 쉽고 단순한 클래스를 얻을 있었다.