[우아한테크코스 6기] 3주차 미션 회고 - 로또(feat. EnumMap)

Jinny·2023년 11월 7일
2

💬 들어가며

이번주 미션은 로또 게임이다.
내가 작성한 코드와 PR은 밑에 링크에서 확인할 수 있다.

메일에 보면 이번주 미션의 목표는 "클래스 분리"와 "단위 테스트 작성"이었다.
이전에도 로또 미션은 한번 구현해보았는데, 오히려 저번보다 이번에 머리가 더 아팠다.

이전 2번의 미션에 비해 이번 미션은 더 잘할 것이라고 생각했는데 코드를 짜면서 절망의 계곡에 와버렸다,,
객체지향은... 알면 알수록 어렵다.

각설하고 회고를 시작해보겠다.




🔍 공부한 내용과 고민들

원시값 랩핑

클래스의 책임 분리를 더 잘해보기 위한 방법을 찾아보다가 "원시값 랩핑"에 대한 키워드가 많이 보였다.

그래서 로또 숫자 하나를 의미하는 Integer를 랩핑해
우테코에서 제공한 Lotto 클래스의 List<Integer>List<LottoNumber>로 변경하였다.

원시값을 포장하기 전에는 LottoList의 각 요소들이 1~45 사이의 숫자인지 검증하고, 길이가 6인지도 검증하고 등등 유효성 검증을 많이 해야 했다.

원시값을 포장하니 LottoNumber가 일부 유효성 검증을 책임지게 되면서 확실히 책임이 분리되는 것을 느꼈다.

단점 아닌 단점(?)으로는 원시값이 포장되었다 보니 값을 출력하기 위해 toString()OverRiding해야 했다.
아니면 getter를 사용하면 되는데 출력을 위한 메서드로는 getter 보다는 toString()를 더 깔끔하다고 느껴졌다.

LottoNumber.class

public record LottoNumber(int value) {

    public LottoNumber {
        // 생략
    }
    
    @Override
    public String toString() {
        return String.valueOf(value);
    }
}

일급 컬렉션

또한 클래스 분리를 위해 일급 컬렉션을 활용했다.
원시값을 랩핑하고 일급 컬렉션을 활용하다보니 다음과 같은 구조가 되었다.

  • LottoNumber: 1~45 사이의 숫자를 랩핑한 클래스(숫자 범위 검증 등 담당)
  • Lotto: List<LottoNumber>를 랩핑한 클래스(중복 숫자 검증 등 담당)
  • Lottos: List<Lotto>를 랩핑한 클래스

그런데 Lottos가 갑자기 붕 떠버렸다.
Lottos는 단순히 컬렉션을 랩핑하고, 내부에는 별다른 로직이 없다.

만약 고객이 구매할 수 있는 로또 장수에 제한이 있는 등 제약사항이 있다면 Lottos도 도메인 로직이 생기겠지만,
현재 상태에서는 제약사항이 없기에 오버프로그래밍한 것은 아닌지 고민이 되었다.

계속 고민을 하다가 일급 컬렉션의 장점 중 하나는 “컬렉션에 이름을 붙일 수 있다는 것”이라고 생각이 들었고 오버프로그래밍이라면 이로부터 파생될 수 있는 문제점도 직접 느껴보는 것이 좋을 것 같아서
일단은 해당 클래스를 유지하는 것으로 결론을 내렸다.

메서드 책임에 대한 고민

수익률을 계산하는 책임은 누구한테 있는건데...?

처음에는 Service 없이 Controller에서 도메인 호출만 해서 프로그램을 구현했다.
그런데 그러다보니 누구에게도 속하기 애매한 로직이 생겼다. 바로 수익률을 계산하는 로직이다.

특히 이 두개 클래스 중 어디에 속해야 하는 로직인지 고민을 엄청 많이 했다.

  • Money: "구입 금액"을 책임지는 도메인
  • LottoResult: "당첨 결과"를 책임지는 도메인

Money는 구입 금액을 알고 있고, LottoResult는 당첨 금액에 대해 알고 있다.
수익률을 계산하려면 이 두 금액에 대해 모두 알고 있어야 하는데... getter로 둘다 꺼내와서 외부에서 계산해야 하나 고민하다가 이 두 객체를 필드로 갖고 있는 Profit이라는 객체를 만들었다.

