[Front-End] 우테코 프리코스 2주차 회고 (자동차 경주 게임)

2023. 11. 3. 00:28우아한 테크 코스/프리코스

이번 2주차 미션에서 특별히 신경쓰고자 했던 부분들은 다음과 같다.

  1. 기능 목록 README 파일을 정말 상세히 적기. (기능 요구사항 빠트리지 않기)
  2. 조금 더 메서드를 작은 단위로 나누어 보기. 그리고 Class 나누기
  3. 하드코딩 하지 않기. (매직넘버 상수로 포장하기)
  4. 의미하고자하는 바를 확실히 보여줄 수 있도록 변수명 네이밍하기

그리고 추가된 요구사항과 1주차 미션이 끝나고 받은 공통 피드백을 반영하기 위해 힘썼다.

 

공통 피드백에 내가 1주차 미션을 끝내고 2주차에 개선한 부분들이 많이 언급된 것을 보고 신기하고 뿌듯했다.

 

 

1. 기능 목록 README 파일을 정말 상세히 적기. (기능 요구사항 빠트리지 않기)

 

내가 1주차에 작성했던 리드미 파일과 비교했을 때 확실히 더 세부적인 내용까지 다루고, 보기에도 풍성해진 것 같아서 뿌듯했다. ㅎㅎ

 

하지만 이번 미션이 끝난 뒤에도 다른 분의 훨씬 더 잘 작성한 기능 목록들을 봤지만, 이번엔 기가 죽기보단 식견이 더 넓어져서 내가 안주하지 않게 된 것 같아 기분이 좋았다.

 

그 외에도 우테코 코치님의 공통 피드백에서

" 기능 목록을 클래스 설계와 구현, 함수(메서드) 설계와 구현과 같이 너무 상세하게 작성하지 않는다. 클래스 이름, 함수(메서드) 시그니처와 반환값은 언제든지 변경될 수 있기 때문이다." 

 

위와 같은 언급이 있었는데, 함수명 변수명 같은 것들까지 상세히 적으니 변경되는 사항을 리드미 파일에 일일히 수정해줘야 됐다.

 

 

 

 

리드미 파일 내용을 전부 캡처해서 올리기에는 너무 길어질 것 같아서 대략적인 일부 스냅샷들만 올렸다.

 

 

2. 조금 더 메서드를 작은 단위로 나누어 보기. 그리고 Class 나누기

 

1주차 미션을 끝내고 다른 분들의 코드를 보며 나와는 다르게 클래스와 메서드를 잘 분리해 놓으신 분들을 보고 나도 이번 미션을 진행하며 그런 점들을 배우고자 했는데, 마침 추가 요구사항에도 indent depth를 2로 제한한다는 문항이 있었다.

 

indent depth를 신경쓰며 구현하다보니 자연스럽게 메서드를 따로 빼내게 되고, 이전보다 가독성이 훨씬 좋아진게 느껴졌다.

 

아래는 1주차 미션때 구현한 숫자 야구 게임 코드이다.

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

class App {
  constructor() {
    /*
    생성자 생성 - 게임 초기화
    (게임이 진행중인지의 상태)
    */
    this.isGameRunning = true;
  }

  async play() {
    /*
    입력을 받을때까지 기다린 후 받은 입력 값으로 실행해야 하니 함수명에 async 키워드 사용,
    후에 입력 받는 로직에 await 키워드 사용
    */
    while (this.isGameRunning) {
      const answer = this.generateAnswer();
      MissionUtils.Console.print("숫자 야구 게임을 시작합니다.");

      let result;
      while (result !== "3스트라이크") {
        const input = await MissionUtils.Console.readLineAsync(
          "숫자를 입력해 해주세요 : "
        );
        if (
          //사용자로부터의 입력 처리
          input.length !== 3 ||
          new Set(input).size !== 3 ||
          [...input].some((item) => Number(item) < 1 || Number(item) > 9)
        ) {
          throw new Error("[ERROR] 잘못된 값을 입력하였습니다.");
        }
        result = this.checkAnswer(input, answer); // checkTarget 메소드는 판정결과를 return한다 ex) "3스트라이크"
        MissionUtils.Console.print(result);
      }

      /*
      게임 종료 조건을 충족하면 while문을 빠져나온다.
      test 파일에서 "2"에 대한 예측값으로 "게임 종료"로  설정해두었기 때문에 "게임 종료"라는 문구가 포함되어 있어야 한다.
      */
      MissionUtils.Console.print("3개의 숫자를 모두 맞히셨습니다! 게임 종료");

      const replay = await MissionUtils.Console.readLineAsync(
        "게임을 다시 시작하려면 1, 종료하려면 2를 입력하세요."
      );

      if (replay === "2") {
        this.isGameRunning = false;
      }
    }
  }

