[3주차 프리코스] 로또 개발 회고 - 작성 중...

Nayoung·2024년 11월 28일
0
post-thumbnail

나의 3주차 과제 PR 링크 : https://github.com/woowacourse-precourse/javascript-lotto-7/pull/14

4주차 과제를 제출한지 한참이 지나서야 작성하는 3주차 회고... 3주차와 4주차 과제는 기능을 구현하고 코드를 다듬기에도 너무 바빴어서 계획과 달리 회고를 제 때 작성하지 못했었다. 그리고 4주차가 끝난 뒤에는 프리코스를 하느라 밀렸던 일을 처리하느라 바빴고...

📢 1. 프리코스 3주차 과제 요구사항

https://github.com/woowacourse-precourse/javascript-lotto-7
3주차 프리코스 과제의 요구사항은 다음과 같았다.

로또


📌 과제 진행 요구 사항

  • 미션은 문자열 덧셈 계산기 저장소를 포크하고 클론하는 것으로 시작한다.
  • 기능을 구현하기 전 README.md에 구현할 기능 목록을 정리해 추가한다.
  • Git의 커밋 단위는 앞 단계에서 README.md에 정리한 기능 목록 단위로 추가한다.
  • AngularJS Git Commit Message Conventions을 참고해 커밋 메시지를 작성한다.
  • 자세한 과제 진행 방법은 프리코스 진행 가이드 문서를 참고한다.

🔨 기능 요구 사항

간단한 로또 발매기를 구현한다.

  • 로또 번호의 숫자 범위는 1~45까지이다.
  • 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

  • Node.js 20.17.0 버전에서 실행 가능해야 한다.
  • 프로그램 실행의 시작점은 App.js의 run()이다.
  • package.json 파일은 변경할 수 없으며, 제공된 라이브러리와 스타일 라이브러리 이외의 외부 라이브러리는 사용하지 않는다.
  • 프로그램 종료 시 process.exit()를 호출하지 않는다.
  • 프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 등의 이름을 바꾸거나 이동하지 않는다.
  • 자바스크립트 코드 컨벤션을 지키면서 프로그래밍한다.
  • 기본적으로 JavaScript Style Guide를 원칙으로 한다.

🛠️ 프로그래밍 요구 사항 2

  • indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다.
    - 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
    - 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다.
  • 3항 연산자를 쓰지 않는다.
  • 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라.
  • Jest를 이용하여 정리한 기능 목록이 정상적으로 작동하는지 테스트 코드로 확인한다.
    - 테스트 도구 사용법이 익숙하지 않다면 아래 문서를 참고하여 학습한 후 테스트를 구현한다.

🛠️ 프로그래밍 요구 사항 3

  • 함수(또는 메서드)의 길이가 15라인을 넘어가지 않도록 구현한다.
  • 함수(또는 메서드)가 한 가지 일만 잘 하도록 구현한다.
  • else 예약어를 쓰지 않는다.
    힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다.
  • 구현한 기능에 대한 단위 테스트를 작성한다. 단, UI(System.out, System.in, Scanner) 로직은 제외한다.
    단위 테스트 작성이 익숙하지 않다면 LottoTest를 참고하여 학습한 후 테스트를 작성한다.



👩🏻‍💻 2. 과제 풀이

2.1. 프로젝트 목표

  • 객체 지향 프로그래밍(OOP): 관련된 기능을 클래스로 나누어 객체 간의 협력으로 로또 발매기를 구현한다.
  • 단위 테스트 작성: 기능별로 단위 테스트를 작성해 코드가 의도한 대로 작동하는지 검증한다.
  • 프로그래밍 요구 사항 준수: 제한된 인덴트와 함수 길이를 준수하며, 간결하고 가독성 좋은 코드를 작성한다.

2.2. 프로젝트 소개

이 프로젝트는 간단한 로또 발매기로서, 사용자가 구매한 로또 번호와 당첨 번호를 비교하여 당첨 결과와 수익률을 계산하고, 이를 출력하는 프로그램이다.

2.3. 기능 목록

  1. 구입 금액, 당첨 번호, 보너스 번호 입력 기능
  2. 로또 구매 개수 계산 및 구매 개수 출력 기능
  3. 로또 번호 생성 및 저장 후 출력 기능
  4. 로또 구매 개수만큼 로또 발행 기능
  5. 로또 번호와 당첨 번호를 비교해 당첨 내역 출력 기능
  6. 총 수익률 계산 후 수익률 출력 기능
  7. 잘못된 값을 입력한 경우 에러를 반환하고 다시 입력받기 기능

