[2주차] 로또
리뷰를 폭탄으로 맞았다 🤣🤣 오히려 좋다.. 배울게 너무 많아서 😬😬
이번 2주 차 미션을 진행하면서 새롭게 배운 것들이 많다.
1. 멀티 스레드 환경에서 상태 공유
바로 찾아보았다. 학교 운영체재 시간에 배운 동시성 이슈라고 생각했다.
final로 완전한 불변이 안 만들어져서 아예 내부 상태를 갖지 않도록 하는 게 좋은 것 같다!
당연한 거지만 자주 까먹는 것 같다.🤣 이번에는 기억해 두자!!
2. 방어적 복사
핵심은 객체 내부의 값을 외부로부터 보호하는 것이라는 것을 유념하자.
생성자의 인자로 객체를 받았을 때
외부에서 넘겨줬던 객체를 변경해도 내부의 객체는 변하지 않아야 한다.
따라서 방어적 복사가 적절하다.
getter를 통해 객체를 리턴할 때
이 상황에선 방어적 복사를 통해 복사본을 반환해도 좋고, Unmodifiable Collection을 이용한 값을 반환하는 것도 좋다.
출처에 남겼는데, 3기 파피께서 써주신 글이 너무 잘 읽혀서 이해가 쏙쏙 되었다..!😯
3. Enum Map
HashMap의 경우 일정한 이상의 자료가 저장되면, 자체적으로 resizing을 합니다. 하지만 EnumMap은 시작부터 데이터의 사이즈가 enum으로 제한되기 때문에 문제가 발생할 수 없습니다.
Enum 타입의 키를 사용할때는 성능 및 기능상 이점이 많은 EnumMap 을 사용하자!
4. VO (Value Object)
스프링으로 자바를 시작해서 그런지 VO와 @Embeded 의 값 타입과 무엇이 다른지 이해가 안 갔다. 여러 번의 구글링을 통해 금방 찾았다. VO를 jpa에서 사용할 땐 @Embedded를 이용해 값 타입으로 정의한다는 것이었다. 즉 VO는 값 타입이라고 생각해도 될 것 같다.
1. equals & hash code
VO는 말 그대로 값 객체이다. 타입과 내부 속성이 같다면 같은 객체로 취급하는 게 객체지향적으로도 맞기 때문에 VO는 equals & hashCode를 정의해줘야 한다. 즉, 동일성과 동등성을 보장해줘야 한다.
2. Setter가 없는 불변 객체여야 한다.
Entity가 식별자로 구분을 하지만 VO는 구별할 식별자가 따로 없이 객체 자신으로 구별해야 한다. 즉, VO의 값이 바뀌면 다른 값이 되어 추적이 불가하고, 복사될 때는 의도치 않은 객체들이 함께 변경되는 문제를 유발한다.
이러한 VO의 성질을 두어 불변한 상태를 만들도록 노력해야 겠다.
5. Collectors.toMap()
- 처음 인자는 key값을 어떻게 바꿀 것인지 넣어준다.
- 다음은 Map.Entry::getValue로 원형 값 그대로 넣어준다.
- key가 중복일 때 어떻게 처리하냐인데, 애초에 Map을 이용해서 중복될 일이 없으므로 그대로 key를 반환한다.
- LinkedHashMap으로 Map을 구성한다.
6. collectingAndThen
public static StatisticDto from(Statistic statistics, int size) {
return statistics.getStatistics().entrySet()
.stream()
.collect(Collectors.collectingAndThen(Collectors.toMap(
statistic -> RankDto.from(statistic.getKey()),
Map.Entry::getValue,
(key, value) -> key,
LinkedHashMap::new
// 인자가 두개이면 람다식으로 적어줄수도 있다.
), o -> new StatisticDto(o, size))
);
}
public static StatisticDto from(Statistic statistics) {
return statistics.getStatistics().entrySet()
.stream()
.collect(Collectors.collectingAndThen(Collectors.toMap(
statistic -> RankDto.from(statistic.getKey()),
Map.Entry::getValue,
(key, value) -> key,
LinkedHashMap::new
), StatisticDto::new)
);
}
아래 사진처럼 Map을 하고 다시 return new로 Dto로 변환할 수도 있지만(가독성을 위해서) 위에 처럼 한 번에 변환까지 할 수도 있다.
7. @ParameterizedTest
- @ValueSource : 인자를 하나만 갖는 테스트를 하고 싶을 때 사용하면 좋다.
- @MethodSource : 보다 복잡한 인수를 테스트할 때, 사용하면 좋다. ex) List, 객체들
@Test
@DisplayName("6개 일치 -> 1등 당첨 테스트")
public void checkFirstWinTest() {
Lotto allMatchLotto = new Lotto(LottoNumberGenerator.of(1, 2, 3, 4, 5, 6));
Rank rank = winningLotto.match(allMatchLotto);
assertThat(rank).isEqualTo(Rank.FIRST);
}
@Test
@DisplayName("5개 일치, 보너스 볼 일치 -> 2등 당첨 테스트")
public void checkBonusBallMatchSecondWinTest() {
Lotto fiveMatchLotto = new Lotto(LottoNumberGenerator.of(1, 2, 3, 4, 5, 7));
Rank rank = winningLotto.match(fiveMatchLotto);
assertThat(rank).isEqualTo(Rank.SECOND);
@Test
@DisplayName("5개일치, 보너스 볼 불일치 -> 3등 당첨 테스트")
void checkThirdWinTest() {
Lotto fiveMatchLotto = new Lotto(LottoNumberGenerator.of(1, 2, 3, 4, 5, 44));
Rank rank = winningLotto.match(fiveMatchLotto);
assertThat(rank).isEqualTo(Rank.THIRD);
}
@Test
@DisplayName("4개 일치 -> 4등 당첨 테스트")
void checkFourthWinTest() {
Lotto fourMatchLotto = new Lotto(LottoNumberGenerator.of(1, 2, 3, 4, 43, 44));
Rank rank = winningLotto.match(fourMatchLotto);
assertThat(rank).isEqualTo(Rank.FOURTH);
}
@Test
@DisplayName("3개 일치 -> 5등 당첨 테스트")
void checkFifthWinTest() {
Lotto threeMatchLotto = new Lotto(LottoNumberGenerator.of(1, 2, 3, 42, 43, 44));
Rank rank = winningLotto.match(threeMatchLotto);
assertThat(rank).isEqualTo(Rank.FIFTH);
}
@Test
@DisplayName("꽝 테스트")
void checkNoWinTest() {
Lotto noMatchLotto = new Lotto(LottoNumberGenerator.of(1, 2, 41, 42, 43, 44));
Rank rank = winningLotto.match(noMatchLotto);
assertThat(rank).isEqualTo(Rank.SIXTH);
이렇게 긴 코드를 @ParameterizedTest, @MethodSource("methodName") 를 사용하면 아래처럼 줄어든다.
@MethodSource에 매서드 인자를 이름으로 넣어주고 static Stream를 반환하도록 매서드를 만들어주면 된다.
MethodSource에 매서드 이름을 인자로 넘기면 해당 매서드의 파라미터들을 대입시켜준다.
8. Dto
- dto위치는 의존하는 가장 낮은 레이어에 위치가 좋다고 한다!
- dto에 로직을 한 번 추가하다 보면 계속 추가될 수 있으므로 가벼운 로직이라도 추가하지 말자!
리뷰어: 핀!
많은 리뷰들을 남겨주셨고 덕분에 구조와 의존성 등 많은 부분을 생각해볼 수 있었습니다! 감사합니다🙏
mvc 구조로 첫 pr을 남겼는데 리뷰를 받고 진행하고 보니 `인터페이스`, `dto`, `service` 까지 생기면서 변화에 탄탄이 대응할 수 있는 구조까지 된 것 같아요.. 리뷰의 힘이란..😇
핀의 리뷰로 호되게 맞은 덕분에 많은 성장을 한 것 같습니다~.~ 이번 미션으로 배운 모든 것을 체득하도록 꾸준히 복습해야겠네요!
PR
- 1단계 : https://github.com/woowacourse/java-lotto/pull/334
- 2단계 : https://github.com/woowacourse/java-lotto/pull/444
리뷰 공유 스터디
깃 : https://github.com/woowacourse-study/2022-back-end-code-review-study
Reference
- https://www.manty.co.kr/bbs/detail/develop?id=61
- https://tecoble.techcourse.co.kr/post/2020-06-11-value-object
- https://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests