프로그래밍/클린코드 & 리팩토링

[객체지향 생활체조 원칙] 규칙 3. 모든 원시값과 문자열을 포장한다

림딩동 2021. 8. 28. 16:36

모든 원시값과 문자열을 포장한다

 int, long, String 과 같은 원시타입, 문자열 변수를 객체로 포장해 사용하라는 지침이다.

숨은 의미

 프로그래밍에서 변수는 '상태' 로 쓰일 수 있다. 상태는 '자료'가 아니라 '정보'다. 단순히 값을 나타내는 것 뿐 아니라, 비즈니스적인 의미를 함께 표현해준다. 이렇게 업무적 의미를 갖는 변수를 객체로 포장해 사용하면 얻는 이점이 많다.

예시

public class EvaluateService {

    private static final int MIN_CREDIT_SCORE = 0;
    private static final int MAX_CREDIT_SCORE = 1000;

    public void evaluateCustomerCreditRate(int score) {

        if (score < MIN_CREDIT_SCORE || score > MAX_CREDIT_SCORE) {
            throw new IllegalArgumentException("신용점수는 " + MIN_CREDIT_SCORE + " 점 이상, " + MAX_CREDIT_SCORE + " 점 미만이어야 합니다.");
        }

        // 검증로직...
    }
}

 위 EvaluateService 클래스는 신용점수를 기반으로 고객을 심사해주는 역할을 한다. 딱히 문제될게 있어보이지는 않는다. 매직넘버를 사용하지도 않았고, 입력값을 잘 검증해주고 있다.

 

 문제는 저 score 에 대한 검증을 지금은 EvaluateService 클래스만 할 수 있다는 것이다. 다른 클래스에서 신용점수를 사용해야 한다면 동일한 로직을 구현해 주어야 한다.

 

 CreditScoreValidator 라는 클래스를 만들어 validate 메소드를 통해 검증할 수 있지 않나? 라는 생각을 할 수도 있다. 하지만 이 방법은 객체가 스스로 자신의 상태를 관리하지 못하는 형태이다. 상태를 갖는 객체는 상태에 대한 책임을 스스로 져야 한다.

상태를 포장해 보자

public class CreditScore {

    private static final int MIN_CREDIT_SCORE = 0;
    private static final int MAX_CREDIT_SCORE = 1000;

    private int value;

    public CreditScore(int value) {
        if (value < MIN_CREDIT_SCORE || value > MAX_CREDIT_SCORE) {
            throw new IllegalArgumentException("신용점수는 " + MIN_CREDIT_SCORE + " 점 이상, " + MAX_CREDIT_SCORE + " 점 미만이어야 합니다.");
        }
        this.value = value;
    }
}

public class EvaluateService {

    public void evaluateCustomerCreditRate(CreditScore creditScore) {
        
        // 검증로직
    }
}

 위 코드에서는 기존의 int 타입 score 변수를 CreditScore 클래스로 포장했다. 이렇게 값을 사용하면 EvaluateService 클래스에 의존하는 프로그램에서는 반드시 CreditScore 클래스의 인스턴스를 생성해야 한다. 생성 과정에서 자연스럽게 값에 대한 유효성 체크를 할 수 있다. 다른 곳에서 '신용점수' 라는 '정보' 가 사용되더라도 검증로직을 일관되게 관리할 수 있다.

 

 유효성 검증의 기준을 클래스가 직접 가지게 되므로, 신용점수에 음수구간이 신설되거나, 최대점수가 상향되더라도 대응하기에 용이하다. 점수를 특정 구간으로 나누어 등급화 하는 경우에도 CreditScore 클래스에 기능을 추가함으로써 대응할 수 있다.

원시타입을 포장함으로써 메소드의 시그니처가 명확해진다.

 클래스로 포장한 타입을 메소드의 파라미터로 사용하게 되면 메소드를 설명하기가 더욱 편해진다.

public void evaluateCustomerCreditRate(int score)
public void evaluateCustomerCreditRate(CreditScore creditScore)

 두 메소드를 비교해 보면, 아래의 메소드 시그니처가 더욱 명확함을 알 수 있다. int 타입에는 값에 대한 검증이 구현되어있지 않기 때문에, 유효성을 확인하기 위해 메소드의 로직을 열어서 보아야 한다. 객체 타입인 경우 해당 객체의 생성자를 확인해주면 되기 때문에 별도의 validation을 확인해야 한다는 번거로움이 적어진다.

마치며

 객체지향 프로그래밍을 구사하고 싶으나, 클래스의 정의를 내리기 어려운 경우가 많다. 이런 경우에는 원시타입 변수를 포장하려는 시도가 도움이 될 수 있다. 일급컬렉션, VO 와 같은 개념도 원시값을 포장하려는 의도와 통하는 부분이 많다.

 

참고자료 (소트웍스 엔솔러지)

https://book.naver.com/bookdb/book_detail.nhn?bid=5441199