[우아한테크코스 FE 5기] 프리코스 3주차 로또 미션 회고

Chex·2022년 11월 14일
0

우아한테크코스

목록 보기
3/19
post-thumbnail

💰 로또 미션


🚀 기능 요구 사항

로또 게임 기능을 구현해야 한다. 로또 게임은 아래와 같은 규칙으로 진행된다.

- 로또 번호의 숫자 범위는 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원이다.
  • 당첨 번호와 보너스 번호를 입력받는다.
  • 사용자가 구매한 로또 번호와 당첨 번호를 비교하여 당첨 내역 및 수익률을 출력하고 로또 게임을 종료한다.
  • 사용자가 잘못된 값을 입력할 경우 throw문을 사용해 예외를 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 종료한다.

입출력 요구 사항

입력

  • 로또 구입 금액을 입력 받는다. 구입 금액은 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%입니다.

🔗 관련 링크


🐾 기능목록

[✔️] 구입금액을 입력받는 기능

[✔️] 구입금액이 유효한 입력인지 확인하는 기능

  • ✔️ 숫자가 아닌 경우
  • ✔️ 0으로 시작하는 경우
  • ✔️ 0보다 작거나 같은 경우
  • ✔️ 1,000원으로 나누어 떨어지지 않는 경우

[✔️] 발행할 로또 수량을 구하는 기능

[✔️] 중복되지 않는 6개의 숫자를 뽑는 기능

[✔️] 뽑은 6개의 숫자가 유효한 로또번호인지 확인하는 기능

  • [✔️] n개의 숫자를 뽑았는지 확인하는 기능
  • [✔️] 로또 번호의 숫자 범위가 1~45가 맞는지 확인하는 기능
  • [✔️] 중복인지 확인하는 기능

[✔️] 6개의 로또번호를 리턴하는 기능

[✔️] 구입한 로또 수량만큼 로또를 발행하는 기능

  • [✔️] 6개의 로또 번호를 오름차순 정렬하는 기능

[✔️] 발행한 로또 내역을 출력하는 기능

  • [✔️] 로또 구매 수량 출력
  • [✔️] 로또 번호 출력

[✔️] 당첨 번호를 입력받는 기능

[✔️] 당첨 번호가 유효한 입력인지 확인하는 기능

  • ✔️ ‘,’ 또는 숫자가 아닌 경우
  • ✔️ ','가 2개 이상 연속되는 경우
  • ✔️ ','이 맨 앞에 오는 경우
  • ✔️ ','이 맨 뒤에 오는 경우

[✔️] 당첨번호 문자열을 숫자배열로 변경하는 기능

[✔️] 보너스 번호를 입력받는 기능

[✔️] 보너스 번호가 유효한 입력인지 확인하는 기능

  • ✔️ 숫자가 아닌 경우
  • ✔️ 숫자 범위가 1~45가 아닌 경우
  • ✔️ 입력한 당첨번호에 보너스 번호가 포함되는 경우

[✔️] 발행한 모든 로또의 일치하는 번호 개수를 구하는 기능

  • [✔️] 당첨번호와 일치하는 번호 개수를 구하는 기능
  • [✔️] 보너스 번호와 일치하는지 확인하는 기능

[✔️] 로또 당첨 내역을 구하는 기능(등수 별 당첨 개수)

  • 1등: 6개 번호 일치
  • 2등: 5개 번호 일치 + 보너스 번호 일치
  • 3등: 5개 번호 일치
  • 4등: 4개 번호 일치
  • 5등: 3개 번호 일치

[✔️] 당첨금을 계산하는 기능

  • 1등: 2,000,000,000원
  • 2등: 30,000,000원
  • 3등: 1,500,000원
  • 4등: 50,000원
  • 5등: 5,000원

[✔️] 수익률을 계산하는 기능

  • (당첨금 / 구매금액)x100
  • 소수점 둘째 자리에서 반올림

[✔️] 당첨 통계를 출력하는 기능

  • [✔️] 당첨내역 출력
  • [✔️] 수익률 출력

