프리코스 3주차 회고록
우아한형제들의 개발자 양성 프로젝트, 우아한테크코스의 프리코스를 경험하게 된 것은 정말 뜻 깊은 시간이었습니다. 이 프리코스를 통해 많은 것을 배우고 성장하는 기회를 얻었습니다. 아래에는 이 경험에서 얻은 교훈과 느낀 점, 그리고 부족한 부분을 솔직하게 정리해보겠습니다.
🚀 기능 요구 사항
로또 게임 기능을 구현해야 한다. 로또 게임은 아래와 같은 규칙으로 진행된다.
- 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]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다.
- Exception이 아닌 IllegalArgumentException, IllegalStateException 등과 같은 명확한 유형을 처리한다.
입출력 요구 사항
입력
- 로또 구입 금액을 입력 받는다. 구입 금액은 1,000원 단위로 입력 받으며 1,000원으로 나누어 떨어지지 않는 경우 예외 처리한다.
14000
- 당첨 번호를 입력 받는다. 번호는 쉼표(,)를 기준으로 구분한다.
1,2,3,4,5,6
- 보너스 번호를 입력 받는다.
7
출력
- 발행한 로또 수량 및 번호를 출력한다. 로또 번호는 오름차순으로 정렬하여 보여준다.
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]
- 당첨 내역을 출력한다.
"3개 일치 (5,000원) - 1개
4개 일치 (50,000원) - 0개
5개 일치 (1,500,000원) - 0개
5개 일치, 보너스 볼 일치 (30,000,000원) - 0개
6개 일치 (2,000,000,000원) - 0개"
- 수익률은 소수점 둘째 자리에서 반올림한다. (ex. 100.0%, 51.5%, 1,000,000.0%)
"총 수익률은 62.5%입니다."
- 예외 상황 시 에러 문구를 출력해야 한다. 단, 에러 문구는 "[ERROR]"로 시작해야 한다.
"[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다."
실행 결과 예시
"구입금액을 입력해 주세요.
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원) - 1개
4개 일치 (50,000원) - 0개
5개 일치 (1,500,000원) - 0개
5개 일치, 보너스 볼 일치 (30,000,000원) - 0개
6개 일치 (2,000,000,000원) - 0개
총 수익률은 62.5%입니다."
1. Static 메서드의 사용
이번 프로젝트에서 저는 성능과 효율성을 위해 static 메서드의 사용을 확대했습니다. 구체적으로, 'MatchCalculator' 클래스에서는 로또 번호의 일치 여부를 판단하는 `calculateMatchCount()`와 `isBonusNumberMatched()` 메서드를 static으로 구현했습니다. 이전에는 이러한 기능을 인스턴스 메서드로 구현하여 각 기능에 대해 별도의 객체 인스턴스를 생성했습니다. 그러나 static 메서드로 전환함으로써, 불필요한 객체 생성을 줄이고 전체적인 메모리 사용량을 최적화할 수 있었습니다. 또한, 이러한 접근 방식은 코드의 재사용성과 가독성을 크게 향상시켰습니다. 이는 프로그램의 성능 개선에 크게 기여했으며, 특히 대규모 데이터를 처리하는데 있어서 더욱 중요한 요소가 되었습니다
public class MatchCalculator {
public static int calculateMatchCount(Lotto userLotto, WinningNumbers winningNumbers) {
Set<LottoNumber> userNumbers = new HashSet<>(userLotto.getNumbers());
Set<LottoNumber> winningNumbersSet = new HashSet<>(
winningNumbers.getWinningLotto().getNumbers());
userNumbers.retainAll(winningNumbersSet);
return userNumbers.size();
}
public static boolean isBonusNumberMatched(Lotto userLotto, WinningNumbers winningNumbers) {
return userLotto.getNumbers().contains(winningNumbers.getBonusNumber());
}
}
2. 객체 자체 검증 방식
이전 프로젝트에서는 범용 검증 로직을 사용했었습니다. 하지만 그렇게 했을때 만약 리스트의 길이를 검증하는 클레스가 있다고 했을때 이를 범용적으로 쓰기엔 너무 많은 책임을 가지고 있다고 느꼈습니다. 또한 클래스가 많아지고 너무 자주 사용하게 된다면 가독성도 좋지 않을꺼 같다고 느꼈습니다. 그래서 이번 프로젝트에서는 각 객체가 자신의 상태를 스스로 검증하도록 변경하여 코드의 신뢰성을 향상시켰습니다. 예를 들어, 'Lotto' 클래스는 생성자 내에서 입력된 숫자 리스트의 크기가 적절한지와 중복된 숫자가 없는지를 검증합니다. 마찬가지로 'LottoNumber' 클래스는 숫자가 지정된 범위 내에 있는지 검사합니다. 이러한 자체 검증 방식은 코드의 안정성을 높이고, 유효성 검사를 더욱 정확하고 세밀하게 수행할 수 있도록 했습니다. 각 객체의 책임이 명확해졌고, 이는 유지보수와 코드의 확장성에 긍정적인 영향을 미쳤습니다. 또한, 이 방법은 객체 간의 결합도를 낮추는 데도 도움이 되었습니다.
--- 범용정 검증 방법----
public Lotto(List<Integer> numbers) {
ListLengthValidator.validateMaxLength(numbers, MAX_LENGTH);
this.numbers = numbers;
}
------- 본인이 직접 검증 -----
public Lotto(final List<Integer> numbers) {
validateSize(numbers);
validateDuplicate(numbers);
this.numbers = convertToLottoNumber(numbers);
}
private void validateSize(List<Integer> numbers) {
if (numbers.size() != LottoConfig.NUMBER_COUNT.getValue()) {
throw new IllegalArgumentException(INVALID_SIZE.getMessage());
}
}
private void validateDuplicate(List<Integer> numbers) {
if (hasDuplicates(numbers)) {
throw new IllegalArgumentException(DUPLICATE_NUMBERS.getMessage());
}
}
3. 생성자에서 Final 키워드 사용
이번 프로젝트에서 저는 생성자의 매개변수에 'final' 키워드를 사용하는 방식에 주목했습니다. 이 방식은 매개변수의 불변성을 강화하고, 생성자 내에서 이 매개변수들이 의도치 않게 변경되는 것을 방지합니다. 예를 들어, 'Lotto' 클래스의 생성자에서 숫자 리스트를 final 매개변수로 받아들임으로써, 이 리스트는 생성자 내에서 변경될 수 없습니다.
이러한 접근 방식은 객체의 안전한 생성을 보장하는 데 중요한 역할을 합니다. 생성자에서 final 매개변수를 사용하면, 객체가 처음 생성될 때 주어진 값이 생성 과정 내내 일관되게 유지됩니다. 이는 특히 복잡한 로직이나 여러 단계의 초기화가 필요한 객체를 생성할 때 유용합니다. 불변 매개변수를 사용함으로써, 생성자 내의 부작용이나 예기치 않은 상태 변경을 방지하고, 객체의 안정성과 예측 가능성을 높일 수 있습니다.
public Lotto(final List<Integer> numbers) {
validateSize(numbers);
validateDuplicate(numbers);
this.numbers = convertToLottoNumber(numbers);
}
public Money(final int amount) {
validatePositive(amount);
validateDivisibility(amount);
this.amount = amount;
}
🐣 [구현 코드 보러가기]
https://github.com/woowacourse-precourse/java-lotto-6/pull/979
'우아한 프리코스' 카테고리의 다른 글
[우아한 테크 코스]6기 프리코스 4주차 회고록 (0) | 2023.11.24 |
---|---|
[우아한 테크 코스]6기 프리코스 2주차 회고록 (0) | 2023.11.02 |
[우아한 테크 코스]6기 프리코스 1주차 회고록 (0) | 2023.10.25 |