[우아한 테크 코스 프리코스 5기] 3주차

seheo·2022년 11월 25일
0

우테코

목록 보기
3/3

로또 게임

🚀 기능 요구 사항

로또 게임 기능을 구현해야 한다. 로또 게임은 아래와 같은 규칙으로 진행된다.

  • 로또 번호의 숫자 범위는 1~45까지이다.
  • 1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑는다.
  • 당첨 번호 추첨 시 중복되지 않는 숫자 6개와 보너스 번호 1개를 뽑는다.
  • 당첨은 1등부터 5등까지 있다. 당첨 기준과 금액은 아래와 같다.
    - 1등: 6개 번호 일치 / 2,000,000,000원
    - 2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원
    - 3등: 5개 번호 일치 / 1,500,000원
    - 4등: 4개 번호 일치 / 50,000원
    - 5등: 3개 번호 일치 / 5,000원
    로또 구입 금액을 입력하면 구입 금액에 해당하는 만큼 로또를 발행해야 한다.
    로또 1장의 가격은 1,000원이다.
    당첨 번호와 보너스 번호를 입력받는다.
    사용자가 구매한 로또 번호와 당첨 번호를 비교하여 당첨 내역 및 수익률을 출력하고 로또 게임을 종료한다.
    사용자가 잘못된 값을 입력할 경우 IllegalArgumentException를 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 종료한다.

추가된 요구 사항

  1. 함수(또는 메서드)의 길이가 15라인을 넘어가지 않도록 구현한다.
  2. 함수(또는 메서드)가 한 가지 일만 잘 하도록 구현한다.
  3. else 예약어를 쓰지 않는다.
    • 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다.
    • else를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다.
  4. Java Enum을 적용한다.
  5. 도메인 로직에 단위 테스트를 구현해야 한다. 단, UI(System.out, System.in, Scanner) 로직은 제외한다.
    • 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 분리해 구현한다.
    • 단위 테스트 작성이 익숙하지 않다면 test/java/lotto/LottoTest를 참고하여 학습한 후 테스트를 구현한다.

기능 분석

기능 목록

  • 로또 자판기

    • 중복 없이 로또 6자리 생성
    • 지불 금액에 맞게 생성
    • 구매 가능한 로또 수량 계산
    • 오름차순으로 정렬하여 생성
  • 구매 가능한 로또 수량 출력

  • 구매한 로또 번호를 저장한다.

  • 구매한 로또 번호 출력

  • 당첨 내역 계산

    • 당첨 번호와 비교 계산
    • 보너스 번호 비교
    • 상금 계산기
    • 수익률 계산
  • 당첨 내역 출력

  • 수익률 출력

  • 로또 구매 금액을 입력한다.
  • 당첨 번호를 입력받는다. 번호는 쉼표를 기준으로 구분
    • 보너스 번호를 입력받는다

예외 사항

  • 로또 번호는 1부터 45 사이의 숫자여야 합니다.
  • 로또 번호 중복 시
  • 구입 금액 1000원 미만
  • 당첨 번호 6자리가 아닌 경우
  • NULL 입력
  • 보너스 번호가 당첨 번호와 겹치는 경우
  • 지불 금액이 숫자가 아닌 경우

실수한 부분

요구 사항 명세서 자세하게 보기

실행 결과 예시

구입금액을 입력해 주세요.
8000

8개를 구매했습니다.
[8, 21, 23, 41, 42, 43] 
[3, 5, 11, 16, 32, 38] 
[7, 11, 16, 35, 36, 44] 
[1, 8, 11, 31, 41, 42] 
[13, 14, 16, 38, 42, 45] 
[7, 11, 30, 40, 42, 43] 
[2, 13, 22, 32, 38, 45] 
[1, 3, 5, 14, 22, 45]

당첨 번호를 입력해 주세요.
1,2,3,4,5,6

보너스 번호를 입력해 주세요.
7

당첨 통계
---
3개 일치 (5,000) - 14개 일치 (50,000) - 05개 일치 (1,500,000) - 05개 일치, 보너스 볼 일치 (30,000,000) - 06개 일치 (2,000,000,000) - 0개
총 수익률은 62.5%입니다.

우선 위 처럼 출력이안된다. Input을 받기전에 출력하는 메시지들을 실행 결과 예시를 안보고 입출력 요구 사항만을 보고 구현했는데, 입출력 요구 사항에 따라 입력관련 메시지를 출력하라는 부분이 없어서 따로 구현을 안했었다.... 요구사항 문서를 자세하게 보지 않아서 일어난 실수이다.

매개변수 네이밍 오류 (?)

로또 당첨 등수를 2등인지, 3등인지 구분하는 코드이다.

LottoRanking class

	public static LottoRanking findByLottoRanking(int matchCount, Boolean hasBonus) {
		if (hasBonus) {
			return SECOND;
		}

		return Arrays.stream(LottoRanking.values())
			.filter(ranking -> ranking.isWin(matchCount))
			.findAny()
			.orElse(EMPTY);
	}

