- 로또 요구사항을 파악한다.
- 요구사항에 대한 구현을 완료한 후 자신의 github 아이디에 해당하는 브랜치에 Pull Request(이하 PR)를 통해 코드 리뷰 요청을 한다.
- 코드 리뷰 피드백에 대한 개선 작업을 하고 다시 PUSH한다.
- 모든 피드백을 완료하면 다음 단계를 도전하고 앞의 과정을 반복한다.
- 쉼표(,) 또는 콜론(:)을 구분자로 가지는 문자열을 전달하는 경우 구분자를 기준으로 분리한 각 숫자의 합을 반환 (예: “” => 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);
}
}
- [fix01][StringAddCalculator.java] 숫자 1, 2를 상수화 하여 Readability 개선하기
- [fix02][StringAddCalculator.java] delimiter 상수 합치기
- [fix03][StringAddCalculatorTest.java] @DisplayName 이용하여 테스트에 Description 부여하기
- 로또 구입 금액을 입력하면 구입 금액에 해당하는 로또를 발급해야 한다.
- 로또 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도 허용하지 않는다.
- [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 를 ==로 변경해보기
- 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;
}
}
- [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도 받을 수 있도록 수정하기
- 현재 로또 생성기는 자동 생성 기능만 제공한다. 사용자가 수동으로 추첨 번호를 입력할 수 있도록 해야 한다.
- 입력한 금액, 자동 생성 숫자, 수동 생성 번호를 입력하도록 해야 한다.
구입금액을 입력해 주세요.
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이 발생하지 않도록 한다.
- [fix01][Lotto.java] 애초에 자료구조를 Set 으로 정의하고, 출력을 위한 정렬은 출력 객체에서 담당할 수 있도록 해보기
- 예를 들면,
Lotto (Set) -> LottoResponse (List)
- 예를 들면,
- [fix02][LottoNumber.java] Boxed type 은 == 비교를 하면 안됨, 애초에 Boxed type을 사용한 이유도 있어야 함
- [fix03][PurchaseAmount.java] static 키워드 누락
- [fix04][LottoNumberDice.java] private 생성자 누락