Refactoring#9

17
Refactoring Chapter 09 - 조건문 간결화

Transcript of Refactoring#9

RefactoringChapter 09 - 조건문 간결화

Decompose Conditional(조건문 쪼개기)

복잡한 조건문(if-then-else)이 있을 때는 if, then, else 부분을 가각 메서드로 빼내자.

if 절을 별도의 메서드로 빼내자.

then 절과 else 절을 각각의 메서드로 빼내자.

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);

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

여러 조건 검사식의 결과가 같을 때는 하나의 조건문으로 합친 후 메서드로 빼내자.

모든 조건문에 부작용이 없는지 검사하자.

하나라도 부작용이 있으면 X

여러 개의 조건문을 논리 연산자를 사용해 하나의 조건문으로 바꾸자.

합친 조건문에 메서드 추출 적용을 고려하자.

double����������� ������������������  disabilityAmount()����������� ������������������  {����������� ������������������  ����������� ������������������   if����������� ������������������  (_seniority����������� ������������������  <����������� ������������������  2)����������� ������������������  return����������� ������������������  0;����������� ������������������  ����������� ������������������   if����������� ������������������  (_monthDisabled����������� ������������������  >����������� ������������������  12)����������� ������������������  return����������� ������������������  0;����������� ������������������  ����������� ������������������   if����������� ������������������  (_isPartTime)����������� ������������������  return����������� ������������������  0;����������� ������������������  ����������� ������������������   ����������� ������������������   ����������� ������������������   ����������� ������������������   …⋯

double����������� ������������������  disabilityAmount()����������� ������������������  {����������� ������������������  ����������� ������������������   if����������� ������������������  (isNotEligableForDisability())����������� ������������������  return����������� ������������������  0;����������� ������������������  ����������� ������������������   ����������� ������������������   ����������� ������������������   ����������� ������������������   …⋯

Consolidate Duplicate Conditional Fragments(조건문의 공통 실행 코드 빼기)조건문의 모든 절에 같은 실행 코드가 있을 때는 같은 부분을 조건문 밖으로 빼자.

조건에 상관없이 공통적으로 실행되는 코드를 찾자.

공통 코드가 조건문의 앞 절에 있을 땐 조건문 앞으로 빼자.

공통 코드가 조건문의 끝 절에 있을 땐 조건문 뒤로 빼자.

공통 코드가 조건문의 중간 절에 있을 땐 앞뒤의 코드와 위치를 바꿔도 되는지 판단하자. 그래서 바꿔도 된다면 조건문의 앞이나 끝 절로 뺀 후 앞의 단계처럼 조건문의 앞이나 뒤로 빼자.

공통 코드 명령이 둘 이상일 땐 메서드로 만들자.

if����������� ������������������  (isSpecialDeal())����������� ������������������  {����������� ������������������  ����������� ������������������   total����������� ������������������  =����������� ������������������  price����������� ������������������  *����������� ������������������  0.95;����������� ������������������  ����������� ������������������   send();����������� ������������������  }����������� ������������������  else����������� ������������������  {����������� ������������������  ����������� ������������������   total����������� ������������������  =����������� ������������������  price����������� ������������������  *����������� ������������������  0.98;����������� ������������������  ����������� ������������������   send();����������� ������������������  }

if����������� ������������������  (isSpecialDeal())����������� ������������������  ����������� ������������������   total����������� ������������������  =����������� ������������������  price����������� ������������������  *����������� ������������������  0.95;����������� ������������������  else����������� ������������������  ����������� ������������������   total����������� ������������������  =����������� ������������������  price����������� ������������������  *����������� ������������������  0.98;����������� ������������������  send();����������� ������������������  

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

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

break 문이나 continue 문이 있을 때

논리문을 빠져나오게 하는 제어 플래그 값을 찾자.

그 제어 플래그 값을 대입하는 코드를 break 문이나 continue 문으로 바꾸자.

break 문이나 continue 문이 없는 언어에서

로직을 메서드로 빼내자.

논리문을 빠져나오게 하는 제어 플래그 값을 찾자.

빠져나오게 하는 값을 return 문으로 바꾸자.

void����������� ������������������  checkSecurity(String[]����������� ������������������  people)����������� ������������������  {����������� ������������������  ����������� ������������������   boolean����������� ������������������  found����������� ������������������  =����������� ������������������  false;����������� ������������������  ����������� ������������������   for����������� ������������������  (int����������� ������������������  i����������� ������������������  =����������� ������������������  0;����������� ������������������  i����������� ������������������  <����������� ������������������  people.length;����������� ������������������  i++)����������� ������������������  {����������� ������������������  ����������� ������������������   ����������� ������������������   if����������� ������������������  (!found)����������� ������������������  {����������� ������������������  ����������� ������������������   ����������� ������������������   ����������� ������������������   if����������� ������������������  (people[i].equals(“Don”))����������� ������������������  {����������� ������������������  ����������� ������������������   ����������� ������������������   ����������� ������������������   ����������� ������������������   sendAlert();����������� ������������������  ����������� ������������������   ����������� ������������������   ����������� ������������������   ����������� ������������������   found����������� ������������������  =����������� ������������������  true;����������� ������������������  ����������� ������������������   ����������� ������������������   ����������� ������������������   }����������� ������������������  ����������� ������������������   ����������� ������������������   ����������� ������������������   if����������� ������������������  (people[i].equals(“John”))����������� ������������������  {����������� ������������������  ����������� ������������������   ����������� ������������������   ����������� ������������������   ����������� ������������������   sendAlert();����������� ������������������  ����������� ������������������   ����������� ������������������   ����������� ������������������   ����������� ������������������   found����������� ������������������  =����������� ������������������  true;����������� ������������������  ����������� ������������������   ����������� ������������������   ����������� ������������������   }����������� ������������������  ����������� ������������������   ����������� ������������������   }����������� ������������������  ����������� ������������������   }����������� ������������������  }

void����������� ������������������  checkSecurity(String[]����������� ������������������  people)����������� ������������������  {����������� ������������������  ����������� ������������������   for����������� ������������������  (int����������� ������������������  i����������� ������������������  =����������� ������������������  0;����������� ������������������  i����������� ������������������  <����������� ������������������  people.length;����������� ������������������  i++)����������� ������������������  {����������� ������������������  ����������� ������������������   ����������� ������������������   if����������� ������������������  (people[i].equals(“Don”)����������� ������������������  ||����������� ������������������  people[i].equals(“John”))����������� ������������������  {����������� ������������������  ����������� ������������������   ����������� ������������������   ����������� ������������������   sendAlert();����������� ������������������  ����������� ������������������   ����������� ������������������   ����������� ������������������   break;����������� ������������������  ����������� ������������������   ����������� ������������������   }����������� ������������������  ����������� ������������������   }����������� ������������������  }

TODO: try one more step

Replace Nested Conditional with Guard Clauses(여러 겹의 조건문을 감시 절로 전환)

메서드에 조건문이 있어서 정상적인 실행 경로를 파악하기 힘들 땐 모든 특수한 경우에 감시 절을 사용하자.

조건 절마다 감시 절을 넣자.

그 감시 절은 값을 반환하거나 예외를 통지한다.

각 조건 절을 감시 절로 바꿀 때마다 컴파일과 테스트를 실시하자.

모든 감시 절의 결과가 같다면 중복 조건식 통합 기법을 실시하자.

double����������� ������������������  getPayAmount()����������� ������������������  {����������� ������������������  ����������� ������������������   double����������� ������������������  result;����������� ������������������  ����������� ������������������   if����������� ������������������  (_isDead)����������� ������������������  result����������� ������������������  =����������� ������������������  deadAmount();����������� ������������������  ����������� ������������������   else����������� ������������������  {����������� ������������������  ����������� ������������������   ����������� ������������������   if����������� ������������������  (_isSeparated)����������� ������������������  result����������� ������������������  =����������� ������������������  separatedAmount();����������� ������������������  ����������� ������������������   ����������� ������������������   else����������� ������������������  {����������� ������������������  ����������� ������������������   ����������� ������������������   ����������� ������������������   if����������� ������������������  (_isRetired)����������� ������������������  result����������� ������������������  =����������� ������������������  retiredAmount();����������� ������������������  ����������� ������������������   ����������� ������������������   ����������� ������������������   else����������� ������������������  result����������� ������������������  =����������� ������������������  normalAmount();����������� ������������������  ����������� ������������������   ����������� ������������������   }����������� ������������������  ����������� ������������������   }����������� ������������������  ����������� ������������������   return����������� ������������������  result;����������� ������������������  }

double����������� ������������������  getPayAmount()����������� ������������������  {����������� ������������������  ����������� ������������������   if����������� ������������������  (_isDead)����������� ������������������  return����������� ������������������  deadAmount();����������� ������������������  ����������� ������������������   if����������� ������������������  (_isSeparated)����������� ������������������  return����������� ������������������  separatedAmount();����������� ������������������  ����������� ������������������   if����������� ������������������  (_isRetired)����������� ������������������  return����������� ������������������  retiredAmount();����������� ������������������  ����������� ������������������   return����������� ������������������  normalAmount();����������� ������������������  }

Replace Conditional with Polymorphism(조건문을 재정의로 전환)객체 타입에 따라 다른 기능을 실행하는 조건문이 있을 때는 조건문의 각 절을 하위클래스의 재정의 메서드 안으로 옮기고, 원본 메서드는 abstract 타입으로 수정하자.

조건문이 메서드 코드의 일부라면 그 조건문을 따로 떼서 메서드 추출을 적용하자

필요하다면 메서드 이동을 적용해서 조건문을 최상위 클래스로 옮기자.

하위클래스 중 하나를 택해서 그 안에 조건문 메서드를 재정의하는 메서드를 작성하자. 조건문의 해당 절에 있는 코드를 그 하위클래스 메서드로 옮기고 적절히 수정하자.

메서드로 복사해 넣은 조건문 안의 절은 삭제하자.

조건문의 나머지 절의 코드도 마찬가지로 하위클래스 메서드 안으로 옮기자.

상위클래스 메서드를 abstract 타입으로 만들자.

class����������� ������������������  Employee…⋯����������� ������������������  {����������� ������������������  ����������� ������������������   int����������� ������������������  payAmount()����������� ������������������  {����������� ������������������  ����������� ������������������   ����������� ������������������   switch����������� ������������������  (getType())����������� ������������������  {����������� ������������������  ����������� ������������������   ����������� ������������������   ����������� ������������������   case����������� ������������������  EmployeeType.ENGINEER:����������� ������������������  ����������� ������������������   ����������� ������������������   ����������� ������������������   ����������� ������������������   return����������� ������������������  _monthlySalary;����������� ������������������  ����������� ������������������   ����������� ������������������   ����������� ������������������   case����������� ������������������  EmployeeType.SALESMAN:����������� ������������������  ����������� ������������������   ����������� ������������������   ����������� ������������������   ����������� ������������������   return����������� ������������������  _monthlySalary����������� ������������������  +����������� ������������������  _commission;����������� ������������������  ����������� ������������������   ����������� ������������������   ����������� ������������������   case����������� ������������������  EmployeeType.MANAGER:����������� ������������������  ����������� ������������������   ����������� ������������������   ����������� ������������������   ����������� ������������������   return����������� ������������������  _monthlySalary����������� ������������������  +����������� ������������������  _bonus;����������� ������������������  ����������� ������������������   ����������� ������������������   ����������� ������������������   default:����������� ������������������  ����������� ������������������   ����������� ������������������   ����������� ������������������   ����������� ������������������   throw����������� ������������������  new����������� ������������������  RuntimeException(“Exception”);����������� ������������������  ����������� ������������������   ����������� ������������������   }����������� ������������������  ����������� ������������������   }����������� ������������������  }

class����������� ������������������  Employee…⋯����������� ������������������  {����������� ������������������  ����������� ������������������   int����������� ������������������  payAmount()����������� ������������������  {����������� ������������������  ����������� ������������������   ����������� ������������������   return����������� ������������������  _type.payAmount(this);����������� ������������������  ����������� ������������������   }����������� ������������������  }����������� ������������������  class����������� ������������������  EmployeeType…⋯����������� ������������������  {����������� ������������������  ����������� ������������������   abstract����������� ������������������  int����������� ������������������  payAmount(Employee����������� ������������������  emp);����������� ������������������  }����������� ������������������  class����������� ������������������  Engineer…⋯����������� ������������������  class����������� ������������������  Salesman…⋯

Introduce Null Object(Null 검사를 널 객체에 위임)

Null 값을 검사하는 코드가 계속 나올 때는 null 값을 널 객체로 만들자.

원본 클래스 안에 널 객체 역할을 할 하위클래스를 작성하자. 원본 클래스와 널 클래스 안에 isNull 메서드를 작성하자. 원본 클래스의 isNull 메서드는 false 를 반환해야 하고, 널 클래스의 isNull 메서드는 true 를 반환해야 한다.

원본 객체에 요청하면 null 을 반환할 수 있는 부분을 전부 찾자. 그래서 그 부분을 널 객체로 바꾸자.

원본 타입의 변수를 null 과 비교하는 코드를 전부 찾아서 isNull 호출로 바꾸자.

클라이언트가 null 이 아니면 한 메서드를 호출하고 null 이면 다른 메서드를 호출하는 case 문을 전부 찾자.

각 case 문마다 널 클래스 안의 해당 메서드를 다른 기능의 메서드로 재정의하자.

재정의 메서드를 사용하는 부분에서 조건문을 삭제하고 컴파일과 테스트를 실시하자.

if����������� ������������������  (customer����������� ������������������  ==����������� ������������������  null)����������� ������������������  plan����������� ������������������  =����������� ������������������  BillingPlan.basic();����������� ������������������  else����������� ������������������  plan����������� ������������������  =����������� ������������������  customer.getPlan();

plan����������� ������������������  =����������� ������������������  customer.getPlan();

class����������� ������������������  Customer����������� ������������������  {����������� ������������������  ����������� ������������������   static����������� ������������������  Customer����������� ������������������  newNull()����������� ������������������  {����������� ������������������  ����������� ������������������   ����������� ������������������   return����������� ������������������  new����������� ������������������  NullCustomer();����������� ������������������  ����������� ������������������   }����������� ������������������  ����������� ������������������   BillingPlan����������� ������������������  getPlan()����������� ������������������  {����������� ������������������  ����������� ������������������   ����������� ������������������   …⋯����������� ������������������  ����������� ������������������   }����������� ������������������  }����������� ������������������  …⋯����������� ������������������  Customer����������� ������������������  getCustomer()����������� ������������������  {����������� ������������������  ����������� ������������������   return����������� ������������������  (_customer����������� ������������������  ==����������� ������������������  null)����������� ������������������  ?����������� ������������������  ����������� ������������������   ����������� ������������������   Customer.newNull()����������� ������������������  :����������� ������������������  ����������� ������������������   ����������� ������������������   _customer;����������� ������������������  }����������� ������������������  …⋯����������� ������������������  class����������� ������������������  NullCustomer����������� ������������������  extends����������� ������������������  Customer����������� ������������������  {����������� ������������������  …⋯����������� ������������������  

Introduce Assertion(어설션 넣기)

일부 코드가 프로그램의 어떤 상태를 전제할 때는 어설션을 넣어서 그 전제를 확실하게 코드로 작성하자.

어설션 기능을 사용할 수 있는 Assert 클래스를 작성하자.

double����������� ������������������  getExpenseLimit()����������� ������������������  {����������� ������������������  ����������� ������������������   //����������� ������������������  비용����������� ������������������  한도를����������� ������������������  두던지,����������� ������������������  주요����������� ������������������  프로젝트를����������� ������������������  정해야����������� ������������������  한다.����������� ������������������  ����������� ������������������   return����������� ������������������  (_expenseLimit����������� ������������������  !=����������� ������������������  NULL_EXPENSE)����������� ������������������  ?����������� ������������������  ����������� ������������������   ����������� ������������������   _expenseLimit����������� ������������������  :����������� ������������������  ����������� ������������������   ����������� ������������������   _primaryProject.getMemberExpenseLimit();����������� ������������������  }

double����������� ������������������  getExpenseLimit()����������� ������������������  {����������� ������������������  ����������� ������������������   Assert.isTrue(_expenseLimit����������� ������������������  !=����������� ������������������  NULL_EXPENSE����������� ������������������  ����������� ������������������   ����������� ������������������   ����������� ������������������   ����������� ������������������   ||����������� ������������������  _primaryProject����������� ������������������  !=����������� ������������������  null);����������� ������������������  ����������� ������������������   return����������� ������������������  (_expenseLimit����������� ������������������  !=����������� ������������������  NULL_EXPENSE)����������� ������������������  ?����������� ������������������  ����������� ������������������   ����������� ������������������   _expenseLimit����������� ������������������  :����������� ������������������  ����������� ������������������   ����������� ������������������   _primaryProject.getMemberExpenseLimit();����������� ������������������  }