2.4. 개발 중 체크리스트

2.4.1. 클래스 및 함수 구조

  • ✅ 관련된 함수들을 하나의 클래스로 묶어서 작성했는가?
  • ✅ 클래스는 필드, 생성자, 메서드 순서로 작성했는가?
  • ✅ 한 클래스 또는 함수가 한 가지 기능만 수행하도록 분리했는가?
  • ✅ 중복되는 코드가 있으면 별도의 함수로 분리했는가?
  • ✅ 함수 길이 15줄 이하 규칙을 준수했는가?
  • ✅ else문 대신 조건절에서 값을 반환하는 방식으로 설계했는가?

2.4.2. 요구 사항 검토

  • ✅ 구입 금액, 당첨 번호, 보너스 번호 입력과 예외 처리를 구현했는가?
  • ✅ 로또 번호를 1~45 범위에서 중복 없이 생성하는지 확인했는가?
  • ✅ 오름차순 정렬된 로또 번호를 출력하는 기능을 구현했는가?
  • ✅ 당첨 결과를 1등~5등까지 분류하여 계산하는 로직을 정확히 구현했는가?
  • ✅ 총 수익률 계산과 출력 형식(소수점 둘째 자리 반올림)을 준수했는가?

2.4.3. 입출력 및 에러 처리

  • ✅ 사용자의 입력값이 유효하지 않을 경우 "[ERROR]"로 시작하는 메시지를 출력하고, 다시 입력을 받도록 했는가?
  • ✅ 예외 상황별로 에러 메시지를 출력하도록 구현했는가?
  • ✅ 출력 형식을 요구 사항에 맞게 작성했는가?

2.4.4. 테스트 코드 작성

  • ✅ 단위 테스트를 각 기능 단위로 작성했는가?
  • ✅ UI제외 로직 부분만 단위 테스트했는가?
  • ✅ 예외 상황에 대한 테스트 케이스를 충분히 작성했는가?
  • ✅ 작은 단위의 테스트부터 작성하여 핵심 기능을 검증했는가?

2.4.5. 코드 스타일 및 컨벤션

  • ✅ Node.js 20.17.0 버전인지 확인했는가?
  • ✅ const 상수를 정의하여 의미 있는 이름을 부여하고 하드 코딩된 값 사용을 지양했는가?
  • ✅ 3항 연산자를 사용하지 않았는가?
  • ✅ indent depth가 2를 초과하지 않았는가?

2.4.6. 추가 피드백 사항

  • ✅ 기능 목록을 정기적으로 업데이트하여 최신 상태로 유지했는가?
  • ✅ README.md 파일을 통해 프로젝트의 개요와 기능을 간결하게 설명했는가?
  • ✅ 중복된 코드가 있다면 리팩토링하여 줄였는가?

2.5. 테스트 케이스 작성

2.5.1 🛠️ [ERROR] 를 반환해야하는 경우

공통

📍 입력값이 비었을 경우(trim() 적용 후)

📍 숫자가 아닌 입력을 했을 경우

📍 숫자 사이에 띄어쓰기를 했을 경우

📍 자연수(양의 정수)가 아닌 경우

구매 금액 입력

📍 입력값이 비었을 경우(trim() 적용 후)

📍 1000의 배수가 아닌 경우

📍 너무 많은 금액일 경우(10억 초과)

당첨 번호 입력

📍 입력한 숫자열이 6개가 아닌 경우

📍 1~45까지의 자연수를 입력하지 않은 경우

📍 입력한 숫자열에 중복이 있을 경우

📍 구분자로 ','이 아닌 다른 것을 쓴 경우

보너스 번호 입력

📍 1~45까지의 자연수를 입력하지 않은 경우

📍 당첨 번호와 중복된 숫자를 입력했을 경우

📍 숫자를 여러개 입력했을 경우


💭 3. 개발 회고


📝 4. 배운 내용 정리


🤔 5. 고칠 점

