본문 바로가기
Developer/후기

[FE] 우아한 테크코스 6기 프리코스 1주차 회고: 숫자야구 게임

by 청량리 물냉면 2023. 10. 26.
반응형

 

기능 요구 사항

 

구현 기능 목록

구현한 기능 목록은 다음과 같다.

  • 입력받기
    • 서로 다른 3자리 수 입력받기
    • 게임이 끝난 경우 재시작/종료를 구분하는 1과 2 중 하나의 수를 입력받기
    • (예외 1) 3자리 수가 아닌 수를 입력받은 경우
    • (예외 2) 3자리 수이지만 수에 동일한 수가 포함된 경우
    • (예외 3) 게임 종료 시 1, 2 이외의 다른 수를 입력받은 경우
  • 입력한 수에 대한 결과 연산하기
    • 컴퓨터가 랜덤으로 3개의 수를 고르기
    • 사용자가 입력한 값의 자릿수와 랜덤수를 차례대로 비교하기
  • 결과 출력하기
    • 같은 수가 같은 자리에 있으면 스트라이크
    • 다른 자리에 있으면 볼
    • 같은 수가 전혀 없으면 낫싱
  • 반복
    • 사용자가 랜덤수를 맞출 때까지 숫자 입력받기 반복
    • 게임 종료 후 사용자가 1, 2를 입력하면 랜덤수를 초기화하고 재시작

 

순서도 작성

클래스 설계

📂 game

ㄴ📄 GameLogic.js ...게임에 필요한 로직들의 모음(랜덤수 생성, 숫자야구 결과 연산 로직)

ㄴ📄 GameManagement.js ...게임 시작부터 종료까지 루틴을 관리하는 로직

📂 exception

ㄴ📄 Exception.js ...입력패턴 검사 및 오류 리턴

 

챌린징

1. 게임 종료-재시작 시 발생하는 문제

숫자 야구 게임을 시작합니다.
[ 1, 3, 9 ]
게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.11

 1스트라이크
숫자를 입력해주세요 :

게임 종료 후 재시작 시 이런 식으로 순서가 뒤죽박죽이 되어 나타나는 문제가 발생했다. 

 

import { MissionUtils } from "@woowacourse/mission-utils";
import GameLogic from "./GameLogic.js";

class GameManager {
  constructor() {
    this.gameLogic = new GameLogic();
    this.replay = true;
  }

  startGame() {
    MissionUtils.Console.print("숫자 야구 게임을 시작합니다.");
    this.playGame();
    this.replay = this.restartGame();
  }

  async playGame() {
    let computerNumber = this.gameLogic.generateNewNumber();
    let userNumber = "";

    while (true) {
      const answer = await MissionUtils.Console.readLineAsync(
        "숫자를 입력해주세요 : "
      );

      userNumber = this.gameLogic.checkGameResult(computerNumber, answer);
      MissionUtils.Console.print(userNumber);

      if (userNumber === "3스트라이크") {
        MissionUtils.Console.print("3개의 숫자를 모두 맞히셨습니다! 게임 종료");
        return;
      }
    }
  }

  async restartGame() {
    const answer = await MissionUtils.Console.readLineAsync(
      "게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요."
    );

    if (answer === 1) {
      return true;
    } else if (answer === 2) {
      return false;
    }
  }
}

export default GameManager;

위와 같은 기존 코드에서 고민을 하다가, 이번에는 playGame() 함수에서 restartGame() 함수를 반환하는 식으로 코드를 고쳐보았다.

...

class GameManager {
  ...

  async playGame() {
    ...
      if (userNumber === "3스트라이크") {
        MissionUtils.Console.print("3개의 숫자를 모두 맞히셨습니다! 게임 종료");
        return this.restartGame();
      }
    }
  }

  async restartGame() {
    const answer = await MissionUtils.Console.readLineAsync(
      "게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요."
    );

    if (answer === 1) {
      return this.playGame();
    } else if (answer === 2) {
      return false;
    }
  }
}

export default GameManager;

하지만 여전히 코드 실행 실패😭.

재시작 함수 실행 후 1이든 2든 입력하기만 하면 그대로 console이 종료되어 버린다. 

한참을 헤매다가 Chat GPT에게 원인을 물어보고 답을 얻었는데 생각보다 간단한 곳에서 실수를 했었다.

콘솔로 받은 입력의 자료형은 "문자열"인데 answer 값을 비교할 때 나는 Number형을 사용했다. 

  async restartGame() {
    const answer = await MissionUtils.Console.readLineAsync(
      "게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요."
    );

    if (answer === "1") {	//이 부분 문자열 비교로 변경
      return this.playGame();
    } else if (answer === "2") {
      return false;
    }
  }

