스터디 4주차(22.10.28 ~ 22.11.05)
15장. 서로 다른 통화 더하기
할일 목록 :
- $5 + 10CHF = $10(환율이 2 : 1일 경우)
- $5 + $5 = $10
- $5 + $5에서 Money 반환하기
- Bank.reduce(Money)
- Money에 대한 통화 변환을 수행하는 Reduce
- Reduce(Bank, String)
- $5 + 10CHF에 대한 테스트 추가
public void testMixedAddition(){
Expression fiveBusks = Money.dollar(5);
Expression tenFranc = Money.dollar(10);
Bank bank = new Bank();
bank.addRate("CHF", "USD", 2);
Money result = bank.reduce(fiveBucks.plus(tenFranc), "USD");
assertEquasl(Money.dollar(10), result);
}
앞으로 나아가는 두 가지 선택 경로
- 좁은 범위의 한정적인 테스트를 빠르게 작성한 후에 일반화하는 방법
- 모든 실수를 컴파일러가 잡아줄 거라 믿고 진행하는 방법
public void testMixedAddition(){
Money fiveBucks = Money.dollar(5);
Money tenFrancs = Money.franc(10);
Bank bank = new Bank();
bank.addRate("CHF", "USD", 2);
Money result = bank.reduce(fiveBucks.plus(tenFrancs), "USD");
assertEquals(Money.dollar(10), result);
}
테스가 실패한다.
- 나온 값 : 15USD
- 예측 값 : 10USD
Sum.reduce()
가 인자를 축약하지 않는 것 같아 보인다.
//Sum
public Money reduce(Bank bank, String to){
int amount = augend.amount + addend.amount;
return new Moeny(amount, to);
}
- 두 인자를 모도 축약하면 테스트 통과
//Sum
public Money reduce(Bank bank, String to){
int amount = augend.reduce(bank, to).amount + addend.reduce(bank, to).amount;
return new Moeny(amount, to);
}
결과는 통과
Expression이어야 하는 Money들을 조금씩 쪼아서 없앨 수 있다.
//Sum
Expression augend;
Expression addend;
Sum 생성자의 인자 역시 Expresion일 수 있다.
//Sum
Sum(Expression augend, Expression addend){
this.augend = augend;
this.addend = addend;
}
Sum이 컴포지트(Composite)패턴을 상기 시킨다.
하지만 일반화 시킬만큼 강하지는 않다.
만약 Sum이 둘 이상의 인자를 갖는 순간이 온다면 그땐 바꾸게 될 것이다.
Money 에서 plus()의 인자가 Expression으로 취급될 수 있다.
//Money
Expression plus(Expression addend){
return new Sum(this, addend);
}
times()의 반환 값도 Expression일 수 있다.
//Money
Expression times(int multiplier){
return new Money(amount * multiplier, currency);
}
위 코드는 Expression이 plus()와 times() 오퍼레이션을 포함해야 함을 제안하고 있다.
plus()
의 인자도 바꿀 수 있다.Money
→Expression
으로 변경
public void testMixedAddition(){
Money fiveBucks = Money.dollar(5);
Expression tenFrancs = Money.franc(10);
Bank bank = new Bank();
bank.addRate("CHF", "USD", 2);
Money result = bank.reduce(fiveBucks.plus(tenFrancs), "USD");
assertEquals(Money.dollar(10), result);
}
fiveBucks
를Expression
으로 바꾸고 나면 몇 군데를 같이 수정해야 한다.
public void testMixedAddition(){
Expression fiveBucks = Money.dollar(5);
Expression tenFrancs = Money.franc(10);
Bank bank = new Bank();
bank.addRate("CHF", "USD", 2);
Money result = bank.reduce(fiveBucks.plus(tenFrancs), "USD");
assertEquals(Money.dollar(10), result);
}
- 컴파일러가 Expression에 plus()가 정의되지 않았다고 알려준다.
//Expression
Expression plus(Expression addend);
- Money와 Sum을 추가해야한다.
//Money
public Expression plus(Expression addend){
return new Sum(this, addend);
}
//Sum : Sum의 구현을 스텁 구현으로 바꾸고, 할일 목록 추가하자.
public Expression plus(Expression addend){
return null;
}
할일 목록 : Sum.plus, Expression.times 추가
- $5 + 10CHF = $10(환율이 2 : 1일 경우)
- $5 + $5 = $10
- $5 + $5에서 Money 반환하기
- Bank.reduce(Money)
- Money에 대한 통화 변환을 수행하는 Reduce
- Reduce(Bank, String)
- Sum.plus
- Expression.times
Money를 Expression으로 일반화하는 작업을 마무리할 준비가 되었다.
15장 정리
- 원하는 테스트를 작성하고, 한 단계에 달성할 수 있도록 뒤로 물렀다.
- 조금 더 추상적인 선언을 통해 가지에서뿌리로 일반화했다.
- 변경 후(Expresion fiveBucks), 그 영향을 받은 다른 부분들을 변경하기 위해 컴파일러의 지시를 따랐다.(Expression에 plus()를 추가하기 등..)
16장. 드디어 추상화
할일 목록
- $5 + 10CHF = $10(환율이 2 : 1일 경우)
- $5 + $5 = $10
- $5 + $5에서 Money 반환하기
- Bank.reduce(Money)
- Money에 대한 통화 변환을 수행하는 Reduce
- Reduce(Bank, String)
- Sum.plus
- Expression.times
Expression.plus
를 끝마치려면 Sum.plus()
를 구현해야 한다.
그리고
Expresion.times()
를 구현하면 전체 예제가 끝난다.
- Sum.plus() 테스트
public void testSumPlusMoney(){
Expression fiveBucks = Money.dollar(5);
Expression tenFrancs = Money.franc(10);
Bank bank = new Bank();
bank.addRate("CHF", "USD", 2);
Expression sum = new Sum(fiveBucks, tenFrancs.plus(fiveBucks);
Money result = bank.reduce(sum, "USD");
assertEquals(Money.dollar(15), result);
}
//Sum
public Expression plus(Expression addend){
return new Sum(this, addend);
}
할일 목록 : Sum.plus 완료
- $5 + 10CHF = $10(환율이 2 : 1일 경우)
- $5 + $5 = $10
- $5 + $5에서 Money 반환하기
- Bank.reduce(Money)
- Money에 대한 통화 변환을 수행하는 Reduce
- Reduce(Bank, String)
- Sum.plus
- Expression.times
TDD로 구현할 땐 테스트 코드의 줄 수와 모델 코드의 줄 수가 거의 비슷한 상태로 끝난다.
TDD가 경제적이기 위해서는 매일 만들어 내는 코드의 줄 수가 두 배가 되거나 동일한 기능을 구현하되 절반의 줄 수 로 해내야 할 것이다.
TDD가 자신의 방법에 비해 어떻게 다른지 직접 측정해 보아야 할 것이다. 이때 디버깅, 통합 작업, 다른 사람에게 설명하는 데 걸리는 시간 등의 요소를 반드시 포함해야 한다는 것을 기억하라고 한다.
Expression.times()
일단 Sum.times()
가 작동하게 만들 수 있만 있다면 Expression.times()
를 선언하는 일이야 어렵지 않을 것이다.
public void testSumTimes() {
Expression fiveBucks = Money.dollar(5);
Expression tensFrancs = Money.franc(10);
Bank bank = new Bank();
bank.addRate("CHF", "USD", 2);
Expression sum = new Sum(fiveBucks, tenFrancs).times(2);
Money result = bank.reduce(sum, "USD");
assertEquals(Money.dollar(20), result);
}
//Sum
Expression times(int multiplier){
return new Sum(augend.times(multiplier), addend.times(multiplier));
}
15장에서 피가산수(augend)와 가수(addend)를 Expression으로 추상화했기 때문에 코드가 컴파일되게 만들려면 Expression에 times()를 선언해야 한다.
//Expression
Expression times(int multiplier);
Money.times()
와 Sum.times()
의 가시성을 높여줘야 한다.
//Sum
public Expression times(int multiplier){
return new Sum(augend.times(multiplier), addend.times(multiplier);
}
//Money
public Expression times(int multiplier) {
return new Money(amount * multiplier, currency);
}
할일 목록 : Expression.times 삭제
- $5 + 10CHF = $10(환율이 2 : 1일 경우)
- $5 + $5 = $10
- $5 + $5에서 Money 반환하기
- Bank.reduce(Money)
- Money에 대한 통화 변환을 수행하는 Reduce
- Reduce(Bank, String)
- Sum.plus
- Expression.times
테스트 통과
이제 마지막으로 $5 + $5에서 Money반환하기를 하면 끝이난다.
public void testPlusSameCurrencyReturnMoney(){
Expression sum = Money.dollar(1).plus(Money.dollar(1));
assertTrue(sum instanceof Money);
}
위 테스트트 외부에서 드러나는 객체의 행위에 대한 것이 아니라 구현 중심이기 때문에 지저분하다.
//Money
public Expression plus(Expression addend){
return new Sum(this, addend);
}
할일 목록 : $5 + $5에서 Money 반환하기 - 삭제
- $5 + 10CHF = $10(환율이 2 : 1일 경우)
- $5 + $5 = $10
- $5 + $5에서 Money 반환하기
- Bank.reduce(Money)
- Money에 대한 통화 변환을 수행하는 Reduce
- Reduce(Bank, String)
- Sum.plus
- Expression.times
16장 정리
- 나중에 코드를 읽는 다른 사람들을 생각하여 테스트를 작성함
- TDD와 현재 개발 스타일을 비교할 수 있는 실험 방법을 제시했다.