Chapter10. 조건부 로직 간소화

김신영·2023년 7월 22일
0

Refactoring

목록 보기
10/12
post-thumbnail

조건문 분해하기 (Decompose Conditional)

Decompose Conditional

image

절차

  1. 조건식과 그 조건식에 딸린 조건절 각각을 함수로 추출한다. (Extract Method)

예시

  • Before
    if (!aDate.isBefore(plan.summerStart) && !aDate.isAfter(plan.summerEnd))
      charge = quantity * plan.summerRate;
    else
      charge = quantity * plan.regularRate + plan.regularServiceCharge;
  • After
    if (summer())
      charge = summerCharge();
    else
      charge = regularCharge();

중복 조건식 통합하기 (Consolidate Conditional Expression)

Consolidate Conditional Expression

image

절차

  1. 해당 조건식들의 모두에 부수효과가 없는지 확인한다.
    • 부수효과가 있는 조건식들에는 질의 함수와 변경 함수 분리하기(Separate Query from Modifier)를 먼저 적용한다.
  2. 조건문 두 개를 선택하여 두 조건문의 조건식들을 논리 연사자로 결합한다.
  3. 테스트한다.
  4. 조건이 하나만 남을 때까지 2~3번 과정을 반복한다.
    • 하나로 합쳐진 조건식을 함수로 추출(Extract Method)할지 고려해본다.

예시

  • Before
    if (anEmployee.seniority < 2) return 0;
    if (anEmployee.monthsDisabled > 12) return 0;
    if (anEmployee.isPartTime) return 0;
  • After
    if (isNotEligibleForDisability()) return 0;
    
    function isNotEligibleForDisability() {
      return ((anEmployee.seniority < 2)
              || (anEmployee.monthsDisabled > 12)
              || (anEmployee.isPartTime));
    }

중첩 조건문을 보호 구문으로 바꾸기 (Replace Nested Conditional with Guard Clauses)

Replace Nested Conditional with Guard Clauses

image

보호 구문 (Guard Clause)

  • 조건이 참이면(비정상 상태), 함수에서 빠져나온다.

절차

  1. 교체해야 할 조건 중 가장 바깥 것을 선택하여 보호 구문으로 바꾼다.
  2. 테스트한다.
  3. 1~2번 과정을 필요한 만큼 반복한다.
  4. 모든 보호 구문이 같은 결과를 반환한다면, 보호 구문들의 조건식을 통합한다. (Consolidate Conditional Expression)

예시

  • Before
    function getPayAmount() {
      let result;
      if (isDead)
        result = deadAmount();
      else {
        if (isSeparated)
          result = separatedAmount();
        else {
          if (isRetired)
            result = retiredAmount();
          else
            result = normalPayAmount();
        }
      }
      return result;
    }
  • After
    function getPayAmount() {
      if (isDead) return deadAmount();
      if (isSeparated) return separatedAmount();
      if (isRetired) return retiredAmount();
      return normalPayAmount();
    }

⭐ 조건부 로직을 다형성으로 바꾸기 (Replace Conditional with Polymorphism)

Replace Conditional with Polymorphism

image

절차

  1. 다형적 동작을 표현하는 클래스들이 아직 없다면 만들어준다.
    • 이왕이면 적합한 인스턴스를 알아서 만들어 반환하는 Factory 함수도 함께 만든다.
  2. 호출하는 코드에서 Factory 함수를 사용하게 한다.
  3. 조건부 로직 함수를 Super Class로 옮긴다.
    • 조건부 로직이 온전한 함수로 분리되어 있지 않다면 먼저 함수로 추출한다. (Extract Method)
  4. Sub Class 중 하나를 선택한다. Sub Class에서 Super Class의 조건부 로직 메서드를 오버라이드한다.
    • 조건부 문장 중 선택된 Sub Class에 해당하는 조건절을 Sub Class 메서드로 복사한 다음 적절히 수정한다.
  5. 같은 방식으로 각 조건절에 해당 Sub Class에서 메서드로 구현한다.
  6. Super Class 메서드에는 기본 동작 부분만 남긴다.
    • 혹은 Super Class가 추상 클래스여야 한다면, 이 메서드를 추상으로 선언하거나 Sub Class에서 처리해야 함을 알리는 에러를 던진다.

예시

  • Before
    switch (bird.type) {
      case 'EuropeanSwallow':
        return "average";
      case 'AfricanSwallow':
        return (bird.numberOfCoconuts > 2) ? "tired" : "average";
      case 'NorwegianBlueParrot':
        return (bird.voltage > 100) ? "scorched" : "beautiful";
      default:
        return "unknown";
  • After
    class EuropeanSwallow {
      get plumage() {
        return "average";
      }
    class AfricanSwallow {
      get plumage() {
         return (this.numberOfCoconuts > 2) ? "tired" : "average";
      }
    class NorwegianBlueParrot {
      get plumage() {
         return (this.voltage > 100) ? "scorched" : "beautiful";
      }

⭐ 특이 케이스 추가하기 (Introduce Special Case)

Introduce Special Case

image

절차

  1. 컨테이너에 특이 케이스인지를 검사하는 속성을 추가하고, false를 반환하게 한다.
  2. 특이 케이스 객체를 만든다. 이 객체는 특이 케이스인지를 검사하는 속성만 포함하며, 이속성은 true를 반환하게 한다.
  3. 클라이언트에서 특이 케이스인지를 검사하는 코드를 함수로 추출한다.
    • 모든 클라이언트가 값을 직접 비교하는 대신 방금 추출한 함수를 사용하도록 고친다.
  4. 코드에 새로운 특이 케이스 대상을 추가한다.
    • 함수의 반환 값으로 받거나 변환 함수를 적용하면 된다.
  5. 특이 케이스를 검사하는 함수 본문을 수정하여 특이 케이스 객체의 속성을 사용하도록 한다.
  6. 테스트한다.
  7. 여러 함수를 클래스로 묶기나 여러 함수를 변환 함수로 묶기를 적용하여 특이 케이스를 처리하는 공통 동작을 새로운 요소로 옮긴다.
  8. 아직도 특이 케이스 검사 함수를 이용하는 곳이 남아 있다면 검사 함수를 인라인한다.

예시

  • Before
    if (aCustomer === "unknown") customerName = "occupant";
  • After
    class UnknownCustomer {
        get name() {return "occupant";}

Assertion 추가하기 (Introduce Assertion)

Introduce Assertion

image

절차

  1. 참이라고 가정하는 조건이 보이면, 그 조건을 명시하는 Assertion을 추가한다.

예시

  • Before
    if (this.discountRate)
      base = base - (this.discountRate * base);
  • After
    assert(this.discountRate >= 0);
    if (this.discountRate)
      base = base - (this.discountRate * base);

제어 플래그를 탈출문으로 바꾸기 (Replace Control Flag with Break)

Replace Control Flag with Break

image

절차

  1. 제어 플래그를 사용하는 코드를 함수로 추출할지 고려한다.
  2. 제어 플래그를 갱신하는 코드 각각을 적절한 제어문으로 바꾼다.
    • 제어문으로는 주로 return, break, continue
  3. 제어 플래그를 제거한다.

예시

  • Before
    for (const p of people) {
      if (!found) {
        if (p === "Don") {
          sendAlert();
          found = true;
        }
  • After
    for (const p of people) {
      if (p === "Don") {
        sendAlert();
        break;
      }
profile
Hello velog!

0개의 댓글