Skip to content

로또 게임 구현을 관리하는 저장소

Notifications You must be signed in to change notification settings

neojjc2/java-lotto

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

로또

진행 방법

  • 로또 요구사항을 파악한다.
  • 요구사항에 대한 구현을 완료한 후 자신의 github 아이디에 해당하는 브랜치에 Pull Request(이하 PR)를 통해 코드 리뷰 요청을 한다.
  • 코드 리뷰 피드백에 대한 개선 작업을 하고 다시 PUSH한다.
  • 모든 피드백을 완료하면 다음 단계를 도전하고 앞의 과정을 반복한다.

온라인 코드 리뷰 과정

1단계 - 문자열 덧셈 계산기

기능 요구사항

  • 쉼표(,) 또는 콜론(:)을 구분자로 가지는 문자열을 전달하는 경우 구분자를 기준으로 분리한 각 숫자의 합을 반환 (예: “” => 0, "1,2" => 3, "1,2,3" => 6, “1,2:3” => 6)
  • 앞의 기본 구분자(쉼표, 콜론)외에 커스텀 구분자를 지정할 수 있다.
    • 커스텀 구분자는 문자열 앞부분의 “//”와 “\n” 사이에 위치하는 문자를 커스텀 구분자로 사용한다.
    • 예를 들어 “//;\n1;2;3”과 같이 값을 입력할 경우 커스텀 구분자는 세미콜론(;)이며, 결과 값은 6이 반환되어야 한다.
  • 문자열 계산기에 숫자 이외의 값 또는 음수를 전달하는 경우 RuntimeException 예외를 throw한다.

프로그래밍 요구사항

  • indent(들여쓰기) depth를 2단계에서 1단계로 줄여라.
  • depth의 경우 if문을 사용하는 경우 1단계의 depth가 증가한다. if문 안에 while문을 사용한다면 depth가 2단계가 된다.
  • 메소드의 크기가 최대 10라인을 넘지 않도록 구현한다.
  • method가 한 가지 일만 하도록 최대한 작게 만들어라.
  • else를 사용하지 마라.

기능 요구사항 분리 및 힌트

  • 빈 문자열 또는 null 값을 입력할 경우 0을 반환해야 한다.(예 : “” => 0, null => 0)

if (text == null) {}
if (text.isEmpty()) {}
  • 숫자 하나를 문자열로 입력할 경우 해당 숫자를 반환한다.(예 : “1”)
int number = Integer.parseInt(text);
  • 숫자 두개를 컴마(,) 구분자로 입력할 경우 두 숫자의 합을 반환한다.(예 : “1,2”)
String[] numbers = text.split(",");

// 앞 단계의 구분자가 없는 경우도 split()을 활용해 구현할 수 있는지 검토해 본다.

  • 구분자를 컴마(,) 이외에 콜론(:)을 사용할 수 있다. (예 : “1,2:3” => 6)
String[] tokens= text.split(",|:");
  • “//”와 “\n” 문자 사이에 커스텀 구분자를 지정할 수 있다. (예 : “//;\n1;2;3” => 6)

// java.util.regex 패키지의 Matcher, Pattern import
Matcher m = Pattern.compile("//(.)\n(.*)").matcher(text);
if (m.find()) {
    String customDelimiter = m.group(1);
    String[] tokens= m.group(2).split(customDelimiter);
    // 덧셈 구현
}
  • 음수를 전달할 경우 RuntimeException 예외가 발생해야 한다. (예 : “-1,2,3”)
    • 구글에서 “junit4 expected exception”으로 검색해 해결책을 찾는다.
    • TestCase 소스 코드

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

public class StringAddCalculatorTest {
    @Test
    public void splitAndSum_null_또는_빈문자() {
        int result = StringAddCalculator.splitAndSum(null);
        assertThat(result).isEqualTo(0);

        result = StringAddCalculator.splitAndSum("");
        assertThat(result).isEqualTo(0);
    }

