리팩토링 시에 주의해야 할 점

송민준·2023년 11월 22일
0

우테코 프리코스

목록 보기
10/10

당연한 얘기로 들릴 수 있지만 이후에 치명적인 실수로 돌아올 수 있으므로 메모 차 글로 남기려고 한다.

상황

3주차 로또 미션에서 사용자는 금액을 입력 받고 갯수에 맞는 로또를 생성한다. 그 로또의 숫자들은 1에서 45 범위에 들어야한다.
로또는 6가지 로또 번호를 사용자에게 입력받는다.
-> 그래서 나는 사용자에게 입력받은 로또 번호가 1에서 45에 벗어나면 예외처리하는 기능을 정의했다.

평소 같이 테스트 코드를 먼저 작성했고 예외처리 기능을 구현했다.

public class WinningNumberTest {
//6개 이상 예외처리가 되기 전에 숫자 범위 예외처리가 됨
    @ParameterizedTest  
    @ValueSource(strings = {"10 50 100", "0 10"})  
    void 당첨번호_범위에_벗어난_입력_예외처리_테스트(String input) {  
        List<String> inputs = Arrays.asList(input.split(" "));  
        assertThatThrownBy(() -> new WinningNumber(inputs)).isInstanceOf(IllegalArgumentException.class);  
    }  
}

public class WinningNumber {
	public WinningNumber(List<String> inputs) {...}
	private void validateRange(String input) {  
	    int number = Integer.parseInt(input);  
	    if (number > 0 && number <= 45) return;  
	    throw new IllegalArgumentException(("당첨 번호는 1에서 45 숫자를 입력해주세요"));  
	}
}

예외처리 테스트에 성공하는 코드이다. 여기까지는 문제가 없다.

리팩토링

WinningNumber의 생성자를 보면 입력을 List<String>으로 받고 있다. WinningNumber는 파라미터로 들어온 인자를 String에서 int 로 바꿔야하며 그 안에서 숫자인지, 범위에 들어가는 지 예외처리해야한다.
WinningNumber가 아니더라도 다른 클래스에서도 파라미터에 String으로 받고 있었다.
문자열을 숫자인 지 예외처리하고 숫자로 변경하는 책임은 WinningNumber에 없다고 생각해 아예 숫자로 바꾼 결과를 파라미터로 넣은 식으로( public WinningNumber(List<Integer> inputs) ) 로 변경했다.

public class WinningNumberTest {
	<Removed>
}

public class WinningNumber {
	public WinningNumber(List<Integer> inputs) {...}
	<Removed>
}

여기서 문제가 발생했다. 단순하게 List<Integer>를 파라미터로 들어오도록 만드니 기존에 WinningNumber에서 수행했던 예외처리를 삭제했다. 게다가 기존 WinningNumberTest에서도 예외처리 테스트 기능을 삭제했다.
삭제하면 책임이 옮겨진 클래스에 구현을 해야하지만 문자열을 숫자로 바꾸는 기능만 옮겼다.

해결 방법

중요한 점은 내가 테스트 코드도 WinningNumberTest에서 지웠다는 점이다. 테스트 코드는 기능 단위로 작성된다. 테스트 코드가 삭제되면 그 기능을 테스트 하지 못하게 되며 전체적으로 단위 테스트를 실행해도 놓친 기능을 발견 할 수 없게 되는 것이다.

리펙토링을 하게 되면 어쩔 수 없이 여러 기능을 건드려야 한다. 그 사이에서 놓친 기능이 있을 수 있다. 이는 객체가 수행했던 기능이 다른 객체로 옮겨졌기 때문에 일어난다.
그래서 리팩토링 전에 테스트 코드부터 옮겨야한다.

public class LottoNumberTest {  
    @DisplayName("로또 번호의 범위가 1에서 45 범위 밖에 있다면 예외를 발생한다")  
    @ParameterizedTest  
    @ValueSource(ints = {0, 46})  
    void 범위_밖_로또넘버_예외처리_테스트(int input) {  
        assertThatThrownBy(() -> new LottoNumberDTO(input)).isInstanceOf(IllegalArgumentException.class);
    }
}

public class LottoNumberDTO {  
	... 
    public LottoNumberDTO(int number) {  
        validateNumberRange(number);  
        this.number = number;  
    }  
	...
  
    private void validateNumberRange(int number) {  
        if (number > 0 && number <= 45) return;  
        throw new IllegalArgumentException(RANGE_ERROR_TEXT);  
    }  
}

테스트 코드를 LottoNumberTest로 옮긴 모습이다. 이 테스트 코드의 범위_밖_로또넘버_예외처리_테스트를 만족하는 LottoNumberDTOvalidateNumberRange를 구현했다.

테스트 코드를 먼저 작성하려고 하니
1. 어떤 클래스에 "범위 예외처리" 기능을 어떤 클래스에 할당해야 하는 지 고민하게 된다.
2. 실패하는 테스트 코드를 적으니 할당된 클래스에서 기능을 구현하지 않을 수 없다.

profile
개발자

0개의 댓글