두 개의 객체 어디에도 속하기 애매한 로직인것 같고,
그렇다고 굳이 객체를 만들어야 하나 싶기도 하고...
그렇다고 ControllerServicegetter로 값을 꺼내서 직접 계산하기에는 도메인 로직이 외부에 있고! 🤯
내가 느끼기에는 어디에 위치하기가 참 애매한 로직이었다.

고민했던 방법 중에 그나마 제일 났다고 판단한 방법은 그나마 Profit 객체를 만드는 것이었다.
(다른 더 좋은 방법은 없는지 미션 제출 시점까지, 끝까지 고민했던 부분인데 프리코스 끝나고 다시 찬찬히 봐야겠다.)

EnumMap과 저장순서

로또 당첨 정보 관련 정보들을 모아 Enum으로 구현했다.

Rank.class

public enum Rank {

    FIFTH(3, false, 5_000),
    FOURTH(4, false, 50_000),
    THIRD(5, false, 1_500_000),
    SECOND(5, true, 30_000_000),
    FIRST(6, false, 2_000_000_000),
    NO_RANK(0, false, 0);

    public final int matchedCount;
    public final boolean matchesBonusNumber;
    public final int prize;

    Rank(int matchedCount, boolean matchesBonusNumber, int prize) {
        this.matchedCount = matchedCount;
        this.matchesBonusNumber = matchesBonusNumber;
        this.prize = prize;
    }
    
    // 이하 생략
}

해당 정보를 view에서 다음과 같이 출력하기 위해 당첨 정보를 LottoResult 클래스에 EnumMap을 활용하여 저장했다.

그리고 출력 시 사용하기 위해 Collections.unmodifiableMap()에 감싸서 값을 넘겨주고 있었는데
순서 보장에 대한 의문이 들었다.

출력 예시

3개 일치 (5,000원) - 1개
4개 일치 (50,000원) - 0개
5개 일치 (1,500,000원) - 0개
5개 일치, 보너스 볼 일치 (30,000,000원) - 0개
6개 일치 (2,000,000,000원) - 0개
총 수익률은 62.5%입니다.

LottoResult.class

public class LottoResult {

    private final Map<Rank, Long> results = new EnumMap<>(Rank.class);

    public LottoResult(Map<Rank, Long> results) {
        // 생략
    }

    public Map<Rank, Long> getResults() {
        return Collections.unmodifiableMap(results);
    }

    // 생략
}

EnumMap의 저장 순서

EnumMapEnum에 정의된 순서로 저장된다.
그런데 Collections.unmodifiableMap()으로 감싸도 순서가 보장이 될까?

출력할 때마다 순서가 바뀐다면 LinkedHashMap과 같은 다른 대안을 생각해야 되니까
얼릉 내부 코드를 살펴보자.

두번째의 private 메서드를 살펴보면 UnmodifiableMap이라는 구현체를 반환할 때 인자로 받는 Map을 참조하고 있다.

그리고 스크린샷의 1504 라인쪽 메서드를 보면 삭제하거나 추가하는 등
기존 Map을 수정하지 못하도록 막고 있는 것을 확인할 수 있다.

즉 원본 객체인 EnumMap을 그대로 참조하고 있기 때문에 순서가 보장된다.!
(이 말은 반대로 하면 원본 데이터가 바뀌면 참조하는 데이터도 순서가 바뀐다는 뜻이다.)




👋 마치며

미션 구현하면서 엄청 많은 고민들을 했는데, 이렇게 또 회고하면서 정리해보니 별로 많게 안 느껴지도 하는 것이...
코딩 능력과 더불어 글 쓰기 능력도 키워야겠다는 생각이 든다.

어느새 날씨가 쌀쌀해졌고, 프리코스도 절반 이상을 지나 이제 1개의 미션만 남겨두고 있다.
마지막까지 후회없이 최선을 다하자.

profile
공부는 마라톤이다. 한꺼번에 많은 것을 하다 지치지 말고 조금씩, 꾸준히, 자주하자.

1개의 댓글

comment-user-thumbnail
2023년 11월 10일

글 유잼 ㅎ

답글 달기