     @Test
    public void splitAndSum_숫자하나() throws Exception {
        int result = StringAddCalculator.splitAndSum("1");
        assertThat(result).isEqualTo(1);
    }

    @Test
    public void splitAndSum_쉼표구분자() throws Exception {
        int result = StringAddCalculator.splitAndSum("1,2");
        assertThat(result).isEqualTo(3);
    }

    @Test
    public void splitAndSum_쉼표_또는_콜론_구분자() throws Exception {
        int result = StringAddCalculator.splitAndSum("1,2:3");
        assertThat(result).isEqualTo(6);
    }

    @Test
    public void splitAndSum_custom_구분자() throws Exception {
        int result = StringAddCalculator.splitAndSum("//;\n1;2;3");
        assertThat(result).isEqualTo(6);
    }

    @Test
    public void splitAndSum_negative() throws Exception {
        assertThatThrownBy(() -> StringAddCalculator.splitAndSum("-1,2,3"))
                .isInstanceOf(RuntimeException.class);
    }
}

Step1 리뷰사항

  • [fix01][StringAddCalculator.java] 숫자 1, 2를 상수화 하여 Readability 개선하기
  • [fix02][StringAddCalculator.java] delimiter 상수 합치기
  • [fix03][StringAddCalculatorTest.java] @DisplayName 이용하여 테스트에 Description 부여하기

2단계 - 로또(자동)

기능 요구사항

  • 로또 구입 금액을 입력하면 구입 금액에 해당하는 로또를 발급해야 한다.
  • 로또 1장의 가격은 1000원이다.

구입금액을 입력해 주세요.
14000
14개를 구매했습니다.
[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]
[23, 25, 33, 36, 39, 41]
[1, 3, 5, 14, 22, 45]
[5, 9, 38, 41, 43, 44]
[2, 8, 9, 18, 19, 21]
[13, 14, 18, 21, 23, 35]
[17, 21, 29, 37, 42, 45]
[3, 8, 27, 30, 35, 44]

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

당첨 통계
---------
3개 일치 (5000원)- 1개
4개 일치 (50000원)- 0개
5개 일치 (1500000원)- 0개
6개 일치 (2000000000원)- 0개
총 수익률은 0.35입니다.(기준이 1이기 때문에 결과적으로 손해라는 의미임)

힌트

  • 로또 자동 생성은 Collections.shuffle() 메소드 활용한다.
  • Collections.sort() 메소드를 활용해 정렬 가능하다.
  • ArrayList의 contains() 메소드를 활용하면 어떤 값이 존재하는지 유무를 판단할 수 있다.

프로그래밍 요구사항

  • 모든 기능을 TDD로 구현해 단위 테스트가 존재해야 한다. 단, UI(System.out, System.in) 로직은 제외
  • 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 구분한다. U* I 로직을 InputView, ResultView와 같은 클래스를 추가해 분리한다.
  • indent(인덴트, 들여쓰기) depth를 2를 넘지 않도록 구현한다. 1까지만 허용한다.
  • 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
  • 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메소드)를 분리하면 된다.
  • 함수(또는 메소드)의 길이가 15라인을 넘어가지 않도록 구현한다.
  • 함수(또는 메소드)가 한 가지 일만 잘 하도록 구현한다.
  • 모든 로직에 단위 테스트를 구현한다. 단, UI(System.out, System.in) 로직은 제외
  • 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 구분한다.
  • UI 로직을 InputView, ResultView와 같은 클래스를 추가해 분리한다.
  • 자바 코드 컨벤션을 지키면서 프로그래밍한다.
  • else 예약어를 쓰지 않는다.
    • 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다.
    • else를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다.

Step2 리뷰사항

  • [fix01][LottoTicketGenerator.java] 로또 후보 번호 생성 로직 수정하기 range(1, 46) or rangeClosed(1, 45)
  • [fix02][LottoWinningNumber.java] LottoNumber 와 성질이 유사하므로 합쳐보기
  • [fix03][LottoTicketGenerator.java] lambda 로 로직 개선

