각종 후기/우아한테크코스

[우아한 테크코스 3기] LEVEL 1 회고 - 로또 2단계 미션의 2차 피드백을 받아보다 (28일차)

제이온 (Jayon) 2021. 3. 1.

안녕하세요? 제이온입니다.

 

오늘은 삼일절로 우테코 정규 수업은 없었지만, 미션은 자유롭게 수행할 수 있었습니다.

 

 

로또 2단계 미션 2차 피드백 요청하기 전 수정 사항

제가 저번 포스팅에서 가장 고민한 점이 티켓을 구매하는 방식이었습니다. 자동 구매와 수동 구매에 따른 로또의 생성 로직이 달랐지만, '구매'라는 방식에는 동일해서 추상화하려고 노력해 보았습니다.

 

하지만, 수동 구매는 클라이언트와의 소통이 있어야 해서 결국 이상적인 추상화는 실패하였고, 티켓 객체 안에 구매 가능한 전체 티켓의 수와 수동 구매한 티켓의 수를 저장해 놓기로 하였습니다.

 

 

package lotto.domain;

public class Ticket {

    public static final int TICKET_PRICE = 1000;
    public static final String TICKET_MINIMUM_PRICE_ERROR_MESSAGE = "돈은 %d원 이상이어야 합니다.";
    public static final String MANUAL_BUY_ERROR_MESSAGE = "수동 구매는 %d장보다 더 많이 살 수 없습니다.";
    private final int totalCount;
    private int manualCount;

    public Ticket(final Money money) {
        validateMinimumTicketPrice(money);
        this.totalCount = money.getValue() / TICKET_PRICE;
    }

    public Ticket(final Money money, final int manualCount) {
        this(money);
        validateMaximumManualBuy(manualCount);
        this.manualCount = manualCount;
    }

    private void validateMinimumTicketPrice(final Money money) {
        if (money.getValue() < TICKET_PRICE) {
            throw new IllegalArgumentException(
                String.format(TICKET_MINIMUM_PRICE_ERROR_MESSAGE, TICKET_PRICE));
        }
    }

    private void validateMaximumManualBuy(final int manualCount) {
        if (manualCount > totalCount) {
            throw new IllegalArgumentException(
                String.format(MANUAL_BUY_ERROR_MESSAGE, totalCount));
        }
    }

    public int getTotalCount() {
        return totalCount;
    }

    public int getManualCount() {
        return manualCount;
    }

    public int getAutoCount() {
        return totalCount - manualCount;
    }

}

 

 

위와 같이 생성자 단계에서 자동 구매만 하거나 수동 구매를 하도록 설정해 두었습니다. 물론, 유효성 검사도 알맞게 설계해 두었죠. 이렇게 하니까 제가 완전 원하는 구조는 아니었지만, 어느 정도 수지 타산에 맞는 코드라고 생각이 들었습니다. 컨트롤러의 코드도 한결 깔끔해졌고, 반복된 코드가 딱히 보이지는 않았습니다.

 

그래서 이대로 PR을 보내고, 데이브에게 리뷰를 신청하였습니다.

 

 

2차 피드백

피드백 요청은 어젯밤에 하였고, 오늘 오후 2시 경에 2차 피드백이 완료되었다는 메시지를 받았습니다.

 

 

 

 

첫 번째 피드백은 메소드명 수정입니다. 위 코드를 보면, 단순히 lottos 리스트에 lotto를 추가만 하고 있는데, 메소드명은 로또 '생성'이기 때문이죠. 따라서 add 정도로 수정하였습니다.

 

 

 

 

두 번째 피드백은 인스턴스를 미리 만들지 말라는 것입니다. 따라서, 해당 메소드 내에서 인스턴스를 선언한 후, 그것을 반환하는 방식으로 수정하였습니다.

 

 

 

 

세 번째 피드백은 객체의 속성을 함부로 변경할 수 없도록 설정하라는 것이었습니다. Lotteries는 멤버 변수로 List<Lotto> lottos를 갖고 있고, generateLottoByTicket과 generateLottoByManual을 통해 lottos의 요소를 조작할 수 있습니다. 하지만, 프로그램이 돌아가면서 구매 행위는 각각 한 번씩만 일어나므로 이를 생성자로 떠넘김으로써 향후에 추가적인 데이터 변경을 없애는 것이 더 좋죠. 따라서, 아래와 같이 수정하였습니다.

 

 