PrizeChecker enum class


	private boolean checkBonus(int countMatch, List<Integer> lotto, int bonusNumber) {
		return (countMatch == 5 && hasBonus(lotto, bonusNumber));
	}

	private boolean hasBonus(List<Integer> lotto, int bonusNumber) {
		return lotto.stream().anyMatch(number -> number.equals(bonusNumber));
	}

PrizeChecker 클래스가 2등인지 판별해서 LottoRanking을 생성한다.
나는 PrizeChecker라는 클래스가 2등인지 3등인지 판별하는게 더 자연스럽다고 생각해서 LottoRanking을 생성하는 findByLottoRanking 메서드에 로또 번호가 보너스번호를 가지고 5개 맞는다면 SECOND를 리턴하라고 했는데, 변수명이 hasBonus라 리뷰하는 사람도 그렇고 나도 다시 코드를 읽었을때, LottoRanking 클래스만 보고 코드에 오류가 있는줄 알았다.
hasBonus는 보너스 번호를 가지고 있다는 것만 표시하고 로또 번호가 5개 맞다는걸 알 수 없는 변수명인 것 같아서 "isSecond"로 매개변수 명을 고치는게 좋을 것 같다.

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

PrizeChecker class


	public PrizeChecker(LottoCompany lottoCompany, List<Lotto> lottoBundle) {
		List<Integer> winningNumber = lottoCompany.getWinningNumber();
		int bonusNumber = lottoCompany.getBonusNumber();
		initPrizeResult();

		for (Lotto lotto : lottoBundle) {
			List<Integer> lottoNumber = lotto.getNumbers();
			int countMatch = countingMatch(winningNumber, lottoNumber);
			boolean isBonus = checkBonus(countMatch, lottoNumber, bonusNumber);
			LottoRanking lottoRanking = LottoRanking.findByLottoRanking(countMatch, isBonus);
			this.prizeResult.put(lottoRanking, this.prizeResult.getOrDefault(lottoRanking, 0) + 1);
		}
		this.prizeMoney = sumPrize();
	}

Lotto 클래스를 객체 스럽게 사용하지 않고, getter를 통해 로또의 값을 받아서 PrizeChecker에서 검사하고 있다.

public class Lotto {
    private final List<Integer> numbers;

    public boolean contains(int number) {
        // 숫자가 포함되어 있는지 확인한다.
        ...
    }
    
    public int matchCount(Lotto other) {
        // 당첨 번호와 몇 개가 일치하는지 확인한다.
        ...
    }
}

위 코드 처럼 Lotto getter가 아닌 메시지를 보내야한다.
출처 - getter를 사용하는 대신 객체에 메시지를 보내자

어려웠던 부분

else 예약어를 쓰지 않는다.

가장 어려웠던 부분은 else를 쓰지 않는 것이였다.

else를 없애는 가장 간단한 방법은 elarly return 방법이다 if문을 여러 개 사용하고

public String getProductName(String productCode) {
	if ("1".equals(productCode)) {
		return "할부";
	}
	if ("2".equals(productCode)) {
		return "리스";
	}
	if ("3".equals(productCode)) {
		return "담보";
	}
	if ("4".equals(productCode)){
		return "신용";
	}
	throw new IllegalArgumentException("존재하지 않는 상품입니다.");
}

if - return 기반으로 코드를 작성하면, 코드를 읽을 때 의식의 흐름이 메소드의 동작을 위한 '참' 조건을 기반으로 일어난다. 'A인 경우'를 기반으로 생각하는 것이 'A가 아닌경우' 를 기반으로 생각하는 것 보다는 훨씬 명확하고 쉽다.

참고 - [객체지향 생활체조 원칙] 규칙 2. else 예약어를 쓰지 않는다

Java Enum을 적용한다.

elarly return을 통해 조건 분기점이 많아진다는 것은, 클래스의 구조를 변경해야 함을 의미할 확률이 높다. 위 로직을 enum을 활용하면 상품정보의 변경이 일어나더라도, enum 내부에서 모두 처리할 수 있는 유연한 코드가 된다.

public enum LoanProduct {

    INSTALLMENT("1", "할부"),
    LEASE("2", "리스"),
    COLLATERAL("3", "담보"),
    CREDIT("4", "신용")
    ;

    private String productCode;
    private String productName;

    LoanProduct(String productCode, String productName) {
        this.productCode = productCode;
        this.productName = productName;
    }

    public static String productName(String productCode) {
        return Arrays.stream(values())
                .filter(bankingProduct -> bankingProduct.productCode.equals(productCode))
                .findFirst()
                .orElseThrow( () -> new IllegalArgumentException("상품이 존재하지 않습니다."))
                .name();
    }
}

public String getProductName(String productCode) {
	return LoanProduct.productName(productCode);
}

enum이라는 걸 배운적이 있지만, C에서 사용 했을때 상수처럼 사용하는 정도로 사용했기 때문에 java enum을 적용하는 것도 어려웠다.
Java Enum 활용기

이번 주 과제를 통해 배운점

  1. Java enum을 통해 조건을 enum 내부에서 모두 처리할 수 있는 유연한 코드를 만들 수 있는 방법을 배웠다.
  2. 객체를 더 객체답게 사용하자
  3. 메서드 네이밍
  4. 메서드 기능 분리

0개의 댓글