[✔️] 로또 게임을 종료하는 기능

[✔️] 예외 상황 시 에러 문구를 출력하는 기능

  • 에러 문구는 “[ERROR]”로 시작

🔎 후기

✅ 진행방식

  1. 개발환경: node.js v14.20.1, Visual Studio Code로 세팅하였습니다.
  1. 자바스크립트 코드 컨벤션Airbnb 자바스크립트 스타일 가이드를 참고하였습니다.
  2. 커밋 메시지 컨벤션커밋 메시지 컨벤션 가이드를 참고하였습니다.
  3. 기능을 구현하기 전에 기능목록을 만들었습니다.
  4. 문제의 요구사항 대로 기능 단위로 커밋하는 방식으로 진행했습니다.
  5. indent depth가 3이 넘지 않도록 구현했습니다.(depth 2까지만 허용)
  6. Jest를 이용하여 기능 목록이 정상 동작함을 테스트 코드로 확인하였습니다.
  7. 도메인 로직에 대한 단위 테스트를 진행하였습니다.

✅ 3주차 목표

  • 함수 분리하기(한 가지 일만 하도록 최대한 작게 만들기)
  • 각 함수별 테스트 작성에 익숙해지기
  • 클래스(객체)를 분리하는 연습하기
  • 도메인 로직에 대한 단위 테스트를 작성하는 연습하기

✅ 3주차 공통피드백

  • 함수(메서드)라인에 대한 기준

    • 공백 라인도 한 라인에 해당 & 15라인이 넘어간다면 함수 분리 고민하기
  • 예외상황에 대해 고민하기: 예외상항을 모두 고려해 프로그래밍 하도록 고민한다.

  • 비즈니스 로직과 UI 로직을 분리하기: 분리하지 않으면 단일 책임의 원칙에도 위배된다.

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

    • 필드는 private class필드로 구현한다.
    • 객체의 상태를 외부에서 직접 접근하는 방식을 최소화 하는 이유에 대해서는 스스로 찾아본다.
  • 객체는 객체스럽게 사용하기

    • Getter 메서드만을 가지는 객체는 객체스럽지 않다.
    • 객체가 로직을 수행하도록 하자
    • Lotto에서 데이터를 꺼내지(get) 말고 메시지를 던지도록 구조를 바꿔 데이터를 가지는 객체가 일하도록 한다.
    • getter를 사용하는 대신 객체에 메시지를 보내자
  • 필드 수를 줄이기 위해 노력하기

    • 필드 수가 많으면 객체의 복잡도와 버그 발생 가능성을 높일 수 있다.
    • 중복 또는 불필요한 필드를 확인하자
  • 성공하는 케이스 뿐만 아니라 에외에 대한 케이스도 테스트하기

    • 특히 경계값을 꼼꼼하게 확인하자
  • 테스트 코드도 코드다

    • 테스트 코드도 리팩터링을 통해 개선하자
    • test.each 등을 사용해 중복을 개선하자
  • 테스트를 위한 코드는 구현 코드에서 분리하기

    • 테스트를 위한 편의 메서드를 코드에 구현하지 않기
    • 통과하기 위한 코드 변경, 로직 구현 X
  • 단위 테스트하기 어려운 코드를 단위 테스트하기

📖 배운점

1. 클래스의 필드에 관하여

3주차 미션의 프로그래밍 요구사항 중 아래와 같은 요구사항이 있었다.

Lotto에 필드를 추가할 수 없다.

그동안은 자바스크립트 클래스의 프로퍼티, 메서드라는 용어에 익숙해져 있어서 필드는 내가 아는 그 필드(ex. Java에서의 클래스 필드)가 맞는지 의문이 들었다.

모던자바스크립트책을 찾아보니 (p.439)
클래스 필드(또는 멤버)란? 클래스가 생성할 인스턴스프로퍼티를 가리킨다고 한다.
즉, 자바스크립트의 생성자 함수에서 this에 추가한 프로퍼티를 클래스 기반 객체지향 언어에서는 클래스 필드라고 부른다.(내가 아는 그 필드를 말하는 게 맞았다.)

