조건문 간결화
Written by niee on
- 조건문을 간호화 하는 리팩토링 종류
- 조건문을 여러 개로 나누는 조건문 쪼개기
- 여러 조건 검사가 있는데 결과가 모두 같을 땐 중복 조건식 통합
- 조건문 안의 중복 코드를 제거하려면 조건문의 공통 실행 코드 빼내기
- 특수한 case 조건문을 명확히 하려면 여러 겹의 조건문을 감시 절로 전환
- 복잡한 제어 플래그를 제거하려면 제어 플래그 제거
- ** 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문으로 바꾸자
- 방법
- 논리문을 빠져나오게 하는 제어 플래그 값을 찾자.
- 그 제어 플래그 값을 대입하는 코드를 break 문이나 continue문으로 바꾸자
- 하나씩 바꿀 때마다 컴파일과 테스트를 실시하자.
- break문이나 continue문이 없는 언어에서의 방법
- 로직을 메서드로 빼내자
- 논리문을 빠져나오게 하는 제어플래그 값을 찾자
- 빠져나오게 하는 값을 return문으로 바꾸자
- 하나씩 바꿀 때마다 컴파일과 테스트를 실시하자
- 참조 RemoveControlFlag.class
### 4.여러겹의 조건문을 감시 절로 전환 - 메서드에 조건문이 있어서 정상적인 실행 결로를 파악하기 힘들 땐 모든 특수한 경우에 감시절을 사용하자 - 감시절? : 조건문이 특이한 조건이라면 그 조건을 검사해서 조건이 true일 경우 반환하는 이런식의 검사를 감시절이라 한다.
- 방법
- 조건 절마다 감시 절을 찾자
- 그 감시 절은 값을 반환하거나 예외를 통지한다.
- 각 조건 절을 감시 절로 바꿀 때마다 컴파일과 테스트를 실시하자
- 모든 감시 절의 결과가 같다면 중복 조건식 통합 기법을 실시하자
- 조건 절마다 감시 절을 찾자
- 6. 조건문을 재정의로 전환
- 객체 타입에 따라 다른 기능을 실행하는 조건문이 있을땐 조건문의 각 절을 하위 클래스의 재정의 메서드 안으로 옮기고, 원본 메서드는 abstarct타입으로 수정하자.
- 방법
- 조건문이 메서드 코드의 일부라면 그 조건문을 따로 떼서 메서드 추출을 적용하자
- 필요하다면 메서드 이동을 적용해서 조건문을 최상위 클래스로 옮기자.
- 하위 클래스 중 하나를 택해서 그 안에 조건문 메서드를 재정의하는 메서드를 작성하자. 조건문의 해당 절에 있는 코드를 그 하위
클래스 메서드로 옮기고 적절히 수정하자.
-
이를 위해 상위클래스의 일부 private메서드를 protected로 수정해야 할 수도 있다.
-
- 컴파일과 테스트를 실시하자.
- 메서드로 복사해 넣은 조건문 안의 절은 삭제하자.
- 컴파일과 테스트를 실시하자.
- 조건문의 나머지 절의 코드도 마찬가지로 하위클래스 메서드 안으로 옮기자.
- 상위클래스 메서드를 abstract타입으로 만들자
- 7. Null 검사를 널 객체에 위임
- null 값을 검사하는 코드가 계속 나올땐 null 값을 널 객체로 만들자
if(customer == null) {
plan = BillingPlan.basic();
}else {
plan = customer.getPlan();
}
- 방법
- 원본 클래스 안에 널 객체 역할을 할 하위클래스를 작성하자. 원본 클래스와 널 클래스 안에 isNull 메서드를 작성하자. 원본 클래스의 isNull 메서드는 false를 반환해야 하고, 널 클래스의 isNull메서드는 true를 반환해야 한다. -> isNull메서드를 넣을 Nullable 인터페이스를 작성하면 좋을 때도 있다. -> 아니면 검사 인터페이스로 null 여부를 검사하는 방법도 있다
- 컴파일하자.
- 원본 객체에 요청하면 null을 반환할 수 있는 부분을 전부 찾자. 그래서 그 부분을 널 객체로 바꾸자.
- 원본 타입의 변수를 null과 비교하는 코드를 전부 찾아서 isNull 호출로 바꾸자 -> 원본 클래스와 클라이언트를 한 번에 하나씩 수정하고 그때마다 컴파일과 테스트를 실시하면 된다. -> null이 나오지 말아야 할 곳에 null을 검사하는 어선셜을 몇개 넣으면 좋다. 5.컴파일과 테스트를 실시하자
- 클라이언트가 null이 아니면 한 메서드를 호출하고 null이면 다른 메서드를 호출하는 case문을 전부 찾자. 7.각 case문마다 널 클래스 안의 해당 메서드를 다른 기능의 메서드로 재정의하자. 8.재정의 메서드를 사용하는 부분에서 조건문을 삭제하고 컴파일과 테스트를 실시하자.
- 8. 어선셜 넣기
- 일부 코드가 프로그램의 어떤 상태를 전제할 땐 어선셜을 넣어서 그 전제를 확실하게 코드로 작성하자.
- 방법
- 어떤 조건이 참으로 전제된다면 어선셜을 넣어 그 전제를 드러내자. -> 어선셜 기능을 사용할 수 있는 Assert클래스를 작성하자.
Comments