이렇게만 바꿔주면, 제대로 동작한다!

 

2. 리팩토링 도중 오류 발생

import { MissionUtils } from "@woowacourse/mission-utils";
import GameLogic from "./GameLogic.js";
import Exception from "../Exception/Exception.js";

class GameManager {
  constructor() {
    this.gameLogic = new GameLogic();
  }

  startGame() {
    MissionUtils.Console.print("숫자 야구 게임을 시작합니다.");
    this.playGame();
  }

  async playGame() {
    let computerNumber = this.gameLogic.generateNewNumber();
    let userNumber = "";

    while (true) {
      const answer = await MissionUtils.Console.readLineAsync(
        "숫자를 입력해주세요 : "
      );

      Exception.baseballException(answer);

      userNumber = this.gameLogic.checkGameResult(computerNumber, answer);
      MissionUtils.Console.print(userNumber);

      if (userNumber === "3스트라이크") {
        return this.strike();
      }
    }
  }

  strike() {
    MissionUtils.Console.print("3개의 숫자를 모두 맞히셨습니다! 게임 종료");
    return this.restartGame();
  }

  async restartGame() {
    const answer = await MissionUtils.Console.readLineAsync(
      "게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요."
    );

    if (answer === "1") {
      return this.playGame();
    } else if (answer === "2") {
      return false;
    } else {
      throw new Error("[ERROR] 숫자가 잘못된 형식입니다.");
    }
  }
}

export default GameManager;

위의 코드에서 사용자에게 입력받는 부분을 따로 빼서 아래와 같이 리팩터링 했다. 

...

class GameManager {
  constructor() {
    this.gameLogic = new GameLogic();
  }

  async inputUserAnswer(ment) {
    const answer = await MissionUtils.Console.readLineAsync(ment);
    return answer;
  }

  ...

  async playGame() {
    let computerNumber = this.gameLogic.generateNewNumber();
    let userNumber = "";
    let answer = "";

    while (true) {
      answer = this.inputUserAnswer("숫자를 입력해주세요 : ");
      // const answer = await MissionUtils.Console.readLineAsync(
      //   "숫자를 입력해주세요 : "
      // );

      console.log(answer);

      Exception.baseballException(answer);

      userNumber = this.gameLogic.checkGameResult(computerNumber, answer);
      MissionUtils.Console.print(userNumber);

      if (userNumber === "3스트라이크") {
        return this.strike();
      }
    }
  }

  ...
}

export default GameManager;

 

그 결과 아래와 같은 오류가 발생했다.

숫자를 입력해주세요 : Promise { <pending> }
file:///C:/Users/y/Documents/javascript-baseball-6/Src/Exception/Exception.js:3
    const answerSet = new Set(answer.split(""));

