로또 미션 리뷰 정리

주리링·2022년 3월 7일
0

우테코 생존기

목록 보기
4/17
post-thumbnail
post-custom-banner

미래 예측적인 코드는 불필요할지도?

1단계 미션을 진행하며, 로또를 만드는 역할을 하는 LottoFactory 라는 클래스에서 로또가 생성되는지 확인하는 테스트 1개를 진행했었다.

그런데 추후에 다른 테스트를 진행할 수 있을 것 같아서 LottoFactory 를 전역변수로 선언했었다.

public class LottoFactoryTest {

	private LottoFactory lottoFactory;

//test 1개

}

여기에 루피가 책의 내용을 인용하여 리뷰를 주셨는데, 내용은 아래와 같다.

개발자는 "코드는 나중에 정리하면 돼. 당장은 시장에 출시하는게 먼저야!" 라는 흔해 빠진 거짓말에 속는다. 이렇게 속아 넘어간 개발자라면 나중에 코드를 정리하는 경우는 한 번도 없는데, 시장의 압박은 절대로 수그러들지 않기 때문이다. '시장 출시가 먼저'라는 생각을 하는 이유는 바로 뒤에 여러 무리의 경쟁자가 뒤쫓고 있고, 경쟁자보다 앞서가려면 가능한 한 빠르게 달려야 하기 때문이다. - 클린 아키텍쳐 11

루피 : 비즈니스 변화 속도가 빠르다면 코드를 작성하는 시점에 미래를 예측한 코드를 작성한다고 해도 미래의 어느 시점이 오면 예측하여 작성했던 코드도 의미가 없어질 가능성이 높아질 것 같다. 구현과 설계를 구분하여, 미래에 생길 변화 혹은 필요성에 대해서는 구현보다 설계로 대응하려고 하는편.

내가 이해한대로 요약하자면, 현실은 예측이 불가능한데, 아무리 미래를 예측하는 코드를 작성해도 나의 예상대로 현실이 흘러가지 않을 수 있다.

따라서 예측가능하다고 착각하는 코드보다는 현재 시점의 비지니스를 표현할 수 있는 코드들만 작성하자.

test코드에서 expect값은 명확하게 표시하기

void generateLottoByMoney() {
		String purchaseMoney = "14000";
		Money money = Money.from(purchaseMoney);

		LottoFactory lottoFactory = new LottoFactory();
		assertThat(lottoFactory.generateLottoTicket(money).size()).isEqualTo(Integer.parseInt(purchaseMoney) / 1000);
	}

then을 확인하는 줄에서 isEqualTo 메소드 내부에 Integer.parseInt(purchaseMoney) / 1000 이렇게 한눈에 볼 수 있는 값이 아닌 계산해야하는 값을 넣었다.

이렇게 하면 비교해야하는 값을 바로 알기 어렵다.
main 코드 뿐만 아니라, test코드를 내가 아닌 다른 사람들도 쉽게 알아 볼 수 있도록 작성하는 것도 명심하자! 🙂

경계값 테스트 하기

아래와 같은 부분을 test할 때였는데, 로또 숫자가 6개인지 확인하는 테스트를 진행하는데 터무니없이 개수가 4개 막 이렇게 작성했던 적이 있었다...

@Test
	@DisplayName("입력한 숫자의 수가 7개인 경우")
	public void checkManualLottoSize_1() {
		//given
		int autoLottoCount = 0;
		List<List<Integer>> inputManualLotto = Collections.singletonList(
			Arrays.asList(8, 21, 23, 37, 41, 42, 45)
		);
		//when and then
		assertThatThrownBy(() -> lottoFactory.generateLottos(autoLottoCount, inputManualLotto))
			.isInstanceOf(IllegalArgumentException.class)
			.hasMessage("로또 번호는 6개의 숫자여야 합니다");
	}

validate할 때는 항상 무엇을 검사해야하는지 생각하고 그에 대한 경계값을 테스트하자!

방어적 복사와 Unmodifiable Collection 대신 Collection.copyOf

public class Lotto {

	private final List<Integer> lotto;

	public Lotto(final List<Integer> lotto) {
		this.lotto = new ArrayList<>(lotto);
	}

	public List<LottoNumber> getLotto() {
		return Collections.unmodifiableList(lotto);
	}

}

일급 컬렉션 보호를 위해 방어적 복사와 Unmodifiable Collection 모두 사용한다면 생성자에서 Collection.copyOf를 사용하자

public class Lotto {

	private final List<Integer> lotto;

	public Lotto(final List<Integer> lotto) {
		this.lotto = List.copyOf(lotto);
	}

	public List<LottoNumber> getLotto() {
		return lotto;
	}

}

copyOf의 기능

  1. 복사할 컬렉션이 수정 가능한 경우, copyOf 메서드는 원본의 복사본을 이용하여 수정할 수 없는 컬렉션을 생성한다.
    그래서 원본 컬렉션의 요소가 추가되거나 제거되더라도 사본에는 영향을 미치지 않는다.
    그리고 Immutable Collections을 반환하므로 getter에서 반환해서 다른 객체에서 사용되더라도 Collection에 추가되거나 삭제되는 수정을 허용하지 않는다.
  2. 원본 컬렉션을 수정할 수 없는 경우, copyOf 메서드는 원본 컬렉션에 대한 참조만 반환한다.
    그 이유는, 복사본을 만드는 목적은 반환된 컬렉션을 원본 컬렉션의 변경 사항에서 분리하는 것이기 때문이다.
    그러므로 원본 컬렉션을 변경할 수 없다면 복사본을 만들 필요가 없다.

copyOf를 사용할 때 주의해야하는 점

  1. 파라미터 값으로 받는 Collection은 반드시 null이 아니여야하며, 참조값 또한 null이 아니여야한다.
  2. Set.copyOf를 사용할 때 중복 요소가 포함된 경우, 예외가 발생하지 않는다.
profile
코딩하는 감자
post-custom-banner

0개의 댓글