본문 바로가기
IT 공부/객체지향 설계 공부

하이브리드 커플링과 오브젝트

by exdus3156 2023. 12. 7.

1. 변수를 한 가지 목적으로만 사용하라.

<코드 컴플리트> 책의 제 10장. 변수 사용 시 고려할 사항들에는 다음과 같은 조언이 있다.

변수가 한 가지 이상의 목적으로 사용되는 것은 그 변수를 만든 개발자는 몰라도, 그 변수를 읽고 코드를 이해해야 하는 다른 개발자에게는 추론을 강제한다는 점에서 나쁜 코드다. 

변수를 한 가지 이상의 목적으로 사용하는 사례 중 하나는 "변수의 값에 숨은 의미를 부여하는 습관"이다. 이를 "하이브리드 커플링"이라고 부른다. 예를 들면 다음과 같다.

  • pageCount 변수의 값은 출력된 페이지 수를 나타낸다. 만약 이 값이 -1이면 오류가 발생한 것이다.
  • memberID는 회원의 식별 고유번호를 나타낸다. 그런데 이 값이 5000 이상이면 연체된 계정이다. 1000 이하이면 관리 계정이다.

위 예제들에 나오는 변수들이 잘못된 이유는 변수가 두 가지 이상의 책임을 짊어지고 있다는 점이다. pageCount는 말 그대로 페이지 출력 수만을 나타내야 한다. 오류는 다르게 처리해야 한다. 그러나 위 예시에서는 pageCount의 값에 오류라는 맥락을 부여해버렸다.

주석을 달면 괜찮다고 여기겠지만, 변수를 이렇게 이중 목적으로 사용하면 자신에게만 명백해 보일 뿐, 다른 사람에게는 전혀 그렇지 않다는 점을 알아야 한다.

 

2. 오브젝트에 나온 사례

public class Moive {
    private DicountPolicy discountPolicy;
    private Money fee;

    public Money calculateMovieFee(Screening screening) {
        if (this.discountPolicy == null) {
            return fee;
        }
        
        return fee.minus( discountPolicy.calculateDiscountAmount(screening) );
    }
}

조영호님의 <오브젝트> 책의 챕터2 에는 위와 같은 예제가 나온다.

Movie 객체는 영화 예매 요금을 계산하는데, 영화 배급사의 할인 정책(dicountPolicy)에 따라 할인을 계산한다. Movie 객체는 DiscountPolicy(할인 정책 객체)에게 할인 요금 계산 메시지를 전송한다. DiscountPolicy는 추상클래스(Abstract Class)다.

그런데 만약 어떤 Movie는 그 어떤 할인도 적용하지 않는다고 하면 어떻게 될까? 

위 코드는 그 구현을, discountPolicy가 null인 경우 할인이 적용되지 않는다고 해석하고 있다. discountPolicy가 참조 객체 변수이기 때문에, 이것도 따지고 보면 하이브리드 커플링의 일종이라고 나는 생각한다.

이 변수는 현재 두 가지 이상의 목적에 사용되고 있다. null이면 "무할인"이고, null이 아니면 "무언가 할인이 적용된 상태"를 뜻하고 있기 때문이다. null이라는 값에 "무할인"이라는 맥락을 부여해버렸다. 이제 이 코드를 읽는 다른 개발자는 null일 때 요금(fee)을 그대로 반환하는 것을 보고 "무할인"이라는 맥락을 읽어야 하는 고생을 해야할 것이다.

게다가 책임 소지도 뒤죽박죽이다. DicountPolicy 객체가 직접 할인 요금 계산을 담당하고 있다. 그렇다면 끝까지 할인 요금 계산의 책임은 이 객체가 맡아야 한다

그런데 위 코드에서는 Movie 객체가 calculateMovieFee() 메소드 안에서 자신이 직접 요금을 계산해버렸다. 남의 책임을 본인이 떠맡은 것이다. 할인이 적용되지 않아 요금을 그대로 반환해야 한다면, 이것을 검증하는 것 또한 DicountPolicy가 해야하지, Movie가 하면 안 된다.

public class NoneDicountPolicy extends DicountPolicy {
    @Override
    protected Money getDicountAmount(Screening screening) {
        return Money.ZERO;
    }
}

 

위와 같이 DicountPolicy 객체가 무할인 정책 책임을 맡아야 한다. 위 코드에서는 할인 정책 객체가 추상 클래스로 설계되었고, 구체적인 구현은 자식 클래스가 담당하고 있다.