Clean Code

Java 리팩토링 - 플래그 인수 제거하기

1. 알고자 하는 것

회사에서 코드를 작성할 때, Flag 변수를 생각보다 많이 사용했다는 것을 이번 강의를 보고 느끼게 되었다.

단순히 private 메서드 개수를 줄이기 위해 / 공통 메서드로 묶기 위해 Flag 변수를 두어 메서드 하나에서 로직을 분기했었다.

내 딴에서는 공통로직의 재사용이라는 리팩토링 명목으로 사용한 Flag 변수였지만, 이번 강의를 통해 '단순히 라인 수를 줄이는 것이 리팩토링이 아니다' 라는 의미를 한번 더 깨닫게 된 것 같다.

리팩토링은 '짧은 코드를 만드는 것'이 목적이 아니라 '의도를 명확히 / 가독성을 좋게' 하는 것이 목적임을 늘 상기하자!

 

  • 플래그 인수 제거하기

 

 

 

2. 알게된 것

  • boolean, enum 타입과 같은 Flag 변수를 통해 함수 내부의 로직을 분기하는 경우가 많다.
  • 이 때, Flag 변수를 통해 함수 내부의 로직을 분기하게 되었을 때, 호출하는 쪽에서는 해당 메서드의 내부 로직을 보기 전까지는 함수의 의도를 파악하기 어렵다.
    public static void main(String[] args) {
        Order order = new Order(LocalDate.now(), "WA");
        Shipment shipment = new Shipment();
        // Flag 변수가 true일 때 무슨 동작을 하는지?
        LocalDate rushDeliveryDate = shipment.deliveryDate(order, true);
        // false일 때는?
        LocalDate regularDeliveryDate = shipment.deliveryDate(order, false);
    }

    // 해당 메서드까지 찾아가서 로직을 확인해야 명시적으로 파악할 수 있다
    public LocalDate deliveryDate(Order order, boolean isRush) {
        if (isRush) {
            int deliveryTime = switch (order.getDeliveryState()) {
                case "WA", "CA", "OR" -> 1;
                case "TX", "NY", "FL" -> 2;
                default -> 3;
            };
            return order.getPlacedOn().plusDays(deliveryTime);
        } else {
            int deliveryTime = switch (order.getDeliveryState()) {
                case "WA", "CA" -> 2;
                case "OR", "TX", "NY" -> 3;
                default -> 4;
            };
            return order.getPlacedOn().plusDays(deliveryTime);
        }
    }
  • 이렇게 Flag 변수를 매개변수로 넘겨주기보다는 조건문 분해하기를 활용해 함수 내부의 분기 로직을 각 메서드로 추출하고, 호출하는 쪽에서 조건에 따라 명시적인 이름을 갖는 각 메서드를 호출함으로써 각 조건에 따른 의도를 명확하게 파악할 수 있도록 한다.
    public static void main(String[] args) {
        Order order = new Order(LocalDate.now(), "WA");
        Shipment shipment = new Shipment();
        // isRush가 true일 때는 빠른 배송 날짜를 반환한다.
        // isRush가 false일 때는 일반 배송 날짜를 반환한다.
        LocalDate deliveryDate = order.getIsRush() ? 
                                 getRushDeliveryDate(order) : 
                                 getRegularDeliveryDate(order);
    }

    public static LocalDate getRegularDeliveryDate(Order order) {
        int deliveryTime = switch (order.getDeliveryState()) {
            case "WA", "CA" -> 2;
            case "OR", "TX", "NY" -> 3;
            default -> 4;
        };
        return order.getPlacedOn().plusDays(deliveryTime);
    }

    public static LocalDate getRushDeliveryDate(Order order) {
        int deliveryTime = switch (order.getDeliveryState()) {
            case "WA", "CA", "OR" -> 1;
            case "TX", "NY", "FL" -> 2;
            default -> 3;
        };
        return order.getPlacedOn().plusDays(deliveryTime);
    }

 

  • 이와 더불어, 메서드 역시 더 이상 기존 Flag 변수를 매개변수로 가지지 않으므로 매개변수의 개수도 줄일 수 있다. 

 

 

 

3. 정리

  • boolean, enum과 같은 Flag 변수를 메서드의 매개변수를 넘겨주어 메서드 내부 로직을 분기하는 경우가 많다.
  • 이 때, 호출하는 쪽에서는 Flag 변수에 따라 메서드의 내부 동작이 어떻게 바뀌는지 파악하기 어렵다.
  • 따라서 Flag 변수에 따라 분기되는 로직을 명시적인 이름을 갖는 각 메서드로 추출하고 (조건문 분해하기), 호출하는 쪽에서 분기하여 해당 메서드를 호출한다.
  • 이를 통해 호출하는 쪽에서는 각 조건에 따라 명시적인 이름을 갖는 메서드를 호출함으로써 조건에 해당하는 동작을 예측하기 쉽고, 로직을 수행하는 메서드 쪽에서도 Flag 매개변수가 하나 줄어드는 장점을 가진다.

 

 


 

Reference

강의 - 코딩으로 학습하는 리팩토링 (백기선 강사님)