> 📝 Notion 정리 문서
학습내용, 구현 과정, 리팩토링 정리해놓은 문서입니다.
이번 미션은 의도적으로 정적 메서드를 극단적으로 사용해보게 되었습니다. 아래에 정적 메서드 사용에 대한 회고를 적어놓았지만, 양해를 구하기 위해 추가로 작성하게 되었습니다. 정적 메서드에 대해 2주차 이후 학습을 하면서 적용해보고자 하였고, 결론적으로 의존성이 거의 없는 결과물이 완성된 것 같습니다..
- 저장소 포크하고 클론
- README.md에 구현할 기능 목록 정리
- Git 커밋 단위 기능 목록단위로 추가하고, 커밋 메시지 작성
- indent depth를 3이 넘지 않도록 구현한다.
- 3항 연산자를 쓰지 않는다.
- 단일 책임 원칙
- Jest를 활용하여 테스트 코드로 확인한다.
- 함수의 길이가 15라인을 넘어가지 않도록 한다.
- else를 지양한다
- 제공된
Lotto클래스를 이용하여 구현한다. -
Lotto에numbers이외의 필드를 추가할 수 없다.
- 1~45까지의 숫자 중 1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑는다.
- 당첨 번호 추첨 시 중복되지 않는 숫자 6개 + 보너스 번호 1개를 뽑는다.
- 로또 1장의 가격은 1000원이고, 로또 구입 금액을 입력하면 구입 금액에 해당하는 만큼 로또를 발행해야 한다.
- 로또 번호와 당첨 번호를 비교하여 당첨 내역 및 수익률을 출력하고 로또 게임을 종료한다.
- 구입 금액 입력 받기
- 당첨 번호 입력 받기
- 쉼표(,)를 기준으로 분리
- 보너스 번호 입력 받기
- 구입 금액 검증
- 숫자 여부
- 1,000원 단위 여부
- 양수 여부
- 당첨 번호 검증
- 6개의 숫자인지 확인
- 각 번호가 1~45 범위인지 확인
- 중복된 번호가 없는지 확인
- 보너스 번호 검증
- 1~45 범위인지 확인
- 당첨 번호와 중복되지 않는지 확인
- 구입 금액으로 로또 구매 개수 계산
- 랜덤으로 로또 번호 생성 (1~45 중 중복 없이 6개)
- 로또 번호를 오름차순으로 정렬
- 생성된 로또 목록 저장
- 각 로또와 당첨 번호 비교
- 일치하는 번호 개수 세기
- 보너스 번호 일치 여부 확인
- 당첨 등수 판정
- 6개 일치: 1등
- 5개 일치 + 보너스: 2등
- 5개 일치: 3등
- 4개 일치: 4등
- 3개 일치: 5등
- 등수별 당첨 개수 집계
- 총 당첨 금액 계산
- 1등: 2,000,000,000원
- 2등: 30,000,000원
- 3등: 1,500,000원
- 4등: 50,000원
- 5등: 5,000원
- 수익률 계산 (총 당첨 금액 / 구입 금액 * 100)
- 소수점 둘째 자리에서 반올림
- 구매한 로또 개수 출력
- 생성된 로또 번호 목록 출력
- 형식:
[8, 21, 23, 41, 42, 43]
- 형식:
- 당첨 통계 출력
- 형식:
3개 일치 (5,000원) - 1개
- 형식:
- 총 수익률 출력
- 형식:
총 수익률은 62.5%입니다.
- 형식:
- 잘못된 입력 시
[ERROR]메시지 출력 - 에러 발생 시 해당 지점부터 재입력
- 입력 -> 검증 -> 로또 발행 -> 당첨 확인 -> 수익률 계산 -> 결과 출력
- 게임 종료
- 로또 1장 클래스
- 로또 발행
- 당첨 결과 계산
- 검증
- 게임 흐름 제어
- 입력
- 출력
위와 같이 나누기로 하였고, 파일 구조는 다음과 같다.
src/
├── App.js # 프로그램 실행
│
├── entity/
│ ├── Lotto.js # 로또 클래스
│ ├── LottoIssuer.js # 로또 발행 담당
│ ├── LottoDraw.js # 당첨 번호 + 보너스 번호
│ └── LottoResult.js # 당첨 결과 계산 및 수익률
│
├── utils/
│ ├── PurchaseAmountValidator.js # 구입 금액 검증
│ ├── LottoNumberValidator.js # 로또 번호 검증
│ └── BonusNumberValidator.js # 보너스 번호 검증
│
├── controller/
│ ├── Input.js # 사용자 입력
│ ├── Output.js # 결과 출력
│ └── GameController.js # 전체 게임 흐름 제어
│
└── constants/
├── Constants.js # 상수 (프롬프트, 에러 등)
└── LottoConstants.js # 로또 내부 상수 (가격, 번호 범위, 당첨 금액 등)
sequenceDiagram
participant User
participant App
participant GameController
participant Input
participant PurchaseValidator
participant DrawNumberValidator
participant BonusNumberValidator
participant LottoIssuer
participant Lotto
participant Output
participant LottoDraw
participant LottoResult
User->>App: 프로그램 실행
App->>GameController: run()
GameController->>Input: readPurchaseAmount()
Input-->>GameController: 구입 금액
GameController->>PurchaseValidator: validate()
PurchaseValidator-->>GameController: 검증 완료
GameController->>LottoIssuer: issue()
loop 로또 개수만큼
LottoIssuer->>Lotto: new Lotto()
Lotto->>Lotto: validate() (개수, 중복 검증)
Lotto-->>LottoIssuer: Lotto 객체
end
LottoIssuer-->>GameController: Lotto 배열
GameController->>Output: printCount()
GameController->>Output: printLottos()
GameController->>Input: readDrawNumbers()
Input-->>GameController: 당첨 번호
GameController->>DrawNumberValidator: validate()
DrawNumberValidator->>DrawNumberValidator: validateLength()
DrawNumberValidator->>DrawNumberValidator: validateNumbers()
DrawNumberValidator->>DrawNumberValidator: validateRange()
DrawNumberValidator->>DrawNumberValidator: validateDuplicate()
DrawNumberValidator-->>GameController: 검증 완료
GameController->>Input: readBonusNumber()
Input-->>GameController: 보너스 번호
GameController->>BonusNumberValidator: validate()
BonusNumberValidator->>BonusNumberValidator: validateNotEmpty()
BonusNumberValidator->>BonusNumberValidator: validateNumber()
BonusNumberValidator->>BonusNumberValidator: validateInteger()
BonusNumberValidator->>BonusNumberValidator: validateRange()
BonusNumberValidator->>BonusNumberValidator: validateNotDuplicateWithDrawNumbers()
BonusNumberValidator-->>GameController: 검증 완료
GameController->>LottoDraw: new LottoDraw()
LottoDraw-->>GameController: LottoDraw 객체
GameController->>LottoResult: new LottoResult()
loop 각 Lotto마다
LottoResult->>Lotto: getNumbers()
Lotto-->>LottoResult: 로또 번호들
LottoResult->>LottoDraw: countMatch()
LottoDraw-->>LottoResult: 일치 개수
LottoResult->>LottoDraw: hasBonus()
LottoDraw-->>LottoResult: 보너스 일치 여부
LottoResult->>LottoResult: determineRank()
LottoResult->>LottoResult: 순위별 카운트 증가
end
LottoResult-->>GameController: LottoResult 객체
GameController->>LottoResult: getRank()
LottoResult-->>GameController: 당첨 통계
GameController->>Output: printStatistics()
GameController->>LottoResult: calculateProfit()
loop 각 순위별
LottoResult->>LottoResult: 순위별 상금 계산
end
LottoResult->>LottoResult: 총 수익 / 구입 금액 * 100
LottoResult-->>GameController: 수익률
GameController->>Output: printProfitRate()
- 기본 테스트
- 예외 테스트
- 숫자가 아닌 금액 입력
- 구입 금액이 1000원 단위가 아님
- 구입 금액이 0원
- 구입 금액이 음수
- 당첨 번호가 6개 아님
- 당첨 번호에 중복
- 당첨 번호가 범위 외
- 보너스 번호가 당첨 번호와 중복
- 보너스 번호가 범위 외
- 로또 번호 6개 이상 입력 시 에러
- 중복 있을 시 에러
- 로또 번호가 6개 미만일 때 에러
- 로또 번호가 범위 외일 때 에러
- 1000원 당 로또 1장 발행
- 발행된 로또가 6개의 번호를 가지고 있는가
- 발행된 로또 번호가 범위 내
- 발행된 로또 번호 무중복
- 발행된 로또 번호 오름차순 정렬
- 당첨번호 조회
- 보너스 번호 조회
- 일치하는 번호 개수 계산
- 보너스 번호 포함 여부 확인
- 1등, 2등했을 때 등수 계산
- 당첨 통계 계산
- 수익률 계산 (1등, 낙첨)
- 당첨 통계 조회
- 정상 입력
- 빈 문자열, 공백, null, undefined
- 숫자가 아닌 문자, 숫자 문자 혼합
- 0 혹은 음수
- 1000단위 검증
- 정상 입력
- 개수가 6개가 아님
- 중복
- 빈 문자열, 공백
- 숫자가 아님
- 정수가 아닌 숫자 (소수)
- 범위 외 숫자
- 경계값 통과 여부 확인(1,45)
- 정상 입력
- 빈 입력
- 숫자가 아님
- 범위 외
- 당첨 번호와 중복
- 외부에서 사용하지 않는 내부 메서드를 private으로 변경
- 캡슐화를 목적으로 하여 수정하였음
- 불필요한 외부 접근 방지하고, 내부 구현 숨김
- 상태를 가지지 않는 객체들은 static으로 변경
- 인스턴스마다 객체를 생성 -> 낭비되기 때문에 이를 제거함
- 모든 인스턴스가 하나의 Validator를 공유하도록 함
이번 미션에 앞서 정적 메서드를 공부하면서, 상태를 가지지 않는 클래스들을 모두 정적 메서드로 바꾸는 리팩토링을 진행해보았습니다.
로또 프로그램의 단순한 구조 특성상 Controller, Input, Output까지 정적 메서드로 전환되어 의존성이 거의 없는 프로그램이 완성되었습니다.
장점
- 코드가 간결해짐
- 인스턴스 생성 오버헤드가 없음
- 유틸리티 함수처럼 직관적인 사용
단점
- 의존성 주입이 어려움
- 테스트 시 모킹이 복잡함
- 확장성이 제한됨
지금 구현한 프로그램과 같이 단순한 CLI 프로그램에서는 정적 메서드가 효과적일 수 있지만, 복잡한 비즈니스 로직이나 여러 의존성을 가진 프로그램에서는 인스턴스 방식이 더 적합하다는 것을 체감했습니다.
그래도 실험적인 도전을 해본 것에 대해 큰 점수를 주고 싶..습니다.