return Stream.generate(LottoTicketGenerator::generateNumbers)
            .limit(amount / LOTTERY_PRICE)
            .collect(Collectors.collectingAndThen(Collectors.toList(), LottoTicket::new));
  • [fix04][LottoWinningNumber.java] 로또 번호(Integer)도 객체로 분리해 보기
  • [fix05][ResultView.java] 수익률 계산하는 비즈니스 로직 이동
  • [fix06][LottoAward.java] 상금에 언더 바(_) 추가하여 가독성 개선하기
  • [fix07][LottoAward.java] if 문에서 불 필요한 괄호 제거
  • [fix08][LottoAward.java] match 를 ==로 변경해보기

3단계 - 로또(2등)

기능 요구사항

  • 2등을 위해 추가 번호를 하나 더 추첨한다.
  • 당첨 통계에 2등도 추가해야 한다.

[... 생략 ...]

지난 주 당첨 번호를 입력해 주세요.
1, 2, 3, 4, 5, 6
보너스 볼을 입력해 주세요.
7

당첨 통계
---------
3개 일치 (5000원)- 1개
4개 일치 (50000원)- 0개
5개 일치 (1500000원)- 0개
5개 일치, 보너스 볼 일치(30000000원) - 0개
6개 일치 (2000000000원)- 0개
총 수익률은 0.35입니다.(기준이 1이기 때문에 결과적으로 손해라는 의미임)

프로그래밍 요구사항

  • 모든 기능을 TDD로 구현해 단위 테스트가 존재해야 한다. 단, UI(System.out, System.in) 로직은 제외
  • java enum을 적용해 프로그래밍을 구현한다.
  • 규칙 8: 일급 콜렉션을 쓴다.
    • indent(인덴트, 들여쓰기) depth를 2를 넘지 않도록 구현한다. 1까지만 허용한다.
    • 함수(또는 메소드)의 길이가 15라인을 넘어가지 않도록 구현한다.
  • 자바 코드 컨벤션을 지키면서 프로그래밍한다.
  • else 예약어를 쓰지 않는다.

힌트

  • 일급 콜렉션을 쓴다.
  • 6개의 숫자 값을 가지는 java collection을 감싸는 객체를 추가해 구현해 본다.
  • 하드 코딩을 하지 않기 위해 상수 값을 사용하면 많은 상수 값이 발생한다. 자바의 enum을 활용해 상수 값을 제거한다. 즉, enum을 활용해 일치하는 수를 로또 등수로 변경해 본다.

public enum Rank {
    FIRST(6, 2_000_000_000),
    SECOND(5, 30_000_000),
    THIRD(5, 1_500_000),
    FOURTH(4, 50_000),
    FIFTH(3, 5_000),
    MISS(0, 0);

    private int countOfMatch;
    private int winningMoney;

    private Rank(int countOfMatch, int winningMoney) {
        this.countOfMatch = countOfMatch;
        this.winningMoney = winningMoney;
    }

    public int getCountOfMatch() {
        return countOfMatch;
    }

    public int getWinningMoney() {
        return winningMoney;
    }
		
    public static Rank valueOf(int countOfMatch, boolean matchBonus) {
        // TODO 일치하는 수를 로또 등수로 변경한다. enum 값 목록은 "Rank[] ranks = values();"와 같이 가져올 수 있다.
        return null;
    }
}

Step3 리뷰사항

  • [fix01][LottoAward.java] 로직 개선하기