public class Lotteries {

    private final List<Lotto> lottos = new ArrayList<>();

    public Lotteries(final List<Lotto> lottos) {
        addLottoByManual(lottos);
    }

    public Lotteries(final LottoMachine lottoMachine, final int ticketCount) {
        addLottoByTicket(lottoMachine, ticketCount);
    }

    public Lotteries(final List<Lotto> lottos, final LottoMachine lottoMachine,
        final int ticketCount) {
        addLottoByManual(lottos);
        addLottoByTicket(lottoMachine, ticketCount);
    }

    private void addLottoByTicket(final LottoMachine lottoMachine, final int ticketCount) {
        for (int i = 0; i < ticketCount; i++) {
            lottos.add(Lotto.from(lottoMachine.generate()));
        }
    }

    private void addLottoByManual(final List<Lotto> lottos) {
        this.lottos.addAll(lottos);
    }

    public RatingInfo scratchLotto(final WinningLotto winningLotto) {
        final RatingInfo ratingInfo = new RatingInfo();
        for (final Lotto lotto : lottos) {
            final int match = winningLotto.compareLottoNumber(lotto);
            final boolean hasBonusBall = winningLotto.compareBonusBall(lotto);
            ratingInfo.update(Rating.getRating(match, hasBonusBall));
        }
        return ratingInfo;
    }

    public List<Lotto> toList() {
        return Collections.unmodifiableList(lottos);
    }

}

 

 

아까 언급 두 메소드는 private으로 변경하고, 생성자 단계에서 lottos 데이터의 조작이 일어나도록 설계하였습니다. 하지만, 제가 구체적으로 어디서 본 것인지 기억은 나지 않는데, 생성자는 최대한 단순하게 작성하라는 글을 본 적이 있습니다. 또한, 제가 작성한 생성자는 흔히 사용하는 필드 값을 초기화하는 데 사용하지도 않습니다. 그래서 다른 개발자가 사용할 때 혼란을 줄 여지가 있다고 판단하였습니다.

 

하지만, 이 부분은 어떻게 변경하는 것이 좋을지 몰라서 데이브에게 코멘트를 남겨두었습니다.

 

 

 

 

마지막으로는 사용자가 입력한 로또 번호를 한 번에 받고, LottoController에서 Lotto를 생성하는 것이 아니라 Lotteries 내부에서 Lotto를 생성하도록 바꾸라는 것입니다. 저는 처음에 List<List<Integer>> numbers 구조로 코드를 작성하였으나, 'List<List<Integer>>'은 가독성을 해친다는 생각이 들었습니다. 그래서 데이브에게 DM을 보내보니까, 'List<String>'으로 작성한 다음에 데이터 타입을 가공하라는 조언을 받았습니다.

 

이때, 사용자한테 문자열을 그대로 입력받고난 다음에 한 번의 입력을 검사해 주려면 문제가 하나 발생합니다. 저는 LottoNumber와 Lotto 객체를 생성할 때에 대해서 예외 처리를 해 놓았는데, 위처럼 문자열을 한 번에 통으로 받아서 Lotteries로 넘기기에는 문제가 있었습니다.

 

그래서 데이브의 피드백을 완전히 수용하지는 못 하였고, 한 번의 입력 받아서 Lotteries에 넘긴다는 부분만 반영을 하였습니다. 이 내용은 데이브와 다시 이야기해 보려고 합니다.

 

 

정리

오늘은 정보처리산업기사를 공부하고, 로또 미션 피드백을 반영하는 데 시간을 보냈습니다. 이번주 토요일이 정보처리산업기사 필기 시험인데, 짬날 때 계속 공부해서 꼭 붙어야겠습니다.

 

그리고 내일부터 우테코 3기 오프라인 수업이 진행되는데, 저는 잠실새내 주변에 잡아 놓은 방이 3월 13일까지 대기해야해서 부득이하게 2주간 참석은 못하게 되었습니다 ㅠㅠ. 빨리 2주가 지나서 저도 크루들과 같이 학습을 할 수 있었으면 좋겠습니다.

댓글

추천 글