  generateAnswer() {
    /*
    랜덤한 정답을 생성하는 함수
    객체가 생성될 때 뿐만 아니라 사용자가 게임을 다시 시작할때도 정답을 다시 만들어줘야한다.
    */
    const answer = [];
    while (answer.length < 3) {
      const randomNum = MissionUtils.Random.pickNumberInRange(1, 9);
      if (!answer.includes(randomNum)) answer.push(randomNum); //중복 검사
    }

    return [...answer];
  }

  checkAnswer(target, answer) {
    //스트라이크, 볼, 낫싱 판정 메소드, 판정결과를 return해야 한다. ex) "3스트라이크"
    let balls = 0;
    let strikes = 0;

    for (let i = 0; i < 3; i++) {
      if (target[i] == answer[i]) {
        strikes++;
      } else if (answer.includes(Number(target[i]))) {
        balls++;
      }
    }

    if (strikes === 0 && balls === 0) return "낫싱";

    return `${balls ? balls + "볼" : ""} ${
      strikes ? strikes + "스트라이크" : ""
    }`.trim();
  }
}

export default App;

 

 

그리고 아래는 이번 2주차 미션의 자동차 경주 게임을 관리하는 Race 객체와 Race 객체의 메서드들이다.

 
import { Random } from "@woowacourse/mission-utils";
import Car from "./Car.js";
const MIN_TO_MOVE = 4;
const MIN_RANDOM = 0;
const MAX_RANDOM = 9;

class Race {
  constructor(carNames) {
    this.cars = carNames.map((cars) => new Car(cars));
  }

  startRound() {
    this.cars.forEach((car) => {
      if (Random.pickNumberInRange(MIN_RANDOM, MAX_RANDOM) >= MIN_TO_MOVE) {
        car.moveForward();
      }
    });
  }

  stateOfRace() {
    return this.cars.map((car) => ({
      name: car.name,
      distance: car.shapeOfDistance, // 이동한 거리만큼의 '-'를 가져옴
    }));
  }

  decideWinners() {
    const maxDistance = Math.max(...this.cars.map((car) => car.distance));
    return this.cars
      .filter((car) => car.distance === maxDistance)
      .map((car) => car.name);
  }
}

export default Race;

 

물론 첫번째 코드는 전체코드이고 두번째는 일부 메서드들이지만,

그걸 감안하더라도 확실히 가독성이 좋아졌다고 생각한다.

 

 

마무리

 

그리고 기능 목록에서 기능 요구 사항 부분을 나는 철저히 미션에서 주어진 부분에 대해서만 작성하려고 노력했었는데,

다른 분들을 보니 자신들이 판단하기에 이상한 부분이나 추가적인 기능들도 작성한 것을 보고 나도 다음 미션은 그렇게 해야겠다고 느꼈다.

 

그리고 이외에도 커밋 메시지 작성 법, 커밋 메시지 수정하는 법, 리드미 파일에 이미지 업로드 하는법, Mermaid로 플로우 차트 그리는 법 등 이번 미션을 통해 처음 알게된 것들이 많지만, 본 글은 회고 취지이니 그 부분들에 관해서는 따로 포스팅 하려고 한다.

 

1주차 미션을 통해 내가 얻은 것들
1주차 미션을 진행하며 얻은 것들 미션이 끝난 뒤(코드 리뷰를 통해) 얻게 된 것들
바닐라 js로 구현하며 얻게된 Javascript의 기본지식들 기능 목록 작성의 중요성
양질의 내용을 얻기위해 찾아봐야할 곳들 매직 넘버란
Jest 프레임워크를 사용한 테스팅 방법 README.md 의 중요성
기능 목록 작성의 중요 다양한 테스트 샘플의 중요성
자바스크립트의 실행 컨텍스트에 대한 지식  

 

2주차 미션을 통해 내가 얻은 것들
2주차 미션을 진행하며 얻은 것들 미션이 끝난 뒤(코드 리뷰를 통해) 얻게 된 것들
변수, 메서명 네이밍 컨벤션 기능 요구 사항에 명시되지 않은 부분에 대해서도 고민해볼 것
테스트 코드 작성법  
Indent depth를 유의하며 프로그래밍 하는 법  
매직넘버를 상수로 포장하는 법  
커밋 메시지 작성법