Arrays.stream(values())
            .filter(award-> rank.countOfMatch== countOfMatch)
            .filter(award-> !award.equals(SECOND) || matchBonus)
            .findFirst()
            .orElse(LOSE);
  • [fix02][Lotto.java] 중복을 허용하지 않는 구조체로 개선해보기
  • [fix03][Lotto.java] if (AWARD_2ND_OR_3RD == matchCount 조건문 재 검토 해보기
  • [fix04][Lotto.java] getter 는 최대한 없애보기 (출력용도제외)
  • [fix05][LottoNumber.java] getter 는 최대한 없애보기 (출력용도제외)
  • 값 객체의 경우 객체를 재사용하기 좋음
  • 템플릿 메서드 패턴의 장점 중 하나인 객체 재사용을 적용해 볼 것

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
  • 위 코드인 Integer.valueOf구현을 보면 특정 값은 Cache하여 사용하는 것을 볼 수 있음
  • 플라이웨이트 패턴 키워드로 검색해 볼 것
  • [fix06][LottoNumber.java] 게임과 관련된 변수를 한군데서 관리하는 집합 만들어 보기
  • [fix07][LottoTicketGenerator.java] LottoNumber 로 Pool 만들어 보기
  • [fix08][LottoTicketGenerator.java] 일급 컬렉션 만 갖도록 해보기 (price를 가진게 아닌)
  • [fix09][LottoTicketGenerator.java] numbers를 그냥 Lotto로 치환하면 어떤 장점이 있을까 생각해보기
  • [fix10][LottoNumber.java] LottoNumber가 String도 받을 수 있도록 수정하기

4단계 - 로또(수동)

기능 요구사항

  • 현재 로또 생성기는 자동 생성 기능만 제공한다. 사용자가 수동으로 추첨 번호를 입력할 수 있도록 해야 한다.
  • 입력한 금액, 자동 생성 숫자, 수동 생성 번호를 입력하도록 해야 한다.

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

수동으로 구매할 로또 수를 입력해 주세요.
3

수동으로 구매할 번호를 입력해 주세요.
8, 21, 23, 41, 42, 43
3, 5, 11, 16, 32, 38
7, 11, 16, 35, 36, 44

수동으로 3장, 자동으로 11개를 구매했습니다.
[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]
[23, 25, 33, 36, 39, 41]
[1, 3, 5, 14, 22, 45]
[5, 9, 38, 41, 43, 44]
[2, 8, 9, 18, 19, 21]
[13, 14, 18, 21, 23, 35]
[17, 21, 29, 37, 42, 45]
[3, 8, 27, 30, 35, 44]

지난 주 당첨 번호를 입력해 주세요.
1, 2, 3, 4, 5, 6
보너스 볼을 입력해 주세요.
7

당첨 통계
---------
3개 일치 (5000원)- 1개
4개 일치 (50000원)- 0개
5개 일치 (1500000원)- 0개
5개 일치, 보너스 볼 일치(30000000원) - 0개
6개 일치 (2000000000원)- 0개
총 수익률은 0.35입니다.(기준이 1이기 때문에 결과적으로 손해라는 의미임)

프로그래밍 요구사항

  • 규칙 3: 모든 원시값과 문자열을 포장한다.
  • 규칙 5: 줄여쓰지 않는다(축약 금지).
  • 예외 처리를 통해 에러가 발생하지 않도록 한다.
    • 모든 기능을 TDD로 구현해 단위 테스트가 존재해야 한다. 단, UI(System.out, System.in) 로직은 제외
    • java enum을 적용해 프로그래밍을 구현한다.
  • 규칙 8: 일급 콜렉션을 쓴다.
  • indent(인덴트, 들여쓰기) depth를 2를 넘지 않도록 구현한다. 1까지만 허용한다.
  • 함수(또는 메소드)의 길이가 15라인을 넘어가지 않도록 구현한다.
  • 자바 코드 컨벤션을 지키면서 프로그래밍한다.
  • else 예약어를 쓰지 않는다.

힌트

  • 규칙 3: 모든 원시값과 문자열을 포장한다.
  • 로또 숫자 하나는 int 타입이다. 이 숫자 하나를 추상화한 LottoNo 객체를 추가해 구현한다.
  • 예외 처리를 통해 에러가 발생하지 않도록 한다.
  • 사용자가 잘못된 값을 입력했을 때 java exception으로 에러 처리를 한다.
  • java8에 추가된 Optional을 적용해 NullPointerException이 발생하지 않도록 한다.

Step4 리뷰사항

About

로또 게임 구현을 관리하는 저장소

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Java 100.0%