이 글은 리팩터링 2판(마틴 파울러) 책을 참고하였습니다.

순서

I. 배경
II. 절차
III. 예시
IV. 기타


I.  배경

플래그 변수란 조건문에서 행동을 선택하기 위한 변수이다.
예를들면, isPremium, isTrue 등등..

이 변수를 매개변수로 넘기는게 문제다.
매개변수로 넘기면
호출하는 쪽에서 어떻게 호출해야 하는지 직관적이지 않기 때문이다.

매개변수로 플래그를 넘기지 말고 함수 자체를 특정 기능 하나만 수행하더라도
그렇게 명시적인 함수를 제공하는게 훨씬 깔끔하다.

마틴 파울러는 이 책에서 딱 6가지를 싫어하는데 그 중 하나가 플래그 인수이다.

하지만!! 플래그 변수를 둘 이상 써야하는 알고리즘일 때는
써야하는 합당한 근거가 된다.

왜냐하면 모두 함수로 구현한다고 했을 때,
그 갯수는 플래그 변수들의 가능한 조합의 갯수이기 때문이다. (mCn)

 


II. 절차

1. 매개변수로 주어질 수 있는 값 각각에 대응하는 명시적 함수들을 생성한다.

주가 되는 함수에 깔끔한 분배 조건문이 있다면 "조건문 분해하기"로 명시적 함수들을 생성.
그렇지 않다면 Wrapping  Function 형태로 만든다(?)

2. 원래 함수를 호출하는 코드들을 찾아서 수정한다.

 


III. 예시


코드 원본

#include "gtest/gtest.h"

struct Order {
  char* delivery_state;
};

int Includes(char** list, int list_length, char* item) {
  for (int i = 0; i < list_length; ++i) {
    if (list[i] == item) {
      if (!strcmp(list[i], item)) {
        return 1;
      }
    }
  }
  return 0;
}

/* is_rush 가 뭐지?? TEST코드는 진짜 심각하다 */

int DeliveryDate(Order* order, int is_rush) {
  if (is_rush) {
    int delivery_time = 0;
    char* state1[] = {"MA", "CT"};
    char* state2[] = {"NY", "NH"};
    if (Includes(state1, 2, order->delivery_state)) {
      delivery_time = 1;
    } else if (Includes(state2, 2, order->delivery_state)) {
      delivery_time = 2;
    } else {
      delivery_time = 3;
    }
    return 1 + delivery_time;
  } else {
    int delivery_time = 0;
    char* state1[] = {"MA", "CT", "NY"};
    char* state2[] = {"ME", "NH"};
    if (Includes(state1, 3, order->delivery_state)) {
      delivery_time = 2;
    } else if (Includes(state2, 2, order->delivery_state)) {
      delivery_time = 3;
    } else {
      delivery_time = 4;
    }
    return 2 + delivery_time;
  }
}

TEST(RemoveFlagArgumentRushTrue, DeliveryDateFound) {
  Order order = {"NY"};
  ASSERT_EQ(3, DeliveryDate(&order, 1));
}

TEST(RemoveFlagArgumentRushTrue, DeliveryDateNotFound) {
  Order order = {"KR"};
  ASSERT_EQ(4, DeliveryDate(&order, 1));
}

TEST(RemoveFlagArgumentRushFalse, DeliveryDateFound) {
  Order order = {"NY"};
  ASSERT_EQ(4, DeliveryDate(&order, 0));
}

TEST(RemoveFlagArgumentRushFalse, DeliveryDateNotFound) {
  Order order = {"KR"};
  ASSERT_EQ(6, DeliveryDate(&order, 0));
}

 


1. is_rush 기준으로 "조건문 분해하기"

/* Order, Includes 및
   TEST 구현 생략 */
#include "gtest/gtest.h"

struct Order;
int Includes(char** list, int list_length, char* item);

/* 1. is_rush인 경우 함수 추출 */
int rushDeliveryDate(Order* order) {
  int delivery_time = 0;
  char* state1[] = {"MA", "CT"};
  char* state2[] = {"NY", "NH"};
  if (Includes(state1, 2, order->delivery_state)) {
    delivery_time = 1;
  } else if (Includes(state2, 2, order->delivery_state)) {
    delivery_time = 2;
  } else {
    delivery_time = 3;
  }
  return 1 + delivery_time;
}

/* 1. else인 경우 함수 추출 */
int regularDeliveryDate(Order* order) {
    int delivery_time = 0;
    char* state1[] = {"MA", "CT", "NY"};
    char* state2[] = {"ME", "NH"};
    if (Includes(state1, 3, order->delivery_state)) {
      delivery_time = 2;
    } else if (Includes(state2, 2, order->delivery_state)) {
      delivery_time = 3;
    } else {
      delivery_time = 4;
    }
    return 2 + delivery_time;
}

/* 1. 함수 추출된 모습 */
int DeliveryDate(Order* order, int is_rush) {
  if (is_rush)  rushDeliveryDate(order);
  else regularDeliveryDate(order);
}

 


2. 기존 함수 "DeliveryDate" 삭제하고 동작코드 수정하기

/* 함수호출 뒤에 인자로 0, 1 넘길필요 없이
   필요한 함수를 직관적으로 호출하자 */

TEST(RemoveFlagArgumentRushTrue, DeliveryDateFound) {
  Order order = {"NY"};
  ASSERT_EQ(3, rushDeliveryDate(&order));
}

TEST(RemoveFlagArgumentRushTrue, DeliveryDateNotFound) {
  Order order = {"KR"};
  ASSERT_EQ(4, rushDeliveryDate(&order, 1));
}

TEST(RemoveFlagArgumentRushFalse, DeliveryDateFound) {
  Order order = {"NY"};
  ASSERT_EQ(4, regularDeliveryDate(&order));
}

TEST(RemoveFlagArgumentRushFalse, DeliveryDateNotFound) {
  Order order = {"KR"};
  ASSERT_EQ(6, regularDeliveryDate(&order));
}

 


IV. 기타

하지만 완전히 없앨 수 없는 경우 Wrapping 하는 방식이 필요하다.
-> 기존 함수를 그대로 사용

아래와 같이 감쌀 수 있다.

실제 로직함수 "deliveryDate"는 그대로 두고
외부 동작함수에서 Wrapper인 rush~~, regular~~을 호출한다.

대신 감싸진 deliveryDate는 private로 접근을 제한하거나
Helper처럼 이름으로 명시해준다.

int rushDeliveryDate(Order* order) {
  return deliveryDate(order, true); 
}

int regularDeliveryDate(Order* order) {
  return deliveryDate(order, false); 
}

 

+ Recent posts