Eternity's Chit-Chat

aeternum.egloos.com



유연한 설계를 위한 패턴과 원리 - 3.리팩토링을 통한 진보 2부 Supple Design

잠시 동안 환율이란 시간과 무관하게 변경 가능한 기준 통화와 대상 통화 간의 비율이라고 생각하자. 이러한 의미를 가장 간단하고 명확하게 표현할 있는 방법은 Set 대신 기준 통화와 대상 통화 쌍을 속성으로 가지는 객체를 키로 하고, 통화 간의 비율을 double 값으로 저장하는 Map 사용하는 것이다.

우선
shouldExistUniqueExchangeRate() 테스트에 @Ignore 어노테이션을 붙여 테스트가 실행되지 않도록 하자. 기준 통화와 대상 통화를 속성으로 가지는 CurrencyPair 클래스를 추가하자.

 

CurrencyPair.java

public class CurrencyPair {

  private Currency from;

  private Currency to;

 

  public static CurrencyPair with(Currency from, Currency to) {

    return new CurrencyPair(from, to);

  }

  CurrencyPair(Currency from, Currency to) {

    this.from = from;

    this.to = to;

  }

}

 

환율이 동일하다는 것은 기준 통화와 대상 통화의 값이 동일하다는 것을 의미한다.

 

CurrencyPair.java

public boolean equals(Object object) {

  if (this == object) {

    return true;

  }

 

  if (!(object instanceof CurrencyPair)) {

    return false;

  }

 

  CurrencyPair other = (CurrencyPair)object;

  return (from.equals(other.from) && to.equals(other.to));

}

public
int hashCode() {

int result = 17;

  result = 37 * result + from.hashCode();

  result = 37 * result + to.hashCode();

 

  return result;

}

 

ChangeBooth 환율의 의미를 명확하게 표현하기 위해 CurrencyPair 키로 하고 비율을 나타내는 double 값을 저장하는 Map 타입의 rates 속성을 포함한다.

 

ChangeBooth.java

private Map<CurrencyPair, Double> rates = new HashMap<CurrencyPair, Double>();

 

public void addExchangeRate(Currency from, Currency to, double rate) {

  rates.put(CurrencyPair.with(from, to), rate);

}

 

getExchangeRate() Map에서 제공하는 메소드를 사용하여 원하는 환율 값을 찾아 반환할 있다.

 

ChangeBooth.java

double getExchangeRate(Currency from, Currency to) 
  
throws ExchangeRateNotFoundException {

  if (rates.containsKey(CurrencyPair.with(from, to))) {

    return rates.get(CurrencyPair.with(from, to));

  }

 

  throw new ExchangeRateNotFoundException();

}

 

shouldExistUniqueExchangeRate() 붙였던 @Ignore 어노테이션을 제거하고 단위 테스트를 실행하자. 녹색 막대다. 외부 인터페이스의 수정 없이 ExchangeRate 제거함으로써 기준 통화와 대상 통화에 대해 오직 하나의 환율만 존재한다는 사실을 코드에 명확하게 표현했다.

ChangeBoothTest.java

@Test

public void shouldExistUniqueExchangeRate() {

  ChangeBooth changeBooth = new ChangeBooth();

  changeBooth.addExchangeRate(Money.USD, Money.KRW, 1027);

  changeBooth.addExchangeRate(Money.USD, Money.KRW, 1035);

  assertEquals(1, changeBooth.getExchangeRateSize());

}


여기에서 강조하고 싶은 점은 사전 설계 회의에서 얻어진 설계가 반드시 최적의 설계는 아니라는 사실이다. 필자의 경우 원하는 기능을 어떻게 구현해야 할지 한번에 감이 오지 않을 경우 아이디어를 얻기 위해 사전 설계를 한다. 그러나 그렇게 해서 얻어진 설계의 모습 그대로 최종 코드가 구현되는 경우는 거의 없다. 대부분의 경우 코드를 작성하면서 얻게 되는 피드백을 통해 최초의 설계 오류를 수정하고 테스트 케이스의 도움을 받아 단순하고 명확한 코드로 리팩토링해 나간다.

 

이것은 대부분의 사람들의 이상과는 거리가 멀지 모른다. 우리는 설계한 대로 구현하지 못하는 것을 부끄럽게 여겨야 한다고 배워왔다. 그러나 진실은 설계를 고정시켜 버리면 코드를 개선할 있는 기회를 놓쳐버리게 된다는 것이다. 사전 설계가 잘못된 방법이라는 것이 아니라 단지 사전 설계에 얽매여 대안을 찾지 않으려는 자세가 옳지 않다는 것이다.
 

따라서 필자의 결론은 다이어그램은 때로는 불필요할 있다는 것이다. 그렇다면 언제 불필요하겠는가? 다이어그램을 확인할 코드 없이 만들고, 그것을 따르려고 때이다. 아이디어를 탐색하기 위해 다이어그램을 그리는 것은 문제될 것이 없다. 하지만, 다이어그램을 만들었다고 해서 그것이 과업에 가장 최적의 설계라고 가정해서는 된다.

 

- Robert C. Martin, 소프트웨어 개발의 지혜