TypeError: answer.split is not a function
    at Exception.baseballException (file:///C:/Users/y/Documents/javascript-baseball-6/Src/Exception/Exception.js:3:38)
    at GameManager.playGame (file:///C:/Users/y/Documents/javascript-baseball-6/Src/game/GameManager.js:33:17)
    at GameManager.startGame (file:///C:/Users/y/Documents/javascript-baseball-6/Src/game/GameManager.js:17:10)
    at App.play (file:///C:/Users/y/Documents/javascript-baseball-6/Src/App.js:9:22)
    at file:///C:/Users/y/Documents/javascript-baseball-6/Src/App.js:14:5
    at ModuleJob.run (node:internal/modules/esm/module_job:194:25)

Node.js v18.17.1

입력받는 부분에서 사용자 입력까지 대기 시간이 필요한데, 바로 다음 코드를 진행해서 생긴 오류라고 보고 다음과 같이 고쳤다.

 

async playGame() {
    ...

    while (true) {
      answer = await this.inputUserAnswer("숫자를 입력해주세요 : ");

      Exception.baseballException(answer);
      ...
      }
    }
  }

이제 잘 동작한다.

(이 리팩토링 코드는 후에 삭제했다.)

 

3. 테스트 코드 통과 실패

node App.js를 통해 모든 동작이 잘 구현된 걸 확인했는데, 테스트를 돌리니 오류가 발생했다.

 RUNS  __tests__/ApplicationTest.js
C:\Users\y\Documents\javascript-baseball-6\src\exception\Exception.js:22
        throw new Error("[ERROR] 숫자가 잘못된 형식입니다.");

테스트 코드는 처음 사용해 봤는데, 계속해서 오류가 발생해서 이 부분에서 시간을 매우 소모했다.

처음에는 아예 테스트 코드라는 개념 자체를 알 수가 없었기 때문에 테스트 실행 자체에 애를 먹었고 대체 이 에러가 왜 발생하는지 알 수가 없어 답답했다. 알고 보니 테스트 케이스에서 정상적인 입력을 주었는데도 에러를 throw 한 경우 위와 같은 에러가 발생했다.

클래스를 옮겨 다니며 코드를 변경하고 테스트 진행해 본 결과 에러 원인은 GameManage() 클래스에 있었다. 해결하지는 못 했지만...

에러 해결을 위해서 입력받는 부분을 async, await를 이용하지 않고 받아보기도 하고(이러면 재귀로 playGame()을 수행하는 과정에서 하나의 입력을 여러 번 받는 오류가 생긴다. 예를 들어서 내가 1을 입력하면 콘솔창에는 11이 쳐지는 식으로...) while문으로 입력받던 걸 재귀로 바꿔보기도 하고 리팩토링이 원인인가 싶어서 기존으로 되돌리고 다시 코딩해 봤는데 그래도 에러가 해결되지는 않았다. 위의 에러 대신 테스트 케이스가 실패했다는 에러도 뜨고 여러 가지 에러를 마주했다.😂 결국 마지막까지 에러를 해결하지 못하고 제출해서 아쉬움이 남는다.

그래도 이번에 테스트 코드라는 걸 다루어 보았으니 앞으로는 테스트 코드를 염두에 두고 테케에 통과하는 코드를 작성할 수 있을 것 같다. 

 

소감

이번 과제를 진행하면서 자바스크립트 기초에 대한 지식이 아직 많이 부족하다는 사실을 깨닫게 되었다. 이전까지 js 라이브러리인 리액트를 사용했었는데 정작 자바스크립트에 대한 기초 지식이 부족했었다니 회의감이 들었다.😭  앞으로 자바스크립트를 더 깊게 공부해 기본부터 채워나가야겠다는 생각을 하게 된 계기가 되었다.

이번 과제를 진행하며 가장 어려웠던 점은 첫 번째로, 기능에 따라 클래스를 구분하고 함수를 분리하는 일이었다. 
평소처럼 아무런 설계 없이 코드를 먼저 작성하려다가 코드를 어떻게 짜야할지 가늠이 잡히지 않아 시간이 지체되었다. 이에 다이어그램도 그려보고 전체적인 파일구조를 먼저 짠 뒤 코드를 작성하기 시작하였고, 이전과는 달리 코드 짜는 데 시간이 훨씬 단축되었다. 
이러한 경험을 통해 코드 작성 전 설계에 공을 들인다면 프로그래밍을 하는 데 소요되는 시간이 훨씬 줄어들고 효율적인 코드를 짤 수 있다는 사실을 깨달았다.

두 번째로 어려웠던 점은 테스트 코드 실행부터 분석, 실제 코드에 적용까지 일체의 과정이다. 
테스트 코드를 처음 접해보았기에 처음에는 테스트 케이스를 어떻게 사용해야 하는지 알지 못했다. 그래서 늘 하던 대로 코드를 구현하며 중간중간 node App.js 실행을 통해 직접 사용자의 답변을 입력해 출력을 확인하였다. 이러한 방법으로 제가 상정해 두었던 모든 예외를 통과한 코드를 모두 구현하였지만, 막상 npm test를 통해 테스트 케이스 검증을 해 본 결과 테스트 케이스 도중 'Error: [ERROR] 숫자가 잘못된 형식입니다.'라는 에러가 발생하며 도중에 테스트 실행이 종료되는 현상이 발생했다... 이를 해결하기 위해 while문을 재귀로 바꾸어보고 코드 분리도 해보고 전체적인 코드 수정도 진행해 보았지만 아쉽게도 테스트케이스는 통과하지 못했다.
처음부터 테스트케이스를 염두에 두고 코드가 어느 정도 완성될 시점부터 테스트케이스를 실행해 보며 코드 작성을 했어야 했는데, 모든 코드 작성이 끝나고 테스트케이스를 실행해 본 것이 패착이었던 것 같다. 기능 구현을 완료했음에도 테스트 케이스에 통과하지 못한 점이 아쉽지만, jest를 이용한 테스트 코드를 직접 분석해 보고 테스트 코드 실행법과 작성법까지 많은 지식을 알 수 있게 되어 저의 성장에 발판이 되는 기회였다.

이번 과제는 제출기한 내에 테스트 코드 통과를 하지 못했지만 이전보다 자바스크립트와 테스트 코드에 친숙해질 수 있었던 기회였다고 생각한다. 앞으로 있을 다른 과제를 통해서도 더욱 성장하고 발전해 나가고 싶다.

 

 

새롭게 학습한 내용

Git 커밋 컨벤션

주로 사용한 제목은 Feat, Refactor, Docs

 

Git | git 커밋 컨벤션 설정하기

개발자로 시작한지 얼마 안되고 나서, 첫 직장은 지금도 다니고 있는 모든것을 처음부터 새로 시작하는 스타트업이다.프론트엔드는 작성자 혼자 뿐이었고, 아무것도 모르는 주니어 개발자가 하

velog.io

 

JS 클래스

js 클래스는 한 번도 사용해 본 적이 없어서 새로 공부해야 했다. 기본적인 로직은 다른 언어와 비슷하다.

 

클래스와 기본 문법

 

ko.javascript.info

 

JS 모듈

클래스 대신 모듈을 사용할 수 있을까 하고 검색해 본 내용.

 

[JS] 모듈에 대한 이해와 사용법 - 배하람의 블로그

모듈(module)이란? 모듈이란 여러 기능들에 관한 코드가 모여있는 하나의 파일 로 다음과 같은 것들을 위해 사용한다. 유지보수성 : 기능들이 모듈화가 잘 되어있다면, 의존성을 그만큼 줄일 수 있

baeharam.netlify.app

 

[Javascript] " Function() " vs " Const = () => "

머릿말 최근 next-step 과제를 통해 다양한 사람들의 코드를 보면서, 제목에 보이는 2가지 모두 "함수다운" 기능을 수행하는데 그 차이점이 궁금하여 검색을 하게 되었다. 그래서 차이점이 뭔가요?

kimbangg.tistory.com

 

JS 클래스 관련 설명 유튜브(혼자 공부하는 js)

 

module.exports와 export default

 

[JS/Module] CommonJS와 ES Modules는 무엇일까?

1. CommonJS, ES Modules는 무엇일까? 우리는 JS 모듈을 내보내거나 가져올 때 2가지 방식을 사용한다. 첫번째 방법은 module.exports로 모듈을 내보내고 require()로 접근하는 CJS(CommonJS), 두번째 방법은 export

mong-blog.tistory.com

 

배열이 const 인 이유

상수는 한번 지정된 이후 값이 변하지 않는 값이지만, 배열로 선언했을 때 배열 내부의 값이 바뀌지 않는다는 것은 아니다. 배열을 참조하는 값만 고정되기 때문에 배열 내부의 값은 변경될 수 있다. 

 

31. 자바스크립트 배열 상수(Const)

ECMAScript 2015 (ES6)2015년에 자바스크립트는 중요한 키워드를 새로 도입했다: const. const를 사용하...

m.post.naver.com

 

자바스크립트 컨벤션 SNAKE_CASE

JS 컨벤션에서 상수의 경우 SNAKE_CASE를 이용하라고 했는데, 이는 하드코딩한 값을 사용할 때만 사용

 

[자바스크립트]변수와 상수(let, const)

변수와 상수 변수와 상수의 용도를 알아보자 변수란? 변수는 데이터를 저장할 때 쓰이는 ''이름이 붙은 저장소''이다. 자바스크립트에서는 let 키워드를 사용해 변수를 생성한다. let message; //'messa

lakelouise.tistory.com

 

Jest 기본

테스트코드 작성 시 사용되는 Jest를 새로 공부해 보았다. 어떤 함수를 이용해서 어떤 식으로 테스트를 진행하는지 익힐 수 있었다. describe(), test() 등등의 함수가 생각난다...

 

[Jest] 테스트 코드로 JS 의 기능 및 로직 점검하기

Jest를 사용해서 JavaScript 코드를 테스트하고 버그를 줄여보자

velog.io

 

Jest mock function

미리 찾아봤으면 테스트 코드 실행 시 통과에 도움을 받았을 것 같은데, 아쉽게도 제한 시간 종료 후에 디스코드에서 참가자 분이 올려주신 걸 보게 됐다. 아쉽긴 하지만 앞으로 남은 과제가 3개나 더 있으니, 공부해서 활용해 봐야겠다.

 

우테코 테스트 코드로 본 Jest mock function - 1

우테코 5기 미션에 주어진 테스트 코드를 보며 공부해 본 내용입니다.

velog.io

 

반응형