[Refactoring] 조건문 단순화

말하는 감자·2022년 4월 22일
0

Refactoring

목록 보기
7/9
post-thumbnail

📌 조건문 단순화 (Simplifying Conditional Expressions)

복잡해진 조건문을 단순하게 해결하기 위한 기술


✏️ 조건문 통합 (Consolidate Conditional Expression)

같은 결과를 내는 조건문이 연속으로 있다면 하나의 조건식으로 통합

📃 예제

<변경 전>

double disabilityAmount() {
  if (seniority < 2) {
    return 0;
  }
  if (monthsDisabled > 12) {
    return 0;
  }
  if (isPartTime) {
    return 0;
  }
 }

<변경 후>

double disabilityAmount() {
  if (isNotEligibleForDisability()) {
    return 0;
  }
 }
boolean isNotEligibleForDisability() {
  if (seniority < 2 && monthsDisabled > 12 && isPartTime)
    return true;
}

✔️ 장점

  • 중복 제어 흐름 코드 제거

  • 조건문의 목적을 설명하는 이름을 가진 새 메소드에서 복잡한 표현식 분리 가능


✏️ 조건문 중복 부분 통합 (Consolidate Duplicate Conditional Fragments)

조건문의 모든 분기에서 공통된 코드를 조건문 밖으로 이동

✔️ 장점

  • 중복 코드 제거

✏️ 조건문 분해 (Decompose Conditional)

복잡한 조건문(if-then-else)을 조건부분, then부분을 메소드로 추출

이미지 속 코드가 너무 생략되어서 "이게 왜 이렇게 되는 거지?"싶을까봐 예제를 가져왔다.

📃 예제

<변경 전>

class Simple_Example{
 
   void Example(){
       if(date < SUPPER_STAR || date > SUPPER_END)
           charge = quantity * winterRate + winterServiceCharge;
       else 
           Charge = quantity * summerRate;
   }
}

<변경 후>

class Simple_Example{
   void Example(){
       if(IsNotSummer())
           charge = CalculateWinterRate();
       else 
           Charge = CalculateSummerRate());
       }
}

private bool IsNotSummer(){
  return date < SUPPER_STAR || date > SUPPER_END;
}

private bool CalculateWinterRate(){
  return quantity * winterRate + winterServiceCharge;
}

private bool CalculateSummerRate(){
  return quantity * summerRate;
}

✔️ 장점

  • 조건부 코드를 명확하게 명칭된 메소드로 추출하면 코드 유지관리가 쉬워짐

✏️ 조건문을 다형성으로 변경 (Replace Conditional with Polymorphism)

객체의 타입에 따라서 다른 행동을 하는 조건이 있다면 각 조건의 내용을 서브 클래스의 메소드로 오버라이딩하고 원본 메소드는 추상 메소드로 변경

<변경 전>

class Bird {
  // ...
  double getSpeed() {
    switch (type) {
      case EUROPEAN:
        return getBaseSpeed();
      case AFRICAN:
        return getBaseSpeed() - getLoadFactor() * numberOfCoconuts;
      case NORWEGIAN_BLUE:
        return (isNailed) ? 0 : getBaseSpeed(voltage);
    }
    throw new RuntimeException("Should be unreachable");
  }
}

<변경 후>

abstract class Bird {
  // ...
  abstract double getSpeed();
}

class European extends Bird {
  double getSpeed() {
    return getBaseSpeed();
  }
}
class African extends Bird {
  double getSpeed() {
    return getBaseSpeed() - getLoadFactor() * numberOfCoconuts;
  }
}
class NorwegianBlue extends Bird {
  double getSpeed() {
    return (isNailed) ? 0 : getBaseSpeed(voltage);
  }
}

// Somewhere in client code
speed = bird.getSpeed();

✔️ 장점

  • TDA 원칙을 준수

    TDA 원칙 (Tell, don't ask 원칙)

    "물어보지 말고 그냥 시켜라"
    객체와 객체가 협력하는 경우, 다른 객체에게 정보를 요구하지 말고 그냥 행동하도록 시키라는 의미
    정보 은닉의 중요성을 강조하는 원칙

  • 중복 코드 제거
    거의 동일한 조거문을 많이 제거하므로

  • 개방 폐쇄 원칙을 준수
    새로운 실행 조건을 추가해야 하는 경우 기존 코드를 건드리지 않고 새 하위 클래스 생성


✏️ 제어 플래그 제거 (Remove Control Flag)

상태를 기록하고 처리의 흐름을 제어하기 위한 Boolean형 변수 제거
break, continue, return 을 대신 사용

✔️ 장점

  • 코드가 더 간단해짐

✏️ 중첩 조건문을 보호 구문로 변경(Replace Nested Conditional with Guard Clauses)

실행 경로를 알기 힘든 조건문 대신 각 조건별로 보호 구문(guard clause) 사용


✏️ Null 객체 도입 (Introduce Null Object)

null 값을 반복해서 체크한다면 null 값 대신 null 객체 사용

❌ 단점

  • 조건문을 없애는 대가는 또 다른 새로운 클래스 생성
    → 구조 복잡

✏️ Assertion 도입 (Introduce Assertion)

특정 부분의 코드가 프로그램의 상태를 가정한다면 그 가정을 명확한 Assert의 형태로 변경

Assertion

프로그램의 특정 지점에 위치한 어써션은 해당 지점에서 개발자가 반드시 참(true)이어야 한다고 생각하는 사항을 표현한 논리식
어써션이 위반되는 경우(논리식이 거짓)는 프로그램에 버그나 문제가 있는 것을 암시

<변경 전>

double getExpenseLimit() {
  // Should have either expense limit or
  // a primary project.
  return (expenseLimit != NULL_EXPENSE) ?
    expenseLimit :
    primaryProject.getMemberExpenseLimit();
}

<변경 후>

double getExpenseLimit() {
  Assert.isTrue(expenseLimit != NULL_EXPENSE || primaryProject != null);

  return (expenseLimit != NULL_EXPENSE) ?
    expenseLimit:
    primaryProject.getMemberExpenseLimit();
}

✔️ 장점

  • 가정이 사실이 아니어서 코드가 잘못된 결과를 제공하는 경우 치명적인 결과와 데이터 손상이 발생하기 전에 실행 중지

❌ 단점

  • Assertion보다 예외가 더 중요한 상황 발생한다면?

📑 참고 자료

profile
나는 말하는 감자다

0개의 댓글