Skip to content

Chapter 10 - 조건부 로직 간소화

인상깊은 문장, 코드들

10.1 조건문 분해하기

js
if (!aDate.isBefore(plan.summerStart) && !aDate.isAfter(plan.summerEnd))
    charge = quantity * plan.summerRate;
else
    charge = quantity * plan.regularRate + plan.regularServiceCharge;

->

js
if (summer())
    charge = summerCharge();
else
    charge = regularCharge();
  • 조건을 검사하고 그 결과에 따른 동작을 표현한 코드는 무슨 일이 일어나는지는 이야기해주지만 '왜' 일어나는지는 제대로 말해주지 않을 때가 많은 것이 문제다.

10.2 조건식 통합하기

  • 조건은 다르지만 그 결과로 수행하는 동작은 똑같은 코드들이 더러 있는데, 어차피 같은 일을 할 거라면 조건 검사도 하나로 통합하는 게 낫다.

10.4 조건부 로직을 다형성으로 바꾸기

  • 메서드 이름의 "And"는 이 메서드가 두 가지 독립된 일을 수행한다고 소리친다. 그러니 둘을 분리하는 게 현명할 것이다.

10.5 특이 케이스 추가하기

  • 마지막 상황은 좀 더 복잡하다. 특이 케이스가 자신만위 속성을 갖는 또 다른 객체(지불 이력)를 반환해야 하기 때문이다.
js
const weekDelinquent = isUnknown(aCustomer) ? 0 : aCustomer.history.weeksDelinquentInLastYear;

특이 케이스 객체가 다른 객체를 반환해야 한다면 그 객체 역시 특이 케이스여야 하는 것이 일반적이다. 그래서 NullPaymentHistory를 만들기로 했다.

js
// UnknownCustomer 클래스
get paymentHistory() {
    return new NullPaymentHistory();
}

10.6 어서션 추가하기

js
if (this.discountRate) {
    base = base - (this.discountRate * base);
}

->

js
assert(this.discountRate >= 0);
if (this.discountRate) {
    base = base - (this.discountRate * base);
}
  • 특정 조건이 참일 때만 제대로 동작하는 코드 영역이 있을 수 있다. ... 이런 가정이 코드에 항상 명시적으로 기술되어 있지는 않아서 알고리즘을 보고 연역해서 알아내야 할 때도 있다. 주석에라도 적혀 있다면 그나마 형편이 좀 낫다. 더 나은 방법은 어서션을 이용해서 코드 자체에 삽입해놓는 것이다.

느낀점

  • 10.2 조건식 통합하기에 부합하는 코드가 장바구니 담기 로직에서 존재한다. 수량이 맞는지, 함꼐 구매할 수 없는 상품이 같이 주문되고 있는지 등을 검사한다. 함께 구매할 수 없는 상품을 구분하는 로직을 이미 computed property로 분리했는데, 이럼에도 불구하고 조건식을 통합해야할까? 그리고 여러 if문을 만들어주는게 가독성과 여러 상황을 나타내기에 좋다고 생각했었는데, 이번 기회에 다시 생각해봐야겠다.
  • 10.3 중첩 조건문을 보호 구문으로 바꾸기에서 '보호 구문'이라는 표현을 알 수 있어 좋았다. 기존에는 그냥 일찍 return 하기 정도로 표현했던 것 같다.
  • 10.4 조건부 로직을 다형성으로 바꾸기
    • And 이름이 포함된 메서드를 분리해야한다는 것을 알았다. 기존에 몇개 And 구문을 포함한 메서드를 만들었었는데, 돌아보니 왜 그랬나 싶다. 한 예로 then chaining을 해야하는 경우에 And 를 넣어서 추상화 한 것이 있었다. 들여쓰기가 생기는게 싫어서 그랬던 것 같은데 async await로 대체하면서 동기화를 해주면 좋았을 것 같다.
    • 리팩터링 예제에서 china 관련된 로직에서 분기가 생겨서 이를 분리했는데, 이런 분기 지점을 포착하는 것이 좋았다.
  • 10.5 특이 케이스 추가하기
    • 예제에서처럼 또 다시 파생된 NullPaymentHistory를 생성해주는 것은 코드에 대한 커버가 완벽하게 되는 느낌이라 만족감이 들기도 하지만, 실제 이런 리팩터링을 PR로 생성한다면, 그리고 만약 비슷한 관점이 없는 개발자라면 긍정적으로 받아들이지 않을 수도 있을 것 같다. 너무 단순한 값을 위해 클래스까지 동원한 느낌 (마침 이런 설명이 다음 페이지에 바로 나왔다.)
    • 객체 리터럴이 조금 더 익숙한 느낌으로 와닿는다. 그런데 왜 클래스를 사용하는 쪽을 선호하는지 설명이 없었어서 아쉬웠다.
  • 10.6 어서션 추가하기 실제로 숫자 입력 컴포넌트에서 예외처리를 했던 것 같은데, 로직상에서 if (value < 0) return 0; 이런식으로 처리했던 것 같다. 그럼에도 불구하고 어서션을 추가하는 것이 좋은 것일까? 아니면 음수도 입력받을 수 있는 경우 + 사용자로부터 입력받은 value를 사용하는 쪽에서 양수만 가능한 경우 assert를 해줘야할까? 이미 그 전에 음수를 걸러내는 로직이 있는데 assert를 해줘야 하나? 주석으로 처리하는 것과 차이가 있나? 고민이 든다. 뒷 부분에 '프로그래머가 일으킬만한 오류에만 어서션을 사용한다'고 되어있다. 그럼 테스트코드로 잡는 것이 맞지 않나?