본문 바로가기

TDD, CleanCode

클린코더스 강의 리뷰 2. Function part2

Prime Number(소수)를 출력하는 함수에 대한 리팩토링을 한다.

< 오늘의 환자 >

이번 포스팅에 리팩토링할 코드이다.

문제점을 살펴 보면

1. 하나의 메소드가 너무 많은 역할을 한다.

2. 하나의 클래스 역시 너무 많은 역할을 한다.

3. 변수명에서 기능을 유추할 수 없다.

4. 인덴트가 어마무시하다.

 

일단 리팩토링하기 앞서서 리팩토링을 하는 도중 기능이 변경이 생기지 않도록 테스트 코드를 작성한 후 진행하자.

 

Rename

package function;

public class PrintPrimes {
    public static void main(String[] args) {
        final int numberOfPrimes = 1000;
        final int linesPerPage = 50;
        final int columns = 4;
        final int ordmax = 30;
        int primes[] = new int[numberOfPrimes + 1];
        int pagenumber;
		... 이하 생략

3번 문제를 해결 하기 위해 서술적인 변수명을 이용했다.

 

Extreact Method Object

 

그 다음 Extract Method Object로 메소드를 클래스로 분리 시키자. 

이 때, 기준은 어떤 함수가 일련의 변수들을 가지고 있을 때 하나 이상의 클래스가 숨어있다는 것을 전제하에 시작된다.

 

    private static class PrimePrinterHelper {

        private final int numberOfPrimes = 1000;
        private final int linesPerPage = 50;
		...
        private int multiples[] = new int[ordmax + 1];

        public void invoke() {
            candidate = 1;
            primeIndex = 1;
            primes[1] = 2;
            ord = 2;
            square = 9;
            while (primeIndex < numberOfPrimes) {
                do {
            ....
                {
                pagenumber = 1;
                pageoffset = 1;
                while (pageoffset <= numberOfPrimes) {
                    System.out.println("The First " + numberOfPrimes +
                            " Prime Numbers --- Page " + pagenumber);
                    System.out.println("");
                    for (rowoffset = pageoffset; rowoffset < pageoffset + linesPerPage; rowoffset++) {
                        for (column = 0; column < columns; column++)
                            if (rowoffset + column * linesPerPage <= numberOfPrimes)
                                System.out.format("%10d", primes[rowoffset + column * linesPerPage]);
                        System.out.println("");
                    }
                    System.out.println("\f");
                    pagenumber = pagenumber + 1;
                    pageoffset = pageoffset + linesPerPage * columns;
                }
            }

 

Extract Method 

이 때 { } 부분은 prime된 결과를 출력하는 메소드로 분리 할 수 있을 것 같다.

        private void invoke() {
			...
            printNumber();
        }

        private void printNumber() {
            pagenumber = 1;
            pageoffset = 1;
			...
                pageoffset = pageoffset + linesPerPage * columns;
            }
        }

 

main 메소드의 단계를 나누어 보자

    public static void main(String[] args) {
        PrimePrinterHelper primePrinterHelper = new PrimePrinterHelper();
        int [] primes = primePrinterHelper.invoke();
        primePrinterHelper.printNumber(primes);

이 때 invoke는 void가 아닌 primes을 리턴하는 메소드로 만들었다.

이유는 printNumber 메소드에서 값은 invoke에서 변경되는데, 값을 공유 한다면 클래스 분리를 한다거나 할 때, 지나치게 연관성이 깊어지기 때문이다.

 

Method Local Variable

그러고 보니 나서 보니, printNumber에만 사용되는 클래스 변수들이 있다.

이것들은 printNumber에만 적용되게 바꾸어 주는게 좋다.

 

    private static class PrimePrinterHelper {
        private final int linesPerPage = 50;
        private int pagenumber;
        private int pageoffset;
        private int column;
        public int[] invoke() {
				...
            }
            return primes;
        }

        public void printNumber(int[] primes) {
            pagenumber = 1;
            pageoffset = 1;
			...
                pageoffset = pageoffset + linesPerPage * columns;
            }
        }
    }
}

 

위 변수들을 독립적으로 바꾸어 준다.

public class PrintPrimes {
    static final int linePerPage = 50;
    static final int columns = 4;
    public static void main(String[] args) {
        PrimePrinterHelper primePrinterHelper = new PrimePrinterHelper();
        int [] primes = primePrinterHelper.invoke();
        primePrinterHelper.printNumber(primes, linePerPage, columns);
    }
        
        public void printNumber(int[] primes, int linesPerPage, int column) {
            int pagenumber = 1;
            int pageoffset = 1;
            while (pageoffset <= numberOfPrimes) {
                System.out.println("The First " + numberOfPrimes +
                        " Prime Numbers --- Page " + pagenumber);
                System.out.println("");
                for (rowoffset = pageoffset; rowoffset < pageoffset + linesPerPage; rowoffset++) {
                    for (column = 0; column < columns; column++)
                        if (rowoffset + column * linesPerPage <= numberOfPrimes)
                            System.out.format("%10d", primes[rowoffset + column * linesPerPage]);
                    System.out.println("");
                }
                System.out.println("\f");
                pagenumber = pagenumber + 1;
                pageoffset = pageoffset + linesPerPage * columns;
            }
        }

이 때, 변하는 값은 파라미터로, 절때 변하지 않는 값은 지역변수로 정의한다.

 

이러고 나니 PrintPrimes은 PrimePrinterHelper와 독립적이게 됬다.

이는 클래스로 바꿀수 있다는 의미이고, 변수 몇가지를 더 파라미터로 추가해 클래스를 나누었다.

    private class PrimePrinterHelper {
		private void printNumbers(int[] numbers, int numberOfPrimes, int linesPerPage, int columns) {
            new NumberPrinter(numbers, numberOfPrimes, linesPerPage, columns).invoke();
        }
}


package function;

public class NumberPrinter {
    private int[] numbers;
    private int numberOfPrimes;
    private int linesPerPage;
    private int columns;

    public NumberPrinter(int[] numbers, int numberOfPrimes, int linesPerPage, int columns) {
        this.numbers = numbers;
        this.numberOfPrimes = numberOfPrimes;
        this.linesPerPage = linesPerPage;
        this.columns = columns;
    }

    public void invoke() {
        int pagenumber = 1;
        int pageoffset = 1;
        while (pageoffset <= numberOfPrimes) {
            System.out.println("The First " + numberOfPrimes +
                    " Prime Numbers --- Page " + pagenumber);
            System.out.println("");
            for (int rowoffset = pageoffset; rowoffset < pageoffset + linesPerPage; rowoffset++) {
                for (int column = 0; column < columns; column++)
                    if (rowoffset + column * linesPerPage <= numberOfPrimes)
                        System.out.format("%10d", numbers[rowoffset + column * linesPerPage]);
                System.out.println("");
            }
            System.out.println("\f");
            pagenumber = pagenumber + 1;
            pageoffset = pageoffset + linesPerPage * columns;
        }
    }
}

이 때 numbers와 numberOfPrimes는 함수를 부를 때 마다 변경되는 값이다.

그렇기 때문에, 생성자 보단 invoke()의 파라미터로 바꿔 주는게 좋겠다.

package function;

public class NumberPrinter {
    private int linesPerPage;
    private int columns;

    public NumberPrinter(int linesPerPage, int columns) {
        this.linesPerPage = linesPerPage;
        this.columns = columns;
    }

    public void invoke(int[] numbers, int numberOfPrimes) {
        int pagenumber = 1;
        int pageoffset = 1;
        while (pageoffset <= numberOfPrimes) {
            System.out.println("The First " + numberOfPrimes +
                    " Prime Numbers --- Page " + pagenumber);
            System.out.println("");
            for (int rowoffset = pageoffset; rowoffset < pageoffset + linesPerPage; rowoffset++) {
                for (int column = 0; column < columns; column++)
                    if (rowoffset + column * linesPerPage <= numberOfPrimes)
                        System.out.format("%10d", numbers[rowoffset + column * linesPerPage]);
                System.out.println("");
            }
            System.out.println("\f");
            pagenumber = pagenumber + 1;
            pageoffset = pageoffset + linesPerPage * columns;
        }
    }
}

 

이런 식으로 계속해서 반복해서 줄여나가면 된다.

보통은 Extract Till you Drop 방식으로 하라고 하는데

1. 더 이상 extract 할 수 없을 떄까지

2. 4줄 이내의 함수들로만 구성된 클래스

3. if wihle문에서 {}가 보인다면 extract 대상

4. {}는 extract할 기회

라고 한다.

 

< 클린코더스 강의 자료 중 >

가끔 보자.