[리팩터링 2판] 10. 조건부 로직 간소화

조건부 로직은 프로그램의 힘을 강화하는 데 크게 기여하지만, 안타깝게도 프로그램을 복잡하게 만드는 주요 원흉이다.
조건부 로직을 쉽게 바꾸는 리팩터링에 대해 알아보자.


10.0. Refactoring Tips

가변 변수를 제거하면 자다가도 떡이 생긴다는 사실을 항상 기억하자!

최종 코드를 보니 슈퍼클래스인 Bird는 없어도 괜찮아 보인다. 자바스크립트에서는 타입 계층 구조 없이도 다형성을 표현할 수 있다. 객체가 적절한 이름의 메서드만 구현하고 있다면 아무 문제없이 같은타입으로 취급하기 때문이다. (덕 타이핑)


10.1. 조건문 분해하기

배경

  • 거대한 코드 블록이 주어지면 코드를 부위별로 분해한 다음 해체된 코드 덩어리들이 각 덩어리의 의도를 살린 이름의 함수 호출로 바꿔주자.
  • 이렇게 하면 해당 조건이 무엇을 강조하고, 무엇을 분기했는지가 명확해진다.
  • 6.1 함수 추출하기를 적용하는 한 사례로 볼 수도 있다.

절차

  • 조건식과 그 조건식에 딸린 조건절 각각을 함수로 추출한다.


10.2. 조건식 통합하기

배경

  • 비교하는 조건은 다르지만 그 결과로 수행하는 동작은 똑같은 코드들이 더러 있는데, 어차피 같은 일을 할거라면 조건 검사도 하나로 통합하는 게 낫다.
  • 여러 조각으로 나뉜 조건을 하나로 통합함으로써 내가 하려는 일이 더 명확해진다.
  • 함수 추출하기까지 이어질 가능성이 높다.
  • 하나의 검사라고 생각할 수 없는 독립된 검사들이라고 판단되면 이 리팩터링을 해서는 안 된다.

절차

  • 해당 조건식들 모두에 부수효과가 없는지 확인한다.
  • 조건문 두 개를 선택하여 두 조건문의 조건식들을 논리 연산자로 결합한다.
  • 테스트한다.
  • 조건이 하나만 남을 때까지 2~3 과정을 반복한다.
  • 하나로 합쳐진 조건식을 함수로 추출할지 고려해본다.


10.3. 중첩 조건문을 보호 구무으로 바꾸기

배경

  • 조건문은 주로 두 가지 형태로 쓰인다.
  • 참인 경로와 거짓인 경로 모두 정상동작으로 이어지는 형태
    • if 와 else절 사용
  • 한쪽만 정상인 형태
    • 비정상 조건을 if에서 검사한 다음, 조건이 참이면(비정상이면) 함수에서 빠져나온다.
    • 흔히 보호 구문(guard clause) 이라고 한다.
  • 중첩 조건문을 보호 구문으로 바꾸기 리팩터링의 핵심은 의도를 부각하는 데 있다.
    • 이건 이 함수의 핵심이 아니다. 이 일이 일어나면 무언가 조치를 취한 후 함수에서 빠져나온다.

절차

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


10.4. 조건부 로직을 다형성으로 바꾸기

배경

  • 복잡한 조건부 로직은 프로그래밍에서 해석하기 가장 난해한 대상에 속한다.
  • 타입을 여러 개 만들고 각 타입이 조건부 로직을 자신만의 방식으로 처리하도록 구성할 수 있다.
    • 책, 음악, 음식은 다르게 처리해야한다. 왜? 타입이 다르니까!
  • 앞서 이야기한 방법으로 개선할 수 있는 복잡한 조건부 로직을 발견하면 다형성이 막강한 도구임을 깨닫게 된다.

절차

  • 다형적 동작을 표현하는 클래스들이 아직 없다면 만들어준다. 이왕이면 적합한 인스턴스를 알아서 만들어 반환하는 팩터리 함수도 같이 만든다.
  • 호출하는 코드에서 팩터리 함수를 사용하게 한다.
  • 조건부 로직 함수를 슈퍼클래스로 옮긴다.
  • 서브클래스 중 하나를 선택한다. 서브클래스에서 슈퍼클래스의 조건부 로직 매서드를 오버라이드한다. 조건부 문장 중 선택된 서브클래스에 해당하는 조건절을 서브클래스 매서드로 복사한 다음 적절히 수정한다.
  • 같은 방식으로 각 조건절을 해당 서브클래스에서 매서드로 구현한다.
  • 슈퍼클래스 매서드에는 기본 동작 부분만 남긴다. 혹은 슈퍼클래스가 추상 클래스여야 한다면, 이 메서드를 추상으로 선언하거나 서브클래스에서 처리해야 함을 알리는 에러를 던진다.

예시

  • Bird 클래스의 get plumage()
    • 새종류 클래스를 Bird 클래스의 서브클래스로 만들어서 처리함
  • 선박의 항해투자 등급 계산코드 (항해이력에 중국이 있을 때를 분리)
    • 변형 동작을 다형성으로 표현하기
    • 거의 똑같은 객체지만 다른 부분도 있음을 표현할 때도 상속을 쓴다.
    • 변형 동작은 슈퍼클래스와의 차이를 표현해야 하는 서브클래스에서만 신경 쓰면 된다.


10.5. 특이 케이스 추가하기

배경

  • 특수한 경우의 공통 동작을 요소 하나에 모아서 사용하는 특이 케이스 패턴\
  • 널 객체도 특이 케이스의 특수한 예라고 볼 수 있다.

절차

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


10.6. 어서션 추가하기

배경

  • 특정 조건이 참일 때만 제대로 동작하는 코드 영역이 있을 수 있다.
  • 이런 가정이 코드에 항상 명시적으로 기술되어 있지는 않아서 알고리즘을 보고 연역해서 알아내야 할 때도 있다.
    • 주석에라도 적혀있다면 좀 낫다.
    • 더 나은 방법은 어서션을 이용해서 코드 자체에 삽입해놓는 것이다.
  • 어서션은 항상 참이라고 가정하는 조건부 문장이다.
  • 소통 측면에서도 어서션은 매력적이다.

절차

  • 참이라고 가정하는 조건이 보이면 그 조건을 명시하는 어서션을 추가한다.


10.7. 제어 플래그를 탈출문으로 바꾸기

배경

  • 모든 함수의 return문은 하나여야한다고 주장하는 사람도 있지만, 나는 동의하지 않는다.
  • 함수에서 할일을 다 마쳤다면 그 사실을 return문으로 명확히 알리는 편이 낫지 않을까?

절차

  • 제어 플래그를 사용하는 코드를 함수로 추출할지 고려한다.
  • 제어 플래그를 갱신하는 코드 각각을 적절한 제어문으로 바꾼다. 하나 바꿀 때마다 테스트한다.
  • 모두 수정했다면 제어 플래그를 제거한다.

댓글남기기