본문 바로가기

TDD, CleanCode

클린코더스 강의 리뷰 3. Function Structure

https://github.com/jiwon3346/videostore   

 

이번 예제는 switch문을 개별 클래스로 만들어 제거한다.

class Moive{
	double determineAmount(int daysRented) {
        double rentalAmount = 0;
        switch (pricecode) {
            case REGULAR:
                rentalAmount += 2;
                if (daysRented > 2)
                    rentalAmount += (daysRented - 2) * 1.5;
                break;
            case NEW_RELEASE:
                rentalAmount += daysRented * 3;
                break;
            case CHILDRENS:
                rentalAmount += 1.5;
                if (daysRented > 3)
                    rentalAmount += (daysRented - 3) * 1.5;
                break;
        }
        return rentalAmount;
    }
}

우리가 제거해야할 switch문이다.

 

일단 테스트 코드부터 살펴보자

 

1. 테스트를 polymorphism(다형성)하게 바꾼다.

 

    @Before
    public void setUp() {
        statement = new Statement("Customer");
        newReleaseMovie1 = new Movie("New Release 1", Movie.NEW_RELEASE);
        newReleaseMovie2 = new Movie("New Release 2", Movie.NEW_RELEASE);
        childrensMovie = new Movie("Childrens", Movie.CHILDRENS);
        regular1 = new Movie("Regular 1", Movie.REGULAR);
        regular2 = new Movie("Regular 2", Movie.REGULAR);
        regular3 = new Movie("Regular 3", Movie.REGULAR);
    }

기존의 코드는 어떤 경우에서도 Movie 인스턴스를 생성하고 스위치문을 통해 결과를 반환하는 식이다.

 

    @Before
    public void setUp() {
        statement = new Statement("Customer");
        newReleaseMovie1 = new NewReleaseMovie("New Release 1", Movie.NEW_RELEASE);
        newReleaseMovie2 = new NewReleaseMovie("New Release 2", Movie.NEW_RELEASE);
        childrensMovie = new ChildrensMovie("Childrens", Movie.CHILDRENS);
        regular1 = new RegularMoive("Regular 1", Movie.REGULAR);
        regular2 = new RegularMoive("Regular 2", Movie.REGULAR);
        regular3 = new RegularMoive("Regular 3", Movie.REGULAR);
    }

해당 코드를 각각의 객체로 분리했다.

 

각각의 클래스는 다음과 같은 형식을 지닌다. (객체 생성시에 HotFix(Alt +Enter)를 사용하면 쉽게 생성 가능)

public class ChildrensMovie extends Movie {
    public ChildrensMovie(String title, int priceCode) {
        super(title, priceCode);
    }
}

 

여기서 Movie.New_RELEASE와 같은 priceCode 파라미터들은 사실상 중복된다.( 각각의 하위 객체의 파라미터로 받을 필요 없이 그냥 부모 객체에서 가져오면 된다.)

 

public class Movie {

    public static final int CHILDRENS = 2;
    public static final int NEW_RELEASE = 1;
    public static final int REGULAR = 0;

    private String title;
    private int pricecode;

    public Movie(String title, int priceCode) {
        this.title = title;
        pricecode = priceCode;
    }
    
 public class ChildrensMovie extends Movie {
    public ChildrensMovie(String title) {
        super(title, Movie.CHILDRENS);
    }
}


test{
    @Before
    public void setUp() {
        statement = new Statement("Customer");
        newReleaseMovie1 = new NewReleaseMovie("New Release 1");
        newReleaseMovie2 = new NewReleaseMovie("New Release 2");
        childrensMovie = new ChildrensMovie("Childrens");
        regular1 = new RegularMoive("Regular 1");
        regular2 = new RegularMoive("Regular 2");
        regular3 = new RegularMoive("Regular 3");
    }
}

신기한 부분은 super()가 정말 부모 생성자를 함수처럼 가져와 쓴다 ( 완전 하위 클래스와 별개로 작동하는 모습!)

 

 2. push Members Down

이제 switch문은 세개의 클래스로 각각의 케이스가 독립되었으니 한번 발라내어보자.

 

IntelliJ의 push Members Down을 이용하면 쉽게 리팩토링할 수 있다.

 

< 발라낼 두개의 함수를 체크하고 부모에는 추상메소드로 선언! >

다음과 같이 분리 된다.

public abstract class Movie {

    abstract double determineAmount(int daysRented);

    abstract int determineFrequentRentalPoint(int daysRented);
}

public class ChildrensMovie extends Movie {
    public ChildrensMovie(String title) {
        super(title, Movie.CHILDRENS);
    }

    @Override
    double determineAmount(int daysRented) {
        double rentalAmount = 0;
        switch (getPriceCode()) {
            case REGULAR:
                rentalAmount += 2;
                if (daysRented > 2)
                    rentalAmount += (daysRented - 2) * 1.5;
                break;
            case NEW_RELEASE:
                rentalAmount += daysRented * 3;
                break;
            case CHILDRENS:
                rentalAmount += 1.5;
                if (daysRented > 3)
                    rentalAmount += (daysRented - 3) * 1.5;
                break;
        }
        return rentalAmount;
    }

    @Override
    int determineFrequentRentalPoint(int daysRented) {
        boolean bonusIsEarned = (getPriceCode() == NEW_RELEASE && daysRented > 1);
        if (bonusIsEarned)
            return 2;
        return 1;
    }
}

이후에 run with coverage 를 이용하여 실제 사용되는 코드와 사용되지 않는 코드를 눈으로 보고 지워보면 다음과 같다.

 

< ChildrensMovie클래스의 결과 Childrens케이스 이외에는 실행되지 않았다.(빨강) >

public class ChildrensMovie extends Movie {
    public ChildrensMovie(String title) {
        super(title, Movie.CHILDRENS);
    }

    @Override
    double determineAmount(int daysRented) {
        double rentalAmount = 0;
        rentalAmount += 1.5;
        if (daysRented > 3)
            rentalAmount += (daysRented - 3) * 1.5;
        return rentalAmount;
    }

    @Override
    int determineFrequentRentalPoint(int daysRented) {
        boolean bonusIsEarned = (getPriceCode() == NEW_RELEASE && daysRented > 1);
        if (bonusIsEarned)
            return 2;
        return 1;
    }
}

 

determineFrequentRentalPoint 메소드 역시 NEW_REALSE의 조건을 따진다.

클래스가 이미 분리되었기 떄문에 조건을 따질 필요가 사실 없어진다.