본문 바로가기

TDD, CleanCode

클린코더스 강의 리뷰 5. TDD 예제 (Prime factors)

1. 가장 쉬운 예제를 찾는다.

 

1-1 1로 소인수 분해를 했을 때의 얻는 값.

public class PrimeFactorsTest {
    @Test public void canFactorIntoPrimes() {
        assertEquals(Arrays.asList(), of(1));
    }
    private List<Integer> of(int n) {
        return new ArrayList<Integer>() ;
    }
}

assertEquals는 기대한 값이 앞에 , 실제 값이 뒤에 온다 assertEquals(예상값, 실제값)

 

of 메소드는 테스트를 위해 만들었고,

n의 소인수를 리스트로 반환한다.

 

1-2 헌데 예상값의 List는 Object를 실제값은 Integer를 반환한다.

public class PrimeFactorsTest {
    @Test public void canFactorIntoPrimes() {
        assertEquals(list(), of(1));
    }

    private List<Integer> list() {
        return Arrays.asList();
    }

    private List<Integer> of(int n) {
        return new ArrayList<Integer>() ;
    }
}

이 문제를 해결하기 위해서 extract method를 이용하여 리턴 타입을 설정할 수 있게 된다.

 

2. 다음 쉬운 예제 찾기

 

2-1 2의 소인수 찾기

    @Test public void canFactorIntoPrimes() {
        assertEquals(list(), of(1));
        assertEquals(list(2),of(2));
    }

다음과 같이 테스트를 설정할 수 있지만 list는 파라미터를 가지지 않는다.

    private List<Integer> list(Integer... ints) {
        return Arrays.asList(ints);
    }

다음과 같이 argument를 동적으로 받을 수 있고, 그 값이 배열로 저장되게 하여 리스트를 생성한다.

    private List<Integer> of(int n) {
        ArrayList<Integer> factors = new ArrayList<Integer>();
        if(n==2)
            factors.add(2);
        return factors;
    }

of(2)일 경우 그린라이트를 만들기 위해 다음과 같이 만들었다.

기존의 new ArrayList를 variable로 만들고

n==2인 경우에는 2를 추가하고 아닌 경우에는 여전히 new ArrayList의 초기화값인 빈 배열을 반환한다.

 

이때 if(n==2)는 아주 특별한 경우이다.

이 경우를 점점 generic(일반화) 시켜야 하고 다음과 같이 수정해볼 수 도 있다.

        if(n > 1)
            factors.add(2);

여전히 특별하지만 이전 경우보단 더 포괄적으로 작동 되어 진다.

 

3. 이와 같은 식으로 소수의 소인수는 다음과 같이 포괄적으로 코드를 짤 수 있다.

    @Test public void canFactorIntoPrimes() {
        assertEquals(list(), of(1));
        assertEquals(list(2),of(2));
        assertEquals(list(3),of(3));
    }

    private List<Integer> list(Integer... ints) {
        return Arrays.asList(ints);
    }

    private List<Integer> of(int n) {
        ArrayList<Integer> factors = new ArrayList<Integer>();
        if(n > 1)
            factors.add(n);
        return factors;
    }

 

4. 테스트 케이스의 중복을 제거 하자

    @Test public void canFactorIntoPrimes() {
        assertEquals(list(), of(1));
        assertEquals(list(2),of(2));
        assertEquals(list(3),of(3));
    }

다음 테스트 코드는 구조가 같다.

 

다른 부분을 먼저 variable로 바꾸자.

    @Test public void canFactorIntoPrimes() {
        List<Integer> list = list();
        int n = 1;
        assertEquals(list, of(n));
        assertEquals(list(2),of(2));
        assertEquals(list(3),of(3));
    }

그리고 extract method를 한다.

    @Test public void canFactorIntoPrimes() {
        List<Integer> list = list();
        int n = 1;
        assertPrimeFactors(list, n);
        assertPrimeFactors(list(2), 2);
        assertPrimeFactors(list(3), 3);
    }

그리고 extract하기 위한 변수들을 다시 inline 시킨다.

public class PrimeFactorsTest {
    @Test public void canFactorIntoPrimes() {
        assertPrimeFactors(list(), 1);
        assertPrimeFactors(list(2), 2);
        assertPrimeFactors(list(3), 3);
    }

파라미터 값이 예측값, 실제값이다.

실제값, 예측값으로 바꾼다면 훨씬 가독성이 좋아 질 것이다.

change signiture를 이용하여 파라미터의 위치를 바꾼다.

    @Test public void canFactorIntoPrimes() {
        assertPrimeFactors(1, list());
        assertPrimeFactors(2, list(2));
        assertPrimeFactors(3, list(3));
    }

 

5. 소수가 아닌 수를 나눈 소인수 값들

assertPrimeFactors(4,list(2,2));

4의 소인수는 2, 2이다. (엄밀하게 따지면 중복을 허용하지는 않지만...)

 

해당 문제를 해결할 of를 추가하자.

    private List<Integer> of(int n) {
        ArrayList<Integer> factors = new ArrayList<Integer>();
        if(n > 1) {
            if (n % 2 == 0) {
                factors.add(2);
                n /= 2;
            }
            if(n >1)
                factors.add(n);
        }
        return factors;
    }

2로 나누어 떨어지면 2로 나누고, 그 수가 1보다 크다면(2일 경우를 방지) 해당 n을 다시 추가한다.

 

5-2  2개 이상이라면?

assertPrimeFactors(8,list(2,2,2));

해당 케이스는 아마 결과값이 list(2,2)일 것이다.

이를 수정하기 위해

        if (n > 1) {
            while (n % 2 == 0) {
                factors.add(2);
                n /= 2;
            }
        }
        if (n > 1)
            factors.add(n);
        return factors;

가운데 if절을 while로 바꾸어 주었다.

 

5-3 3인 소인수들은?

        assertPrimeFactors(9,list(3,3));

위의 경우에 3에 처리하는 로직이 없기 때문에 다음과 같이 복사한 후 수정한다.

            while (n % 2 == 0) {
                factors.add(2);
                n /= 2;
            }
            while (n % 3 == 0) {
                factors.add(3);
                n /= 3;
            }

 

5-4 중복을 제거하자

        int divisor = 2;
        while (n > 1) {
            while (n % divisor == 0) {
                factors.add(divisor);
                n /= divisor;
            }
            divisor++;
        }

n 이 1이 될때까지 계속 divisor를 나누면서 divisor를 더해준다.

 

이 때 안쪽의 while문을 for문으로 바꿀 수 있는데

            for (;n % divisor == 0;) {
                factors.add(divisor);
                n /= divisor;
            }

정지 조건만 저렇게 만들고 초기조건과 증가 조건은 내버려 둔다.

            for (;n % divisor == 0;n /= divisor) {
                factors.add(divisor);
            }

증가 조건도 있으니 올린다.

 

바깥 조건도 동일하게 바꾼다.

        for (;n > 1;divisor++) {
            for (;n % divisor == 0;n /= divisor)
                factors.add(divisor);
        }

 

6. 복잡한 케이스를 추가해보자.

        assertPrimeFactors(2*2*2*3*3*5*7*11*13,list(2,2,2,3,3,5,7,11,13));

코드가 잘 작성 됬다면 정상적으로 동작할 것이다.