5.1. 우테코 공통 피드백 메모

  • 함수(메서드) 라인에 대한 기준도 적용한다
    - 프로그래밍 요구사항에는 함수의 길이를 15라인으로 제한하는 규칙이 포함되어 있다. 이 규칙은 main() 함수도 동일하게 적용되며, 공백 라인도 한 라인으로 간주한다. 만약 함수가 15라인을 초과한다면, 역할을 더 명확하게 나누고, 코드의 가독성과 유지보수성을 높일 수 있는 신호로 인식하고 함수 분리 또는 클래스 분리를 고려해야 한다.

  • 예외 상황에 대한 고민한다
    - 정상적인 상황을 구현하는 것보다 예외 상황을 모두 고려하여 프로그래밍하는 것이 훨씬 어렵다. 하지만, 이러한 예외 상황을 처리하는 습관을 들이는 것이 중요하다. 코드를 작성할 때는 예상되는 예외를 미리 고려하여, 프로그램이 비정상적으로 종료되거나 잘못된 결과를 내지 않도록 한다.
    예를 들어, 로또 미션에서 고려할 수 있는 예외 상황은 다음과 같다.

    로또 구입 금액에 1000 이하의 숫자를 입력
    당첨 번호에 중복된 숫자를 입력
    당첨 번호에 1~45 범위를 벗어나는 숫자를 입력
    당첨 번호와 중복된 보너스 번호를 입력

  • 비즈니스 로직과 UI 로직의 분리한다
    - 비즈니스 로직과 UI 로직을 한 클래스에서 처리하는 것은 단일 책임 원칙(SRP)에 위배된다. 비즈니스 로직은 데이터 처리 및 도메인 규칙을 담당하고, UI 로직은 화면에 데이터를 표시하거나 입력을 받는 역할로 분리한다. 아래는 비즈니스 로직과 UI 로직이 혼재되어 있다. 비즈니스 로직은 그대로 유지하고, UI 관련 코드는 별도 View 클래스로 분리하는 것이 좋다. 현재 객체의 상태를 보기 위한 로그 메시지 성격이 강하다면, toString() 메서드를 통해 상태를 표현한다. 만약 UI에서 사용할 데이터가 필요하다면 getter 메서드를 통해 View 계층으로 데이터를 전달한다.

    class Lotto {
      #numbers
    
      // 로또 숫자가 포함되어 있는지 확인하는 비즈니스 로직
      contains(numbers) {
          ...
      }
    
      // UI 로직
      print() {
          ...
      }      
    }
    
  • 객체의 상태 접근을 제한한다
    - 필드는 private class 필드로 구현한다.

class WinningLotto {
   #lotto
   #bonusNumber
  • 객체는 객체답게 사용한다
    - Lotto에서 데이터를 꺼내지(get) 말고 메시지를 던지도록 구조를 바꿔 데이터를 가지는 객체가 일하도록 한다. 이처럼 Lotto 객체에서 데이터를 꺼내(get) 사용하기보다는, 데이터가 가지고 있는 객체가 스스로 처리할 수 있도록 구조를 변경해야 한다. 아래와 같이 데이터를 외부에서 가져와(get) 처리하지 말고, 객체가 자신의 데이터를 스스로 처리하도록 메시지를 던지게 한다.
    참고자료: getter를 사용하는 대신 객체에 메시지를 보내자

  • 필드(인스턴스 변수)의 수를 줄이기 위해 노력한다
    - 필드의 수가 많아지면 객체의 복잡도가 증가하고, 관리가 어려워지며, 버그가 발생할 가능성도 높아진다. 따라서 필드에 중복이 있거나 불필요한 필드가 없는지 확인하고, 이를 최소화한다.

  • 성공하는 케이스 뿐만 아니라 예외 케이스도 테스트한다

  • 테스트 코드도 코드다
    - 테스트 코드 역시 코드의 일환이므로, 리팩터링을 통해 지속적으로 개선해 나가는 것이 중요하다. 특히, 반복적으로 수행하는 부분이 있다면 중복을 제거하여 유지보수성을 높이고 가독성을 향상시켜야 한다. 단순히 파라미터 값만 바뀌는 경우라면, 파라미터화된 테스트를 통해 중복을 줄일 수 있다.

  • 단위 테스트하기 어려운 코드를 단위 테스트하기

5.2. 스터디 피드백 메모

  • 사용자가 올바르지 않은 값을 입력 시 재입력을 받는 부분에서 재귀 호출로 인해 비동기 처리가 연속적으로 이루어지기 때문에 await를 써서 리턴해주는 것이 안전할 것 같다
class InputController {
  static async getValidPurchaseAmount() {
    try {
      const input = await InputView.inputPurchaseAmount();
      const purchaseAmount = Parser.parseNumber(input);
      LottoValidator.validatePurchaseAmount(purchaseAmount);
      return purchaseAmount;
    } catch (error) {
      Console.print(error.message);
      return InputController.getValidPurchaseAmount(); // 이 부분을 await 으로

5. 소감

profile
프론트엔드 개발자로 성장하고 싶은 그래픽 디자이너입니다!

0개의 댓글