[리팩토링] 악취 14 : 성의없는 요소

 

✍️ 악취 14 : 성의없는 요소

개발과정에서 정의된 변수, 메서드, 클래스가 시간이 지남에 따라 필요 없는 경우가 있다. 가령 확장성을 고려해서 미리 추가된 변수와 기능들이 그러한 경우에 속한다. 혹은 리팩토링의 결과로 더 이상 필요 없는 요소들이 나올 수 있다. 리팩토링 책의 저자는 이와 같은 요소를 '성의 없는 요소'라고 정의했다.

 

재사용을 고려해서 함수 추출하기 리팩토링이 적용된 어느 메서드가 계속해서 재사용이 되지 않고 한 곳에서만 사용한다면 '함수 인라인'을 적용할 수 있다. 이와 유사하게 클래스의 경우라면 '클래스 인라인'을 적용하면 된다.

 

여기 성의 없는 요소 악취를 해결하기 위한 세 가지 리팩토링 기법이 있다.

1. "함수 인라인"

2. "클래스 인라인"

3. "계층 합치기" 불필요한 상속 구조 제거

 

본 포스팅에서 관심 있게 살펴볼 리팩토링은 불필요한 상속 구조를 해결하기 위한 '계층 합치기'이다.

 

🍊 계층 합치기

상속 구조를 리팩토링 하는 과정에서 일부 하위 클래스를 삭제하거나, 기능을 올리고 내리다 보면 상위 클래스와 하위 클래스의 차이가 없는 경우가 발생한다. 이러한 경우 계층 합치기 리팩토링을 적용할 수 있다.

 

여기 등급에 따른 서로 다른 할인율과 혜택을 받는 클래스가 상속의 형태로 정의되어 있다.

@AllArgsConstructor
@Getter
public abstract class Customer {

    protected double baseDiscountRate;

    public double getDiscountRate() {
        return baseDiscountRate;
    }
    
    public abstract int applyDiscount(int price, int deliveryFee);
    
}
public class GoldCustomer extends Customer {

    private int monthlyPoint;

    public GoldCustomer(double baseDiscountRate, int monthlyPoint) {
        super(baseDiscountRate);
        this.monthlyPoint = monthlyPoint;
    }

    @Override
    public int applyDiscount(int price, int deliveryFee) {
        int discountedPrice = price;
        if (monthlyPoint > 0) {
            int pointDiscount = Math.min(discountedPrice, monthlyPoint);

            discountedPrice -= pointDiscount;
            monthlyPoint -= pointDiscount;
        }

        discountedPrice *= (1.0 - getDiscountRate());

        return discountedPrice + deliveryFee;
    }
}
public class VipCustomer extends Customer {

    private int freeDeliveryCnt;

    public VipCustomer(double baseDiscountRate, int freeDeliveryCnt) {
        super(baseDiscountRate);
        this.freeDeliveryCnt = freeDeliveryCnt;
    }

    @Override
    public double getDiscountRate() {
        return baseDiscountRate + 0.2;
    }

    @Override
    public int applyDiscount(int price, int deliveryFee) {
        int discountedPrice = price;
        discountedPrice *= (1.0 - getDiscountRate());

        if (freeDeliveryCnt > 0) {
            freeDeliveryCnt--;

            return discountedPrice;
        }

        return discountedPrice + deliveryFee;
    }
}

 

만약 VipCustomer 클래스가 사라져서 상속이 무의미해질 때, Customer 클래스와 GoldCustomer를 하나로 묶을 수 있다.

@AllArgsConstructor
@Getter
public class Customer {

    private double baseDiscountRate;
    private int monthlyPoint;

    public double getDiscountRate() {
        return baseDiscountRate;
    }

    public int applyDiscount(int price, int deliveryFee) {
        int discountedPrice = price;
        if (monthlyPoint > 0) {
            int pointDiscount = Math.min(discountedPrice, monthlyPoint);

            discountedPrice -= pointDiscount;
            monthlyPoint -= pointDiscount;
        }

        discountedPrice *= (1.0 - getDiscountRate());

        return discountedPrice + deliveryFee;
    }

}