조건문 간결화

niee
Written by niee on

  • 조건문을 간호화 하는 리팩토링 종류
    1. 조건문을 여러 개로 나누는 조건문 쪼개기
    2. 여러 조건 검사가 있는데 결과가 모두 같을 땐 중복 조건식 통합
    3. 조건문 안의 중복 코드를 제거하려면 조건문의 공통 실행 코드 빼내기
    4. 특수한 case 조건문을 명확히 하려면 여러 겹의 조건문을 감시 절로 전환
    5. 복잡한 제어 플래그를 제거하려면 제어 플래그 제거
  • ** 1. 조건문 쪼개기 **
    • 복잡한 조건문 (if-then-else)이 있을 땐 if, then, else 부분을 각각 메서드로 빼내자
  • 동기
    • 프로그램에서 가장 복잡한 부분은 주로 복잡한 조건문이다.
    • 즉 복잡한 조건문을 쪼개 조건이 눈에 잘 들어오고 갈라지는 로직의 흐름을 알아보기 쉽게~! 게다가 로직을 왜그렇게 갈라 놨는지도 확실히 알기 쉽게 하기위해
  • 방법
    • 위에 첫줄에 적은 대로
    • if 절과 별도의 메서드로 배내자
    • then 절과 else 절을 각각의 메서드로 빼내자
    • 예제 소스 참조 (DecomposeConditional.class)
    if(date.before(SUMMER_START) || date.after(SUMMER)_END))
        charge = quantity * _winterRate + _winterServiceCharge;
    else charge = quantity * _summerRate
 if(notSummer(date))
    charge = winterCharge(quantity);
 else charge = summerCharge(quantity);

private boolean notSummer(Date date){
    return date.before(SUMMER_START) || date.after(SUMMER_END)
}

private double summerCharge(int quantity){
    return quantity * _summerRate;
}
private double winterCharge(int quantity){
    return quantity * _winterRate + _winterServiceCharge;
}
  • 결론
    • 대부분의 프로그래머는 이런 상황에서 조건부를 빼내지 않는다.
    • 짧은 조건문에서도 원본 코드에 비해 notSummer(date) 를 보는것이 코드의 의도를 더욱 분명히 알 수 있다.
    • 원본 코드를 보면 그 기능을 파악해야한다.
    • 예제처럼 간단한 코드는 쉽게 파악할 수 있지만 메서드로 빼내면 더욱 쉽게 파악할 수 있다.
  • ### 2. 중복 조건식 통합### - 여러 조건 검사식의 결과가 같을 땐 하나의 조건문으로 합친 후 메서드로 빼내자.

  • 동기
    • 서로 다른 여러 개의 조건 검사식이 있는데 조건에 따른 결과가 모두 같을 때가 간혹 있다. 이럴 때는 논리연산자 AND와 OR을 사용해서 여러 조건 검사를 하나로 합쳐야한다.
    • 이유?
      • 여러 검사를 OR연산자로 연결해서 실제로 하나의 검사 수행을 표현해서 무엇을 검사하는지 더 확실히 이해
      • 메서드 추출을 적용할 수 있는 기반이 마련된다
  • 방법
    • 모든 조건문에 부작용이 없는지 검사하자
      • 하나라도 부작용이 있으면 이 리팩토링 기법을 실시할 수가 없다.
    • 여러 개의 조건문을 논리 연산자를 사용해 하나의 조건문으로 바꾸자
    • 컴파일과 테스트를 실시하자.
    • 합친 조건문에 메서드 추출을 고려하자.
  • 예제 (예제소스 ConsolidateConditional.class 참조)
  • AND, OR, NOT 연산자를 사용하면 이렇게 여러 조건문을 하나의 조건식으로 연결할 수 있다.
  • 단, 이렇게 하면 조건식이 복잡해질 수 있으니 합친 조건식에 메서드 추출을 적용해서 간단히 만드는 게 좋다.
  • 조건식 안의 루틴이 단순히 조건을 검사해서 값을 반환할 땐 다음과 같이 삼항연산자를 사용해서 그 루틴을 한줄의 return문으로 만들자 ``` if (onVacation() && lengthOfService() > 10) return 1; else return 0.5;

앞의 코드에 삼항연산자를 사용하여 다음과 같이 곧장 return 문으로 반환하면 된다.

return (onVacation() && lengthOfService() > 10) ? 1 : 0.5;



  - **3. 조건문의 공통 실행 코드 빼내기**

  - **조건문의 모든 절에 같은 실행 코드가 있을 땐 같은 부분을 조건문 밖으로 뺴자.**
  - 예제 소스 ConsolidateDuplicate.class 참조
public static void main(String[] args) {
    int total = 0;
    int price = 0;

    if(isSpecialDeal()){
        total = price * 9;
        send();
    }else {
        total = price * 9;
        send();
    }

}

public static boolean isSpecialDeal(){
    return false;
}

public static int send(){
    return 5;
}

public static void main(String[] args) {

    int total = 0;
    int price = 0;

    if(isSpecialDeal()){
        total = price * 9;
    }else {
        total = price * 9;
    }
    send();
}
public static boolean isSpecialDeal(){
    return false;
}

public static int send(){
    return 3;
}; ```
  • 동기
    • 조건문의 절마다 같은 실행 코드가 들어 있을 때 그부분을 조건문 밖으로 빼야한다.
    • 그래야 각 절이 공통적으로 실행할 기능과 서로 다르게 실행할 기능을 한눈에 알 수 있다.
  • 방법
    • 조건에 상관없이 공통적으로 실행되는 코드를 찾자.
    • 공통 코드가 조건문의 앞 절에 있을 땐 조건문 앞으로 빼자.
    • 공통 코드가 조건문의 끝 절에 있을 땐 조건문 뒤로 빼자.
    • 공통 코드가 조건문의 중간 절에 있을 땐 앞뒤의 코드와 위치를 바꿔도 되는지 판단하자. 그래서 바꿔도 된다면 조건문의 앞이나 끝 절로 뺀 후 앞의 단계처럼 조건문 앞이나 뒤로 빼자
    • 공통 코드 명령이 둘 이상일 땐 메서드로 만들자.
  • 예외 처리에도 이 방식을 적용할 수 있다. 코드가 try 구간과 모든 catch 구간안의 예외 발생 명령 뒤에 공통적으로 들어 있으면, 그 코드를 final 구간으로 옮기면 된다.

  • ### 3. 제어 플래그 제거 - 논리 연산식의 제어 플래그 역할을 하는 변수가 있을 땐 그 변수를 break 문이나 return문으로 바꾸자

  • 방법
    1. 논리문을 빠져나오게 하는 제어 플래그 값을 찾자.
    2. 그 제어 플래그 값을 대입하는 코드를 break 문이나 continue문으로 바꾸자
    3. 하나씩 바꿀 때마다 컴파일과 테스트를 실시하자.
  • break문이나 continue문이 없는 언어에서의 방법
    1. 로직을 메서드로 빼내자
    2. 논리문을 빠져나오게 하는 제어플래그 값을 찾자
    3. 빠져나오게 하는 값을 return문으로 바꾸자
    4. 하나씩 바꿀 때마다 컴파일과 테스트를 실시하자
  • 참조 RemoveControlFlag.class

