Skip to content
 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

javascript-planetlotto-precourse

Note

  • 미션 수행에 필요한 내용을 자유롭게 작성하세요.
  • 단, 구현한 기능 목록과 무엇을 도전했는지가 명확히 드러나야 합니다.
  • 자세한 생각이나 맥락, 추가 기록은 회고 시간(17:00~18:00)에 지원 플랫폼에서 별도로 작성할 수 있습니다.

최종 결과

테스트 결과

입출력 양식

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

2개를 구매했습니다.
[8, 11, 13, 21, 22]
[1, 3, 6, 14, 22]

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

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

당첨 통계
---
5개 일치 (100,000,000원) - 0개
4개 일치, 보너스 번호 일치 (10,000,000원) - 0개
4개 일치 (1,500,000원) - 0개
3개 일치, 보너스 번호 일치 (500,000원) - 0개
2개 일치, 보너스 번호 일치 (5,000원) - 1개
0개 일치 (0원) - 1개

기능 요구 사항

  • 로또 숫자 범위 1~30
  • 로또 발행시 5개의 숫자 뽑기
  • 중복되지 않는 5개와 보너스 번호 1개 뽑기
- 1등: 5개 번호 일치 / 100,000,000원
- 2등: 4개 번호 + 보너스 번호 일치 / 10,000,000원
- 3등: 4개 번호 일치 / 1,500,000원
- 4등: 3개 번호 일치 + 보너스 번호 일치 / 500,000원
- 5등: 2개 번호 일치 + 보너스 번호 일치 / 5,000원
  • 로또 금액 500원
  • 로또 번호는 오름차순으로 정렬

기능 목록

Validator

  • validateAmount
    • isNaN 값인지 확인한다.
    • 로또를 최소 1장 이상 살 수 있는지 확인한다.
    • 값이 딱 떨어지는지 확인한다. (현금 없는 사회를 위한 추가 기능)
  • validateWinningNumber
    • 중복된 값이 없는지 확인한다.
    • 로또 번호 범위 안의 값인지 확인한다.
    • isNaN 값이 섞여있는지 확인한다.
  • validateBonusNumber
    • 범위 안에 들어가있는지 확인한다.

LottoIssuer

  • 금액에 맞춰 로또를 배출한다.
    • 이때 로또는 오름차순으로 정렬된다.
  • 로또 숫자로 이루어진 이중 배열을 바로 가져올 수 있다.

LottoMather

  • 당첨 번호 기반으로 로또가 몇개 맞았는지 검사하고 바로 반환한다.
    • 로또 통계값은 Map 자료구조로 저장된다.

App

  • 각 입력값은 적절한 파이프라인을 통해 App에 들어온다.
  • 각 클래스가 원하는 자료구조 형태로 최종 결과값을 출력한다.
  • 에러가 나올 경우 그 부분부터 다시 입력 받는다.

도전 과제

Note

로또를 해본 경험이 없어 추가 기능을 뭘 해야할지 모르겠는 관계로 리팩토링 선택

리팩토링

Tip

Pipeline 도입

도전 과제의 조건을 조금 다르게 해석해봤습니다.
개인적으로, 저의 성향의 경우 리팩토링을 미뤄두면 기술 부채가 되는 경우가 많았습니다.

설계적 결함이 있어 디렉토리 구조를 바꾸는 대작업이 아닌,
단순 코드 퀄리티를 위한 리팩토링은 개발 진행 과정과 함께 진행하는 편이 개인 성향에 더 맞는다고 느꼈습니다.

따라서 이번 도전 과제에는,
특정 시간을 들여 리팩토링을 하기보다 개발과 리팩토링을 동시에 적용하며 작업하는 데 의의를 두고 도전했습니다.

가장 우선순위로 두는 개발 가치는 가독성입니다.

  • 최대한 함수를 기능별로 분리하고,
  • public 함수는 어느정도 다중 책임을 부여하더라도
    내부에서 코드 흐름이 보이도록 코드를 작성했습니다.

프리코스 과정을 진행하는 동안,
App.js영역은 가독성을 신경쓰지 않는 편이었는데 최종 코딩테스트를 준비하며 Pipeline 개념을 도입하면 좋겠다는 생각을 했습니다.

프리코스 과정에서 validate는

  • 단순 Raw 입력값 정제와,
  • 도메인 정책 validate로 나뉜다는 생각을 했기 때문인데요.

예를 들어 로또 구매 가격에 isNaN 값이 들어오는건 입력값 validate에 해당하고,
로또 가격이 장당 500원은 도메인 정책에 해당한다는 생각을 했습니다.

