리팩토링 24. 단계 쪼개기

    들어가기 전

    이 글은 인프런 백기선님의 강의를 복습하며 작성한 글입니다.


    리팩토링 24. 단계 쪼개기 (Split Phase)

    • 서로 다른 일을 하는 코드를 각기 다른 모듈(클래스)로 분리한다.
      • 그래야 어떤 것을 변경할 때, 그것과 관련있는 것만 신경쓸 수 있다.
      • 응집도가 잘 되어있도록 코드 작성을 하는 것인데, 변경이 필요할 때 특정 모듈만 변경하면 되도록 할 수 있음.
    • 여러 일을 하는 함수라면 처리 과정을 각기 다른 단계로 구분할 수 있다.
      • 예) 전처리 → 주요 작업 → 후처리
      • 예) 컴파일러: 텍스트 읽어오기 → 실행 가능한 형태로 변경
    • 서로 다른 데이터를 사용한다면 단계를 나누는데 있어 중요한 단서가 될 수 있다.
    • 중간 데이터를 만들어 단계를 구분하고 매개변수를 줄이는데 활용할 수 있다. 
    • 목표 : 단계를 분리해서, 각 단계가 적절한 클래스에 위치할 수 있도록 하는 것이다. 

     

    단계 분리하기는 '메서드 안에서 각기 다른 일을 하는 코드들을 분리'하는 리팩토링이다. 하나의 메서드는 하나의 일만 할 때 이해하기 좋으며, 변경 시에도 손쉽게 변경할 수 있다. 

    '단계 분리하기'를 적용하기 좋은 경우는 '긴 메서드'일 때다. 긴 메서드에서는 구체적인 흐름이 있을텐데, 그 흐름을 나눠서 별도의 메서드로 분리를 한다. 이 때 메서드의 위치가 적절하지 않다면 다른 모듈로 메서드를 옮길 수 있다. 단계 분리하기는 다른 리팩토링의 전단계 리팩토링으로 사용할 수 있다.

    긴 메서드는 여러 단계의 프로세스로 나눌 수 있다. 예를 들면 전처리 / 주요 작업 / 후처리 등이다. 이렇게 나눈 작업들은 별도의 클래스 옮기거나, 관련있는 작업들끼리 묶어주는 형태의 리팩토링이 가능하다. 


    단계를 나누는 기준은? 

    그렇다면 어떤 경우에 단계를 나누는 것이 좋을까? 사용할 데이터가 다르다면, 그 둘은 서로 다른 프로세스에 있을 가능성이 있다는 것이다.


    단계 나누기의 장점은?

    • 복잡한 함수를 문맥별로 나누는 작업을 한다. 이를 통해서 코드의 응집도를 높일 수 있다. 
    • 쪼갠 프로세스 간에 데이터를 전달해야 할 필요가 있을 때, 중간 데이터를 만들어서 사용할 수 있다. 이 방법으로 메서드의 매개변수를 줄일 수 있게 됨. 

    코드 (쪼개기 + 중간 데이터 만들기)

    아래에서 priceOrder() 메서드를 살펴보자. 이 메서드에서는 다음 두 가지 작업을 한다

    • basePrice + Discount를 계산한다.
    • ShippingCost를 적용한 최종 Price를 구함. 

    이렇게 두 단계로 나누어 볼 수 있고, 각 단계를 메서드로 추출할 수 있다. 

    public class PriceOrder {
    
        // 코드가 너무 복잡하다.
        // 1. basePrice 구함.
        // 2. shipping 비용을 구함.
        // 코드를 나누는 과정에서 중간 단계의 변수를 만들고, 이것을 매개변수로 전달해준다.
        public double priceOrder(Product product, int quantity, ShippingMethod shippingMethod) {
            final double basePrice = product.basePrice() * quantity;
            final double discount = Math.max(quantity - product.discountThreshold(), 0)
                    * product.basePrice() * product.discountRate();
            final double shippingPerCase = (basePrice > shippingMethod.discountThreshold()) ?
                    shippingMethod.discountedFee() : shippingMethod.feePerCase();
            final double shippingCost = quantity * shippingPerCase;
            final double price = basePrice - discount + shippingCost;
            return price;
        }
    }

     

    가장 먼저 배송비를 구하는 단계를 메서드로 추출해 분리해 볼 수 있다. 적용하면 아래와 같이 메서드로 분리된다. 그런데 한 가지 문제점이 있다.

    분리된 메서드에서 너무 많은 매개변수를 필요로 한다는 것이다. 이 부분을 해결 하기 위해서 중간 데이터를 위한 새로운 레코드를 하나 만들어서, 이 부분을 해결할 수 있게 된다. 

    public class PriceOrder {
    
        // 코드가 너무 복잡하다.
        // 1. basePrice 구함.
        // 2. shipping 비용을 구함.
        // 코드를 나누는 과정에서 중간 단계의 변수를 만들고, 이것을 매개변수로 전달해준다.
        public double priceOrder(Product product, int quantity, ShippingMethod shippingMethod) {
            final double basePrice = product.basePrice() * quantity;
            final double discount = Math.max(quantity - product.discountThreshold(), 0)
                    * product.basePrice() * product.discountRate();
            return applyShipping(quantity, shippingMethod, basePrice, discount);
        }
    
        private double applyShipping(int quantity, ShippingMethod shippingMethod, double basePrice, double discount) {
            final double shippingPerCase = (basePrice > shippingMethod.discountThreshold()) ?
                    shippingMethod.discountedFee() : shippingMethod.feePerCase();
            final double shippingCost = quantity * shippingPerCase;
            final double price = basePrice - discount + shippingCost;
            return price;
        }
    }

    중간 단계 변수를 도입하면서 다음과 같이 코드를 한번 수정할 수 있게 되었다. 이제 applyShipping() 메서드는 매개변수를 적게 가지게 된다. 그 다음으로 처리해야 하는 부분은 basePrice, Discount를 계산하는 단계를 메서드로 추출하는 것이다.

    public class PriceOrder {
    
        // 코드가 너무 복잡하다.
        // 1. basePrice 구함.
        // 2. shipping 비용을 구함.
        // 코드를 나누는 과정에서 중간 단계의 변수를 만들고, 이것을 매개변수로 전달해준다.
        public double priceOrder(Product product, int quantity, ShippingMethod shippingMethod) {
            final double basePrice = product.basePrice() * quantity;
            final double discount = Math.max(quantity - product.discountThreshold(), 0)
                    * product.basePrice() * product.discountRate();
            PriceRealData priceRealData = new PriceRealData(basePrice, discount, quantity);
            return applyShipping(priceRealData, shippingMethod);
        }
    
        private double applyShipping(PriceRealData priceRealData, ShippingMethod shippingMethod) {
            final double shippingPerCase = (priceRealData.basePrice() > shippingMethod.discountThreshold()) ?
                    shippingMethod.discountedFee() : shippingMethod.feePerCase();
            final double shippingCost = priceRealData.quantity() * shippingPerCase;
            final double price = priceRealData.basePrice() - priceRealData.discount() + shippingCost;
            return price;
        }
    }

    마지막 단계를 분리하면 다음과 같이 코드가 리팩토링 된다. 

     

    public class PriceOrder {
    
        // 코드가 너무 복잡하다.
        // 1. basePrice 구함.
        // 2. shipping 비용을 구함.
        // 코드를 나누는 과정에서 중간 단계의 변수를 만들고, 이것을 매개변수로 전달해준다.
        public double priceOrder(Product product, int quantity, ShippingMethod shippingMethod) {
            PriceRealData priceRealData = calculatePriceData(product, quantity);
            return applyShipping(priceRealData, shippingMethod);
        }
    
        private PriceRealData calculatePriceData(Product product, int quantity) {
            final double basePrice = product.basePrice() * quantity;
            final double discount = Math.max(quantity - product.discountThreshold(), 0)
                    * product.basePrice() * product.discountRate();
            PriceRealData priceRealData = new PriceRealData(basePrice, discount, quantity);
            return priceRealData;
        }
    
        private double applyShipping(PriceRealData priceRealData, ShippingMethod shippingMethod) {
            final double shippingPerCase = (priceRealData.basePrice() > shippingMethod.discountThreshold()) ?
                    shippingMethod.discountedFee() : shippingMethod.feePerCase();
            final double shippingCost = priceRealData.quantity() * shippingPerCase;
            final double price = priceRealData.basePrice() - priceRealData.discount() + shippingCost;
            return price;
        }
    }

    댓글

    Designed by JB FACTORY