클래스 필드 정의는 2가지 방법으로 할 수 있다.(ES2022)
1. constructor에서 필드(인스턴스 프로퍼티)를 정의하는 방식
2. 클래스 바디에서 필드(인스턴스 프로퍼티)를 정의하는 방식
👉 외부 초기값으로 필드를 초기화할 필요가 있다면 1번, 없다면 1번과 2번 모두 사용 가능하다.
👉 주의) 클래스 바디에서 정의하는 경우 this에 클래스 필드를 바인딩해서는 안된다.
🙋🏻 왜❓ this는 클래스의 constructor메서드 내에서만 유효하기 때문이다.

2. 클래스의 private 필드에 관하여

클래스 필드에 대해 알아보려고 하다가 모던자바스크립트책을 더 읽어보니 (p.440) 아래와 같은 문구가 있었다.

자바스크립트의 클래스에서 인스턴스 프로퍼티를 선언하고 초기화하려면 반드시 constructor 내부에서 this에 프로퍼티를 추가해야 한다.

자바스크립트의 클래스 몸체에는 메서드만 선언할 수 있다.

그럼 내가 본 클래스 바디에 선언되어있는 Lotto 클래스의 #numbers필드는 무언인가?
라는 생각이 들었는데 바로 밑에 설명이 나와있었다.

자바스크립트에서도 인스턴스 프로퍼티를 마치 클래스 기반 객체지향 언어의 클래스 필드처럼 정의할 수 있는 새로운 표준 사양인 "Class field declararations"가 2021년 1월, TC39프로세스의 stage3(candidate)에 제안되어 있다.

현재 시점(2022. 11.)에서 다시 찾아보니 Class field declarations는 stage4를 거쳐 ECMAScript 제13판(ES2022라고도 불린다)에 포함되었다고 한다. 이제 ECMAScript의 정식 표준 사양으로 승급된 것이다.

Lotto클래스의 #numbersprivate필드인데 이 private필드 정의 또한 이 ECMAScript2022에 포함되어 정식 표준 사양으로 승급되었다.

private 필드의 특징은 클래스 내부에서만 참조할 수 있다는 것과 반드시 클래스 바디에 정의해야한다는 것이다. (private필드를 직접 constructor에 정의하면 에러가 발생한다.)

참고포스팅

3. 정적 메서드와 프로토타입 메서드의 차이에 관하여

2주차 때도 정적 메서드 사용에 관하여 고민을 했었는데 새로 알게된 것이 있어 기록하고자 한다.

  • 정적 메서드는 클래스로 호출한다.
  • 프로토타입 메서드는 인스턴스로 호출한다.
  • 정적 메서드는 인스턴스 프로퍼티를 참조할 수 없다.
  • 프로토타입 메서드는 인스턴스 프로퍼티를 참조할 수 있다.

프로토타입 객체(또는 프로토타입)란❓

  • 자바스크립트에서 객체 간 상속을 구현하기 위해 사용되는 객체로 어떤 객체의 상위(부모) 객체의 역할을 하는 객체이다.
  • 다른 객체에 공유 프로퍼티, 공유 메서드를 제공한다.
  • 인스턴스의 프로퍼티(속성)를 참조해야 한다면 this를 사용해야 하므로 이런 경우엔 static메서드 대신 프로토타입메서드로 정의해야 한다.
  • 반대로, 프로토타입메서드는 반드시 인스턴스를 생성한 다음 인스턴스로 호출해야 하므로 this를 사용하지 않는 메서드는 정적 메서드로 정의하는 것이 좋다. (모던자바스크립트 p.433)
  • 정적 메서드는 애플리케이션 전역에서 사용할 유틸리티 함수를 전역 함수로 정의하지 않고 메서드로 구조화할 때 유용하다.

참고포스팅

4. 자바스크립트의 오버로딩에 관하여

  • 오버라이딩❓ 상위 클래스가 가지고 있는 메서드를 하위 클래스가 재정의하여 사용하는 방식
  • 오버로딩❓ 함수의 이름은 동일하지만 매개변수의 타입 또는 개수가 다른 메서드를 구현하고 매개변수에 의해 구별하여 호출하는 방식
  • 자바스크립트는 오버로딩을 지원하지 않는다.

