[리팩토링] 악취 16 : 임시 필드 *

 

✍️ 악취 16 : 임시 필드

클래스의 필드가 특정 상황에만 값을 갖고 그 외의 상황에는 null 또는 기본 값 혹은 임의의 값을 가질 때 이를 '임시 필드'라고 한다. 만약 클래스가 일반 필드와 임시 필드를 동시에 갖는다면 해당 클래스와 관련된 코드를 이해하기 어려워진다. 가령 임시 필드가 언제부터 제대로 된 값을 갖는지, 임시 값의 유무에 따른 동작이 어떻게 달라지는지 이해하는 건 결코 쉬운 일이 아니다.

 

여기 임시 필드를 세 가지 리팩토링 기법이 있다.

1. "클래스 추출하기" 임시 필드를 다루는 전용 클래스를 생성하기

2. "함수 옮기기" 기존에 임시 필드를 사용하는 메서드를 전용 클래스의 메서드로 옮기기

3. "특이 케이스 추출하기" 특정한 경우에 해당하는 클래스 만들기

 

본 포스팅에서 관심 있게 살펴볼 리팩토링은 "특이 케이스 추출하기"이다.

 

🍊 특이 케이스 추출하기

어떤 필드의 특정 값에 따라 동작이 달라지는 코드가 반복적으로 등장한다면 해당 필드를 감싸는 별도의 클래스를 정의할 수 있다. 예외적인 기능을 한 클래스에서 관리하는 장점이 있고 동시에 코드를 이해하기 한 결 쉬워진다. 여기에 적용하는 디자인 패턴을 "특이 케이스 패턴"이라고 부르며 Null Object 패턴이 그 대표적인 예시이다.

 

"unknown"이라는 특정 값에 따라서 메서드의 동작이 달라지는 코드가 반복적으로 등장한다.

public class CustomerService {

    public String customerName(Customer customer) {

        String customerName;
        if (customer.getName().equals("unknown")) {
            customerName = "visitor";
        } else {
            customerName = customer.getName();
        }

        return customerName;
    }

    public Benefits getBenefits(Customer customer) {
        return customer.getName().equals("unknown") ? new BasicBenefits() : customer.getBenefits();
    }

    public int getPurchaseCount(Customer customer) {
        return customer.getName().equals("unknown") ? 0 : customer.getPurchaseHistory().getPurchaseCount();
    }

}

 

특정 값에 따른 행위를 클라이언트 메서드에서 처리하지말고 Customer를 상속받는 별도의 클래스를 정의해서 예외 로직을 작성한다.

public class UnknownCustomer extends Customer {

    public UnknownCustomer() {
        super("unknown", new BasicBenefits(), new NullPurchaseHistory());
    }

    @Override
    public String getName() {
        return "visitor";
    }

    @Override
    public boolean isUnknown() {
        return super.isUnknown();
    }
    
}

public class NullPurchaseHistory extends PurchaseHistory {

    public NullPurchaseHistory() {
        super(0);
    }
    
}

 

클라이언트는 필드의 값이 무엇인지 관심 없고 그저 추상화된 메서드를 호출할 뿐이다. 다만 CustomerService를 호출하는 쪽에서 필드값을 보고 Customer 그대로 전달할지 UnknownCustomer로 변환해서 전달할지 판별해야 한다. 

public class CustomerService {

    public String customerName(Customer customer) {
        return customer.getName();
    }

    public Benefits benefits(Customer customer) {
        return customer.getBenefits();
    }

    public int purchaseCount(Customer customer) {
        return customer.getPurchaseHistory().getPurchaseCount();
    }

}