### 4.여러겹의 조건문을 감시 절로 전환 - 메서드에 조건문이 있어서 정상적인 실행 결로를 파악하기 힘들 땐 모든 특수한 경우에 감시절을 사용하자 - 감시절? : 조건문이 특이한 조건이라면 그 조건을 검사해서 조건이 true일 경우 반환하는 이런식의 검사를 감시절이라 한다.

  • 방법
    1. 조건 절마다 감시 절을 찾자
      • 그 감시 절은 값을 반환하거나 예외를 통지한다.
    2. 각 조건 절을 감시 절로 바꿀 때마다 컴파일과 테스트를 실시하자
      • 모든 감시 절의 결과가 같다면 중복 조건식 통합 기법을 실시하자
  • 6. 조건문을 재정의로 전환
    • 객체 타입에 따라 다른 기능을 실행하는 조건문이 있을땐 조건문의 각 절을 하위 클래스의 재정의 메서드 안으로 옮기고, 원본 메서드는 abstarct타입으로 수정하자.
  • 방법
    1. 조건문이 메서드 코드의 일부라면 그 조건문을 따로 떼서 메서드 추출을 적용하자
    2. 필요하다면 메서드 이동을 적용해서 조건문을 최상위 클래스로 옮기자.
    3. 하위 클래스 중 하나를 택해서 그 안에 조건문 메서드를 재정의하는 메서드를 작성하자. 조건문의 해당 절에 있는 코드를 그 하위 클래스 메서드로 옮기고 적절히 수정하자.
      • 이를 위해 상위클래스의 일부 private메서드를 protected로 수정해야 할 수도 있다.

    4. 컴파일과 테스트를 실시하자.
    5. 메서드로 복사해 넣은 조건문 안의 절은 삭제하자.
    6. 컴파일과 테스트를 실시하자.
    7. 조건문의 나머지 절의 코드도 마찬가지로 하위클래스 메서드 안으로 옮기자.
    8. 상위클래스 메서드를 abstract타입으로 만들자
  • 7. Null 검사를 널 객체에 위임
    • null 값을 검사하는 코드가 계속 나올땐 null 값을 널 객체로 만들자
 if(customer == null) {
        plan = BillingPlan.basic();
    }else {
        plan = customer.getPlan();
    }
  • 방법
    1. 원본 클래스 안에 널 객체 역할을 할 하위클래스를 작성하자. 원본 클래스와 널 클래스 안에 isNull 메서드를 작성하자. 원본 클래스의 isNull 메서드는 false를 반환해야 하고, 널 클래스의 isNull메서드는 true를 반환해야 한다. -> isNull메서드를 넣을 Nullable 인터페이스를 작성하면 좋을 때도 있다. -> 아니면 검사 인터페이스로 null 여부를 검사하는 방법도 있다
    2. 컴파일하자.
    3. 원본 객체에 요청하면 null을 반환할 수 있는 부분을 전부 찾자. 그래서 그 부분을 널 객체로 바꾸자.
    4. 원본 타입의 변수를 null과 비교하는 코드를 전부 찾아서 isNull 호출로 바꾸자 -> 원본 클래스와 클라이언트를 한 번에 하나씩 수정하고 그때마다 컴파일과 테스트를 실시하면 된다. -> null이 나오지 말아야 할 곳에 null을 검사하는 어선셜을 몇개 넣으면 좋다. 5.컴파일과 테스트를 실시하자
    5. 클라이언트가 null이 아니면 한 메서드를 호출하고 null이면 다른 메서드를 호출하는 case문을 전부 찾자. 7.각 case문마다 널 클래스 안의 해당 메서드를 다른 기능의 메서드로 재정의하자. 8.재정의 메서드를 사용하는 부분에서 조건문을 삭제하고 컴파일과 테스트를 실시하자.
  • 8. 어선셜 넣기
    • 일부 코드가 프로그램의 어떤 상태를 전제할 땐 어선셜을 넣어서 그 전제를 확실하게 코드로 작성하자.
  • 방법
    1. 어떤 조건이 참으로 전제된다면 어선셜을 넣어 그 전제를 드러내자. -> 어선셜 기능을 사용할 수 있는 Assert클래스를 작성하자.

Comments

comments powered by Disqus