로또 미션에서 Lotto클래스는 로또번호(#numbers)를 필드로 가지고 있는데, 이것은 숫자배열이었다. 나는 당첨번호 또한 Lotto클래스 생성자함수의 인자로 받아서 #numbers에 할당하고자 했다. 하지만 당첨번호는 처음 입력받을 때 '1,2,3,4,5,6'형식의 string이었기 때문에 숫자배열로 만들어주기 위해 Lotto클래스의 생성자함수를 오버로딩하여 Lotto클래스 내에서 stringarray로 변환하는 작업을 거치려고 했었다. 그런데 찾아보니 자바스크립트는 오버로딩을 지원하지 않는다는 사실을 알게 되었다..

어찌되었든 최종적으로 당첨번호는 굳이 객체로 만들지 않아도 될 것 같다는 생각을 했기 때문에 당첨번호는 App클래스에서 숫자배열로 바꿔서 사용하였다.

5. 네임스페이스 객체 vs 상수를 객체로 분리하는 것에 관하여

전역에 네임스페이스 역할을 담당할 객체를 생성하고 전역 변수처럼 사용하고 싶은 변수를 프로퍼티로 추가하는 방법이다. (모던자바스크립트, p.205)
네임스페이스를 분리해서 식별자 충돌을 방지하는 효과는 있으나 네임스페이스 객체 자체가 전역 변수에 할당되므로 그다지 유용해 보이지는 않는다.

전역변수는 전역객체의 프로퍼티이다.

  • 웹브라우저에서 실행한다면 전역객체window객체
  • Node.js에서 실행한다면 전역객체global객체

자바스크립트는 파일이 분리되어 있어도 전역 스코프를 공유한다는 문제점이 존재한다. 따라서 동일한 이름으로 명명된 변수로 예상치 못한 결과를 가져올 수 있다.

내가 3주차에서 사용한 방법(상수를 모아놓을 객체를 생성하고 메시지를 프로퍼티로 추가하는 방법)도 이런 경우에 해당하나싶어서 한번 찾아보았다.

const MESSAGE = Object.freeze({
  START_GAME: '로또 게임을 시작합니다.',
  FINISH_GAME: '로또 게임을 종료합니다.',
  ENTER_PURCHASE_AMOUNT: '구입금액을 입력해 주세요.\n',
  ENTER_WINNING_NUMBERS: '당첨 번호를 입력해 주세요.\n',
  ENTER_BONUS_NUMBER: '\n보너스 번호를 입력해 주세요.\n',
  WINNING_STATISTICS: '\n당첨 통계\n---',
  FIRST_PLACE: '6개 일치 (2,000,000,000원) - ',
  SECOND_PLACE: '5개 일치, 보너스 볼 일치 (30,000,000원) - ',
  THIRD_PLACE: '5개 일치 (1,500,000원) - ',
  FOURTH_PLACE: '4개 일치 (50,000원) - ',
  FIFTH_PLACE: '3개 일치 (5,000원) - ',
  EA: '개',
  PURCHASE_QUANTITY: (quantity) => `\n${quantity}개를 구매했습니다.`,
  TOTAL_RATE_OF_RETURN: (rateOfReturn) => `총 수익률은 ${rateOfReturn}%입니다.\n`,
});

아래 두 가지 이유로 내가 사용한 방법은 객체 자체가 전역 변수에 할당되는 문제는 발생하지 않을 것 같다고 생각했다.

1. ES6모듈을 사용하면 더는 전역변수를 사용할 수 없다. ES6 모듈은 파일 자체의 독자적인 모듈 스코프를 제공한다. (모던자바스크립트, p.207)
2.let으로 선언한 전역변수는 전역객체의 프로퍼티가 아니다.
window.foo와 같이 접근할 수 없다. let전역변수는 보이지 않는 개념적인 블록(전역 렉시컬 환경의 선언적 환경 레코드) 내에 존재하게 된다. (모던자바스크립트, p.214)
ES6의 let, const키워드로 선언한 전역변수는 전역객체의 프로퍼티가 되지 않고 개념적인 블록 내에 존재하게 된다.(모던자바스크립트, p.370)

나는 constant.js파일을 만들어서 MESSAGE객체에 프로퍼티를 추가하여 사용하였는데 내 결론은 아래와 같다.

  • ES6 모듈은 파일 자체의 독자적인 모듈 스코프를 제공하기 때문에 MESSAGE객체는 전역 변수에 할당되지 않을 것이고,

  • MESSAGE객체를 const로 선언해 주었기 때문에 MESSAGE 객체는 전역객체의 프로퍼티가 아니다!

6. expect().toThrow()의 동작에 관하여

구매금액이 숫자가 아니라면 예외가 발생하는 테스트 코드

구매금액 숫자가 아니라면 예외가 발생하도록 하는 것에 대한 테스트 코드를 작성하는데 궁금한 점이 있어서 찾아보았다. 위 코드에서 expect 안에 원래 함수를 화살표 함수로 감싼 후 던져줘야 toThrow에서 예외를 캐치하는 것이었다. (주석처리한 코드는 제대로 작동 안함) 내가 아래 게시글을 보고 이해한 바로는 expect에서 이미 에러를 던져줘서(?) toThrow에서 에러를 캐치하지 못하는 것이므로 화살표 함수로 원래 함수를 감싸서 던져줘야 된다고 한다. toThrow를 사용할 때 주의해야겠다!
참고 포스팅

✏️ 소감문

1. 도메인 로직에 대한 단위 테스트를 작성하는 연습을 하면서 느낀점

3주차 미션이 나오고 가장 먼저 한 일은 이번 주 목표에 대해 의미를 되새기는 일이었습니다.

처음엔 3주차 목표에 나온 '도메인 로직(비즈니스로직)'이란 무엇인지 마음에 와닿지 않았었는데 '숫자야구 피드백 강의'를 듣고 프리코스 커뮤니티에서 도메인과 관련한 게시글을 보게 되면서 힌트를 얻을 수 있었습니다.

"이 코드가 현실 문제에 대한 의사결정을 하고 있는가?"라는 질문을 해보면 어떤 게 도메인 로직인지 알 수 있다고 합니다. 로또 미션에서의 도메인 로직은 무엇일까 제가 작성한 기능목록을 보면서 생각해보았습니다.

로또 미션의 큰 로직은 “로또구입 ➡️ 로또발행 ➡️ 등수계산 ➡️ 당첨금계산 ➡️ 수익률계산”이라고 보고 그럼 "로또구입, 로또발행, 등수계산, 당첨금계산, 수익률계산"이라는 현실 문제들에 대한 의사결정을 하는 코드는 무엇일까를 생각해봤을 때, 제가 생각한 로또 미션의 도메인로직을 아래와 같이 뽑아낼 수 있었습니다.

로또 구입
 1. 구입금액이 유효한 입력인지 확인하는 기능 ➡️ 로또 구입이 가능한지에 대한 의사결정
 2. 발행할 로또 수량을 구하는 기능 ➡️ 로또 구입 매수에 대한 의사결정

로또 발행
 1. 중복되지 않는 6개의 숫자를 뽑는 기능 ➡️ 로또발행이라는 서비스 수행
 2. 뽑은 6개의 숫자가 유효한 로또번호인지 확인하는 기능 ➡️ 로또발행이 가능한지에 대한 의사결정

등수 계산
 1. 당첨번호와 일치하는 번호 개수를 구하는 기능 ➡️ 등수계산이라는 서비스 수행
 2. 보너스 번호와 일치하는지 확인하는 기능 ➡️ 2등을 확인하기 위한 의사결정

당첨금 계산
 1. 로또 당첨 내역을 구하는 기능(등수 별 당첨 개수) ➡️ '등수 별 당첨 개수'에 따라 당첨금 결정
 2. 당첨금을 계산하는 기능 ➡️ 당첨금 계산이라는 서비스 수행

수익률 계산
 1. 구입금액이 유효한 입력인지 확인하는 기능 ➡️ '구입금액'에 따라 수익률 결정
 2. 당첨금을 계산하는 기능 ➡️ '당첨금'에 따라 수익률 결정
 3. 수익률을 계산하는 기능 ➡️ 수익률 계산이라는 서비스 수행

이렇게 도메인 로직에 대해 생각해보고 이에 대한 단위테스트 구현을 시작하였습니다.

이번 주에도 테스트에서 문제가 있었는데..
구매한 로또번호 배열을 출력하는 부분에서 테스트 실패가 나왔습니다.

또 이유를 한참 고민하다가 Expect에서 expect.arrayContaining(array)라는 메서드가 있는 것을 발견하고 나서 다시 ApplicationTest.js의 테스트 코드를 보니 여기서는 expect.stringContaining()메서드를 사용하고 있었습니다. 처음 저의 코드에서는 단순히 Console.print(로또번호를 담은 배열)을 해주었기 때문에 출력결과는 같아도 string타입이 아니어서 테스트를 통과하지 못 했던 것이었습니다. 이번 주에도 테스트에 대한 새로운 메서드를 알게 되었습니다.

2. 클래스(객체) 분리에 대해 학습하면서 느낀점

어떻게 클래스(객체)를 분리할까 고민했습니다. LottoApp클래스는 나뉘어져 있는 상태에서 클래스 전역에서 필요한 유틸메서드들은 Util클래스로 분리한 후 구현을 시작했습니다.

이번 주 로또 미션에서는 App클래스에서 메서드체이닝을 사용했는데 App 클래스 내에 유효성체크를 하는 메서드들이 섞여있는 것은 코드 가독성이 떨어져보여서 이 체이닝에서 분리하고 싶다는 생각을 했습니다. 그래서 유효성체크를 하는 기능은 Validation클래스로 분리했고 에러메시지나 출력메시지들은 canstant.js에 객체로 분리해주었습니다. 구현 후에 좀더 고쳐야할 게 있을지 찾아보다가 포비님이 쓰신 글을 보게 되었습니다.

상태 데이터를 가진다고 무조건 setter, getter 메소드를 만드는 습관을 버리자. setter, getter 메소드는 정말 필요한 순간까지 뒤로 미루는 습관을 만들면 좋겠다. 아예 추가하지 않는 연습을 하면 더 좋겠다.

위 내용을 보고 또 저의 잘못을 깨달았습니다. Lotto객체에 로또번호(상태데이터)를 조회하는 getLottoNumbers라는 메서드를 만들어놨기 때문입니다. 이 메서드를 사용하지 않고 어떻게 로또번호와 당첨번호, 보너스번호를 비교하는 로직을 처리할 수 있을지 생각했는데 해당 게시글에서 힌트를 얻을 수 있었습니다.

상태 데이터를 꺼내 로직을 처리하도록 구현하지 말고 객체에 메시지를 보내 일을 하도록 리팩토링한다.

Lotto클래스에 대해 다시 살펴보았습니다.

  • 아는 것: 로또번호
  • 하는 일: 로또번호 유효성 검사, 로또번호 조회(반환)

여기서 로또번호를 꺼내 당첨번호 및 보너스번호와 비교하는 로직을 처리하지 않고 Lotto객체에서 로직을 처리하도록 리팩토링해주었습니다.

Lotto클래스

  • 아는 것: 로또번호
  • 하는 일: 로또번호 유효성 검사, 로또번호에 보너스번호 포함여부 반환, 로또번호와 당첨번호의 일치하는 번호 개수 반환, 로또번호 화면출력

위와 같이 구현함으로써 얻는 장점은 무엇일지 생각해보았는데 객체의 의존성을 줄일 수 있고, 이게 바로 캡슐화이고 객체 간의 대화를 구현한게 아닌가하고 생각했습니다. 앞으로도 getter, setter를 구현하기보단 객체 안에서 로직을 처리할 수 있는 코드를 짜야겠다고 다짐했습니다.

스스로 고민하고 문제가 있으면 해결하는 과정들을 매주 반복하면서 저에겐 자신감이 생기고 있습니다.

1주차 공통피드백에 링크되어있던 Git특강에서 메이커준님이 하신 말씀이 떠올랐습니다. 미리 많이 맞아야(?) 두려움이 덜하다라는 뉘앙스의 말이었는데 Git에 대한 두려움을 없애자라는 의미였지만 지금 저의 경우에도 적용될 수 있을 것 같습니다. 수많은 에러와 문제상황을 만나다보면 그것에 대한 두려움은 작아질 것이고 어느 방향으로 가야 이 문제를 해결할 수 있을지 차분히 고민할 수 있는 여유를 가지게 될 것이라 생각합니다. (사실 문제는 어려움이 아니라 낯섦이기 때문에 낯선 것을 익숙하게 만들면 된다고 생각합니다.)

일주일 전인 2주차랑 비교해 봤을 때도 3주차에 npm test 실패가 떴을 땐 크게 당황하지 않고 "아 이건 테스트 코드를 다시 보고, jest에 관한 문서를 찾아보면 되겠다."하고 문제 해결의 방향성을 잡을 수 있었습니다.

그리고 이런 경험들을 통해 우테코의 교육 철학을 다시금 느낄 수 있었습니다. 잡아준 물고기를 받는 것이 아닌 물고기를 잡는 방법을 스스로 터득하는 방식으로요. 티칭에 익숙했던 저이지만 확실히 스스로 해결할 힘을 길러주는 것 그리고 학습의 즐거움을 알게 해주는 것은 코칭이라고 느꼈습니다.

🥹 3주차 공통피드백에서 느낀점

1. 비즈니스 로직과 UI로직을 분리한다.

객체의 상태데이터를 꺼내지 않고 객체 안에서 로직을 처리하기 위해 로또미션에서 printLotto객체 안에서 해주었는데 이렇게 하면 안 되는 거였다! getter를 무조건 사용하지 말라는 말이 아니라 출력을 위한 값 등 순수 값 프로퍼티를 가져오기 위해서라면 어느정도 getter는 허용 되는 것이었다! 나의 의문이 풀렸다.

2. 객체의 상태 접근을 제한한다.

이것은 코드리뷰를 하면서 느낀 것인데 외부에서 객체의 상태를 직접 접근한다면 상태데이터가 언제 어떻게 변경이 될지 예측이 잘 되지 않고 코드예측 및 파악이 더 힘들어지는 것 같다. 그리고 이런 부분이 유지보수를 어렵게 하는 이유가 될 것 같다. 따라서 이러한 이유로 객체의 상태를 외부에서 직접 접근하는 방식을 최소화해야 한다고 생각된다.

3. 객체는 객체스럽게 사용한다.

이 부분을 3주차 공통피드백에서 보고 너무 놀랐다.
위 소감문에서 썼듯이 로또미션 구현 중 객체를 객체스럽게 사용하도록 리팩토링해라.라는 포비님의 글을 보게 되어서 getLottoNumbers를 사용하지 않고 Lotto 객체에서 로직을 수행하도록 리팩터링해주었는데 마침 이것과 같은 피드백이 나와서 말이다..! 미션에서 의도한 바를 미리 알고 적용해봤다는 점에서 내가 잘 가고있구나라고 생각했다.

4. 필드의 수를 줄이기 위해 노력한다.

여기에 LottoResult라는 클래스가 예시로 나오는데 나는 객체라는 것의 개념을 착각하고 있었던 것 같다. 로또라는, 명확하게 파악되는 것들만 객체로 만들려고 했었다. LottoResult라는 클래스를 생성해낼 생각을 왜 못했을까? 오늘도 반성한다. 그리고.. 반성 끝! 4주차 미션엔 클래스 분리를 똑부러지게 잘 하고싶다.

Be the best version of you!

profile
Fake It till you make It!

0개의 댓글

관련 채용 정보