JAVA

우아한테크코스 6기[BE] 3주차 피드백

hyukji 2023. 11. 9. 23:52

3주 차 공통 피드백

함수(메서드) 라인에 대한 기준

함수 하나 당 15라인을 넘지 않는다. 이는 공백도 포함한다.

 

발생할 수 있는 예외 상황에 대해 고민한다

예외 상황을 고려해 프로그래밍하는 습관을 들인다.

  • 로또 구입 금액에 1000 이하의 숫자를 입력
  • 당첨 번호에 중복된 숫자를 입력
  • 당첨 번호에 1~45 범위를 벗어나는 숫자를 입력
  • 당첨 번호와 중복된 보너스 번호를 입력

 

비즈니스 로직과 UI 로직을 분리한다

비즈니스 로직과 UI 로직을 한 클래스가 담당하지 않도록 한다. 단일 책임의 원칙에도 위배된다.

현재 객체의 상태를 보기 위한 로그 메시지 성격이 강하다면 toString()을 통해 구현한다.
View에서 사용할 데이터라면 getter 메서드를 통해 데이터를 전달한다.

 

연관성이 있는 상수는 static final 대신 enum을 활용한다

Constant 클래스 안에 static final로 사용되는 상수들을 모아두었는데 enum을 이용해서
연관성이 있는 상수들을 모아두려고 한다.

 

final 키워드를 사용해 값의 변경을 막는다

자바는 final 키워드를 활용해 값의 변경을 막을 수 있다.

 

객체의 상태 접근을 제한한다

인스턴스 변수의 접근 제어자는 private으로 구현한다.

 

객체는 객체스럽게 사용한다

Lotto 클래스는 numbers를 상태 값으로 가지는 객체이다.
Lotto에서 데이터를 꺼내지(get) 말고 메시지를 던지도록 구조를 바꿔 데이터를 가지는 객체가 일하도록 한다.

 

필드(인스턴스 변수)의 수를 줄이기 위해 노력한다

필드(인스턴스 변수)의 수가 많은 것은 객체의 복잡도를 높이고, 버그 발생 가능성을 높일 수 있다.
필드에 중복이 있거나, 불필요한 필드가 없는지 확인해 필드의 수를 최소화한다.

의외로 놓치기 쉬운 부분이라고 생각한다. 필드 수 줄이는 것에 좀더 신경써야 겠다.

 

성공하는 케이스 뿐만 아니라 예외에 대한 케이스도 테스트한다

테스트를 작성하면 성공하는 케이스에 대해서만 고민하는 경우가 있다.
하지만 예외에 대한 부분 또한 처리해야 한다.
특히 프로그램에서 결함이 자주 발생하는 부분 중 하나는 경계값이므로 이 부분을 꼼꼼하게 확인해야 한다.

 

테스트 코드도 코드다

테스트 코드도 코드이므로 리팩터링을 통해 개선해 나가야 한다.
특히 반복적으로 하는 부분을 중복되지 않게 만들어야 한다.
예를 들어 단순히 파라미터의 값만 바뀌는 경우라면 아래와 같이 테스트할 수 있다.

테스트 코드라고 간단히 넘기는 경우들도 있었는 데 코드라고 생각하고 작성하려고 한다.

 

테스트를 위한 코드는 구현 코드에서 분리되어야 한다

테스트를 위한 편의 메서드를 구현 코드에 구현하지 마라.
아래의 예시처럼 테스트를 통과하기 위해 구현 코드를 변경하거나 테스트에서만 사용되는 로직을 만들지 않는다.

  • 테스트를 위해 접근 제어자를 바꾸는 경우
  • 테스트 코드에서만 사용되는 메서드

테스트를 위해서 프로덕트 코드를 건드리면 안된다!

 

private 함수를 테스트 하고 싶다면 클래스(객체) 분리를 고려한다

public 함수가 private 함수를 사용하고 있기 때문에 자연스럽게 테스트 범위에 포함된다.
하지만 가독성 이상의 역할을 하는 경우, 테스트하기 쉽게 구현하기 위해서는 해당 역할을 수행하는
다른 객체를 만들 타이밍이 아닐지 고민해 볼 수 있다.
다음 단계를 진행할 때에는 너무 많은 역할을 하고 있는 함수나 객체를
어떻게 의미 있는 단위로 분할할지에 초점을 맞춰 진행한다.

3주차에서는 없었지만, 2주차에서 이런 경험을 한 적이 있다. _
_그 당시에는 클래스를 분리하지 못하고 작성했지만, 4주차에서는 분리하는 연습을 더 해보려고 한다.

 

단위 테스트하기 어려운 코드를 단위 테스트하기

아래 코드는 Random 때문에 Lotto에 대한 단위 테스트를 하기 힘들다.
단위 테스트가 가능하도록 리팩터링한다면 어떻게 하는 것이 좋을까?

import camp.nextstep.edu.missionutils.Randoms;

public class Lotto {

    private List<Integer> numbers;

    public Lotto() {
        this.numbers = Randoms.pickUniqueNumbersInRange(1, 45, 6);
    }

}

——————

public class LottoMachine {

    public void execute() {
        Lotto lotto = new Lotto();
    }
}

올바른 로또 번호가 생성되는 것을 테스트하기 어렵다. 테스트하기 어려운 것을 클래스 내부가 아닌 외부로 분리하는 시도를 해 본다.


public class Lotto {

    private List<Integer> numbers;

    public Lotto(List<Integer> numbers) {
        this.numbers = numbers;
    }

}

——————

import camp.nextstep.edu.missionutils.Randoms;

public class LottoMachine {

    public void execute() {
        List<Integer> numbers = Randoms
            .pickUniqueNumbersInRange(1, 45, 6);
        Lotto lotto = new Lotto(numbers);
    }
}

 

위 코드는 A 상황을 B로 바꾼 것이다.

(참고. 메서드 시그니처를 수정하여 테스트하기 좋은 메서드로 만들기)

 

이처럼 단위 테스트를 할 때 테스트하기 어려운 부분은 분리하고 테스트 가능한 부분을 단위 테스트한다.
테스트하기 어려운 부분은 단위 테스트하지 않아도 된다. 남은
LottoMachine은 어떻게 테스트하기 쉽게 바꿀 수 있을지 고민해 본다.

 

2주차에서 가졌던 의문이다. 3주차에서는 랜덤과 관련해서 LottoShop을 만들면서 필요성이 없었지만, 앞으로 이런 경우 클래스 분리를 고려하려고 한다.