두 validate를 서로 분리해 작업하면 좋지 않을까, 하는 생각에서
특정 pipeline에 Raw값을 1차 정제하고, 도메인값을 2차 정제하면 좋겠다는 생각을 하게 된 것이지요.

  async #readBonusNumberPipeline() {
    // 1. Raw 입력
    const bonusRaw = await this.#readUntilValid(
      () => this.input.readBonusNumber(),
      [(v) => this.validator.validateBonusNumberRaw(v)]
    );

    // 2. Parse
    const bonusNumber = Number(bonusRaw);

    // 3. Validate (parsed)
    this.validator.validateBonusNumber(bonusNumber);

    // 4. Return
    return bonusNumber;
  }

이런 식으로 작업하면, 조금 더 단일책임 원칙을 지키는데다
테스트도 쉽겠다! 라는 생각을 했습니다만 InputView가 고정된 관계로...🫠
어떻게 Raw 값을 받아올지 생각나지 않아 적용 하진 못했습니다.

이런 관점에서,
이번 최종 코딩테스트의 경우 Input을 받아올 때 필요한 형태로 정제되기에
별도 Pipeline 메서드를 만들지 않아도 되지 않을까 잠깐 생각했습니다만,
그럼에도 넣었을 때 async run()의 유지보수성이 일부 상승한다고 판단해서 적용해봤습니다.

  async run() {
    const purchaseAmount = await this.#readAmountPipeline();

    const tickets = this.issuer.issue(purchaseAmount); // Lotto[]
    OutputView.printPurchasedLottos(this.issuer.getLottos(tickets));

    const winningNumbers = await this.#readWinningNumbersPipeline();
    const bonusNumber = await this.#readBonusNumberPipeline();

    const target = [winningNumbers, bonusNumber];
    const statisticsMap = this.matcher.match(tickets, target);

    OutputView.printResult(statisticsMap);
  }

현재 시점에서는 pipeline이 크게 의미있거나 하진 않지만,
나중에 로또 값에 대한 도메인 정책이 마구 추가되게 되면 pipeline에 로직을 추가하면 되니,
수정 범위도 줄어들고 여전히 깔끔한 run() 메서드를 사용할 수 있게 됐습니다!🎉

Tip

스텁을 이용한 개발!?

항상 App.js를 너무 더럽게 쓰는 것 같다는 묘한 부끄러움이 있어서,
이번 개발에서는 오히려 App.js부터 가장 먼저 작성하는 식으로 어느정도 흐름을 보이게 코드를 작성해봤습니다.

항상 도메인과 서비스부터 구현할 때는 그때그때 구현 도중에 입력값 형태가 바뀔 일이 많았는데,
뭔가 흐름부터 정리해가니 개발이 더 수월해진 기분이 들었습니다.

단일 책임 원칙을 지키기도 더 쉽구요.
실제 프로젝트에서는 이런 기법을 사용해본 적이 없지만,
음... 이런 방법도 고려해보면 좋겠다는 생각이 들었습니다.
이 코드라면 코드에 대한 사전 지식이 적어도 흐름을 이해할 수 있지 않을까하는 기대감이 생겼습니다.

중간 수정 시간이 줄어드니, 가독성을 조금 더 수월하게 챙길 수 있다는 장점도 있었습니다.
제가 프로젝트를 확장해본 경험은 많지 없지만,
이렇게 흐름부터 정리하며 해당 스텁을 하위 메서드로 갈라 작업하는 방식을 이용하면
추후 유지보수나 기능 확장시 수정할 부분이 줄어들어 조금 더 작업이 수월하지 않을까, 하는 생각이 들었습니다.

특히 요즘처럼 바이브코딩을 이용하는 횟수가 많아질 때는...
이전 개발 방식과 다르게 과몰입하게 되는 경우가 많아 오히려 오버엔지니어링을 하는 경우가 많았거든요.

흐름보다 하나의 기능에 너무 세세한 부분에 빠져든다거나...

이전에 레거시 코드를 봤을 때, 하나의 기능을 수정하는데 수정할 범위가 너무 커지거나,
사이드 이펙트가 너무 커지면 작업을 못 하겠더라구요.

그런 측면에서 이런 개발법을 연습해두면,
좋을 것 같다는 생각이 마구 드네요.

1. 간단한 디렉토리 구조 설계
2. `main` 스텁
3. 2차 아키텍처 설계
4. 개발

이런 식의 작업을 한 번 해봐야겠다는 생각을 하며,
오늘 최종 코딩테스트를 마칠 수 있었습니다.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages