[2주차 프리코스] 자동차 경주 개발 회고 - 작성 중...

Nayoung·2024년 10월 23일
1
post-thumbnail

나의 2주차 과제 PR 링크 : https://github.com/woowacourse-precourse/javascript-racingcar-7/pull/377/

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

우아한테크코스 2주차 미션이 시작됐다!
https://github.com/woowacourse-precourse/javascript-calculator-7
2주차 프리코스 과제의 요구사항은 다음과 같았다.

자동차 경주


📌 과제 진행 요구 사항

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

🔨 기능 요구 사항

초간단 자동차 경주 게임을 구현한다.

  • 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다.
  • 각 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다.
  • 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다.
  • 사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다.
  • 전진하는 조건은 0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우이다.
  • 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다.
  • 우승자가 여러 명일 경우 쉼표(,)를 이용하여 구분한다.
  • 사용자가 잘못된 값을 입력할 경우 "[ERROR]"로 시작하는 메시지와 함께 Error를 발생시킨 후 애플리케이션은 종료되어야 한다.

🔴 입출력 요구 사항

입력

  • 경주할 자동차 이름(이름은 쉼표(,) 기준으로 구분)
pobi,woni,jun
  • 시도할 횟수
5

출력

  • 차수별 실행 결과
pobi : --
woni : ----
jun : ---
  • 단독 우승자 안내 문구
최종 우승자 : pobi
  • 공동 우승자 안내 문구
최종 우승자 : pobi, jun

🛠️ 프로그래밍 요구 사항 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를 이용하여 정리한 기능 목록이 정상적으로 작동하는지 테스트 코드로 확인한다.
    - 테스트 도구 사용법이 익숙하지 않다면 아래 문서를 참고하여 학습한 후 테스트를 구현한다.

라이브러리

  • @woowacourse/mission-utils에서 제공하는 Random 및 Console API를 사용하여 구현해야 한다.
    - Random 값 추출은 Random.pickNumberInRange()를 활용한다.
    - 사용자의 값을 입력 및 출력하려면 Console.readLineAsync()와 Console.print()를 활용한다.
    사용 예시
  • 0에서 9까지의 정수 중 한 개의 정수 반환
MissionUtils.Random.pickNumberInRange(0, 9);




👩🏻‍💻 2. 과제 풀이

이번 과제에서는 함수가 한 가지 일만 하도록 최대한 작게 만들라는 프로그래밍 요구 사항이 있다. 또한 1주차 공통 피드백에서 자바스크립트의 내장 API를 적극적으로 활용하라는 메세지도 있었다.

이 점을 유의하면서 프로그래밍 하기 위해 만들게될 디렉터리 구조 파악 단계를 추가해보았다. 지난 주차 프리코스에서는 컴포넌트 중심의 개발을 허용하는 분위기일지 모르겠어서 파일 분리를 소극적으로 했었는데, 이번엔 최대한 작게 라는 요구사항을 받았으니 최대한 확장성, 재사용성, 유지보수성이 좋은 컴포넌트 중심의 개발을 해보고 싶다.

일단 알고리즘을 대략적으로 짜보자.

2.1. 알고리즘 순서도

그리고 이에 필요할 듯한 함수들을 적어보고 폴더 구조를 스케치 해보자!

1차적으로 생각해본 프로그램 전체 동작 흐름은 다음과 같다.

App.run()으로 프로그램 시작
->main() 함수 호출
->main.js 시작에 프로그램이 시작되었습니다 출력 프린트
->carNameInput.js 실행(사용자에게 경주할 자동차 이름 입력값을 받는 함수)
->carNameInput.js에서는 입력검증을 해주는 validateInput.js 객체 실행
->검증 통과 후 car 객체의 [name: name, advance: 0] 인스턴스 배열을 carList에 추가하는 createCarList() 실행
->시도할 횟수를 입력 받는 tryNumber.js함수 실행
->tryNumber.js에서는 validateInput.js 객체 실행
->검증 통과 후 isTryNumber 숫자열 반환
->isTryNumber를 인자로 받는 gameController호출
->gameController.js는 raceController.js를 isTryNumber번 만큼 반복실행
->raceController.js 실행
->raceController.js는 raceRound.js와 resultRound.js 을 carList의 요소를 모두 순회하며 실행
->raceRound.js 함수에서는 car의 각 요소(name별로 만들어진 차들)에 Random.pickNumberInRange() 호출을 통해 값이 4이상일 경우 각 요소에 advanceNumber를 +1 하고 4미만이면 0을 추가함
->resultRound.js에서는 carList의 현재 상태를 [name1: -, name2: --, ...] 형태로 출력
->resultRound.js는 advanceNumber만큼 '-'를 추가하는 generateAdvanceSymbol()과 ${carName}: ${advanceSymbol} 문자열을 반환하는 formatResultMessage() 함수, printResult()함수 실행
->gameController.js는 반복실행이 끝난 뒤 getIsWinner.js 실행
->getIsWinner.js에서는 현재 carList에서 advanceNumber 속성이 가장 높은 name을 winner.js 객체에 반환. 동점자의 경우엔 동점자를 배열로 반환
->gameController.js 종료
->gameResultController.js 호출
->winner.js 객체 배열을 ', '를 구분자로 문자열 배열 반환시키는 setStringWinner.js 실행
->이어서 printIsWinner.js 호출
->printIsWinner.js는 '최종 우승자 : [winner 문자열 배열]' 출력
->프로그램 종료

오...이런!!!!ㅋㅋㅋㅋㅋㅋㅋㅋ
역시나 간단한 알고리즘을 짰을 때보다 전체적인 프로그램 동작 흐름을 정리해보니 엄청 복잡하다!!ㅋㅋㅋ큐ㅠㅠ 심지어 일단 최대한 하나의 함수가 하나의 동작만 하도록 프로그래밍하기로 했으므로 게임을 관리하는 로직이 필요할 거 같아서 그렇게 생각하다보니 컨트롤러 개념이 필요할 것 같았고?
이 과제는 UI 구현이나 사용자간의 상호작용이 메인이 아닌 게임의 데이터 처리 로직이 중점이므로 이런 경우에는 mvc패턴으로 구현하는 게 효율적이어 보였다. 그래서 원래는 컴포넌트 중심의 개발처럼 함수형 프로그래밍을 모방하려했지만...프로그램의 전체적인 로직을 써내려가다보니 mvc패턴으로 급선회를 하게 되었다.

바닐라 자바스크립트 프론트엔드 개발에서도 mvc 패턴을 쓰나? 하고 찾아봤더니, mvc 패턴으로 oop 프로그래밍을 하기도 한다고 한다.
솔직히 mvc패턴은 이전에 라라벨을 찍먹으로 공부할 때만 잠깐 들여다본 개념이라...이번 과제가 나에겐 또 새로운 도전이 될 것 같다.

또한 무엇보다 나는 객체지향 프로그래밍에 무지하다. class 문법도 솔직히 잘 모르고...
하지만 mvc 패턴을 따르기로한 김에 객체지향 프로그래밍을 녹여내면 좋은 코드가 될 것 같아서 이번 기회에 학습해보려고 한다.
그래서 일단 프로그램 흐름을 위와 같이 작성했지만, 막상 개발하면서 내가 이해하고 있던 객체 개념이 틀린 것이었으면 바뀔 수 있다.

우선 기존의 내 개발 패턴과는 다른 패턴을 적용해보기로 했으니 mvc 패턴에 대해 조금 학습하고 디렉터리 구조를 짠 뒤 기능 목록을 적어봐야겠다.

2.2. 프론트엔드의 디자인 패턴

https://developer.mozilla.org/ko/docs/Glossary/MVC

찾아보니 AngularJS 에서는 React와 달리 프론트엔드에서도 MVC 패턴을 따르려고 한다고 한다. 나는 AngularJS는 공부해본 적이 없고 React의 UI 구현 위주의 컴포넌트 중심 개발만 해봐서 이 부분에 대한 더 깊은 이해가 필요할 것 같다... 과제에 참고 자료로 붙어있던 커밋메세지 작성 참고 링크도 AngularJS이고 말이다.(상관있나?)

일단 기존의 컴포넌트 중심의 개발에 대한 회고를 다시 해보자.
https://www.infoq.com/news/2014/05/facebook-mvc-flux/
페이스북에서만든 리액트는 기존의 mvc패턴의 양방향 데이터 흐름이 스케일업 하기에 좋지 않아 대규모 프로젝트가 될 수록 관리하기 힘들다는 이유로 단방향 데이터 흐름의 flux 패턴을 만들었다. 바로 이 개념이 React에서 상위 컴포넌트에서 하위 컴포넌트로 props 전달하여 상태를 변경하던 컴포넌트 중심 개발의 토대되는 디자인 패턴이다.

그리고 내가 사용했던 ContextAPI는 이로 인한 props지옥을 해결하기 위해 상태를 중앙에서 관리해 어느 컴포넌트에서든 상태를 임포트해 불러올 수 있게 해주는 전역 상태관리 라이브러리이다.

그렇다면 리액트의 flux 패턴은 mvc 패턴의 양방향 데이터 흐름이 정확히 어떤 점에서 스케일업하기에 좋지 않다고 지적하는 걸까?

바로 프론트에서는 사용자와 끊임없이 상호작용이 일어나고 상태가 변화한다는 점이다. 웹에서 '사용자의 현재 마우스 위치에 따라 따라 움직이는 눈알' 같은 기능을 구현한다고 해보자. 그럼 이벤트리스너를 등록하고 실시간으로 사용자 마우스 위치를 감지하고 추적해 뷰를 변화시키는 로직이 필요할 텐데 그럼 위 flux 설명 링크에 나와있는 아래 사진처럼 모델과 view간의 통신이 불필요하게 많고 복잡해져 문제가 일어날 수 있다.

하지만 리액트는 DOM의 변화를 가상머신에서 처리하고 필요한 부분만 실제 DOM에 반영하기 때문에 이런 문제를 일으키지 않고 효율적으로 처리할 수 있다.

자, 다시 바닐라 자바스크립트인 프리코스 과제 이야기로 돌아와서...

그렇다면 이번 과제에는 flux 패턴이 좋을까 mvc패턴이 좋을까? 결론적으로 나는 MVC 패턴이 좋다고 판단했다.
왜냐하면 이번 과제는 사용자와의 상호작용은 초반 입력값 부분밖에 없고 대부분이 프로그램 내의 데이터 처리 및 출력으로 이루어져있기 때문이다.
사용자와의 실시간 상호작용으로 잦은 상태변화가 일어나는 프로그램이 아니라면 MVC 패턴을 따르는 프로그램이 작성하기에도 동작하기에도 효율이 좋을 것이라고 판단했다.

또한 현재 실행 환경은 웹브라우저가 아닌 Node이며, 바닐라 자바스크립트로 작성하는 프로젝트이기 때문에 MVC 패턴을 따라보기로 했다.
추가로, 지난 주 프리코스 리뷰를 하면서 객체지향 프로그래밍을 녹인 개발도 꽤 가독성이 좋고 용이해보여서 객체 활용도 적극적으로 하기로 했다.

2.2. 디렉터리 구조 설계

__tests__/
├── ApplicationTest.js  
src/
├── controllers/
│   ├── main.js
│   ├── gameController.js
│   ├── gameResultController.js
│   ├── raceController.js
│   └──	rounds/
│   	├── raceRound.js
│   	└── resultRound.js
├── models/
│   ├── car.js
│   ├── carList.js
│   └── winner.js
│   
├── views/
│   ├── printIsWinner.js
│   ├── printResult.js
│   └── printRound.js
├── utils/
│   ├── generateAdvanceSymbol.js
│   ├── formatResultMessage.js
│   ├── setStringWinner.js
│	├──	carNameInput.js
│	├──	tryNumberInput.js
|   ├── validateInput.js
|   └── createCarList.js
├──App.js
└──index.js
.
.
.

어라...막상 이렇게 나누고 보니 이번 과제에서는 UI 구현 중심의 컴포넌트 개발보다 게임 동작과 데이터 처리에 특화된 MVC 패턴을 따라 디렉터리 구조를 설계하는 게 나을 것 같다. 게임 흐름을 관리해줄 컨트롤러도 필요할 것 같고 연쇄적인 import로 역할 분리가 비교적 모호한 컴포넌트 개발은 이번 요구 사항에 적합하지 않을 것 같다...

으음...백엔드 개발을 해본 적 없는 내가 mvc 패턴을 모방해 코드를 만들 수 있을까...? mvc 패턴의 디렉터리 구조 및 역할 분리에 대한 공부를
해봐야할 것 같다.

정리하자면 일단 예상 디렉터리 구조는 다음과 같을 것 같다.

/__tests__
   |-- ApplicationTest.js          // 테스트 케이스 코드
/src
|--/controllers
|    |-- gameController.js         // 전체 게임 흐름을 제어하는 컨트롤러
|    |-- raceController.js         // 경주 로직 관련 제어
|  /models
|    |-- Car.js                    // 자동차 객체
|    |-- ErrorMessage.json.        // 에러 메세지 정보 데이터
|  /services
|    |-- raceService.js            // 경주 관련 서비스 (자동차 전진 및 우승자 계산)
|  /utils
|    |-- random.js                 // 랜덤한 숫자 생성 함수
|    |-- validateInput.js          // 경주할 자동차 이름 및 시도 횟수 등 입력 검증 함수
|  /views
|    |-- print.js                  // 출력 관련 함수 (차수별 결과, 에러 메시지, 우승자 안내)
|--App.js                         // 프로그램 실행 시작점

이를 기반으로 기능 구현 목록을 작성해보자.

2.3. 기능 구현 목록 작성

2.4. 개발 체크리스트

[ ] 주석이 필요 없을만큼 명확한 네이밍
[ ] 하나의 함수는 하나의 역할만 수행하기
[ ] 들여쓰기 depth는 2까지
[ ] 3항 연산자 쓰지 않기
[ ] Random 값 추출은 Random.pickNumberInRange()를 활용
[ ] 사용자의 값을 입력 및 출력하려면 Console.readLineAsync()와 Console.print()를 활용
[ ] 줄임말 쓰지 말기
[ ] 코드 포맷팅 하기
[ ] 오류를 찾을 때 함수보단 디버거 활용
[ ] 최대한 자바스크립트 API 잘 활용하기
[ ] 배열도 배열 자체 재할당이 아닌 경우 const 선언
[ ] 커밋 메세지 영어 작성
[ ] run()에서는 최대한 호출만
[ ] 비동기가 필요한 곳에만 잘 쓰였는지
[ ] Jest를 이용하여 정리한 기능 목록이 정상적으로 작동하는지 테스트 코드로 확인
제출할 때 회고 작성하기
(지원서에 작성한 목표를 얼마나 달성하고 있다고 생각하나요? 그 이유는 무엇인가요?
지원서에 작성한 목표를 변경해야 한다고 생각하시나요? 그렇다면 그 이유와 어떤 목표로 변경하고 싶으신가요?
프리코스를 진행하면서 눈에 띄는 변화나 깨달은 점이 있나요?)
[ ] 제출할 때 외부링크 걸지 말기

2.5. 개발 회고


3. 배운 내용 정리


4. 고칠 점

4-1. 우테코 공통 피드백 메모

  • 공통 피드백에 의하면 줄임말을 쓰지 말라고 한다 ㅠㅠ finalDlm->finalDelimiter 이런 식으로 풀어서 쓰자..ㅠ
  • 오류를 찾을 때 함수보단 디버거를 사용하자!
  • 이름을 고심해서 잘 짓자! 주석은 최대한 지양하고, 이름만 봐도 무슨 함수인지 알 수 있게 짓는 게 베스트!
  • 주석 최대한 쓰지 말자 ㅠ (클났네 나 너무 남발해따...)
  • 코드 포매팅을 사용한다! 코드 포매팅과 구조화는 클린 코드를 위한 최소한의 요구 사항이다. IDE의 코드 자동 정렬 기능을 사용하면 더 깔끔한 코드를 볼 수 있다.
  • 자바스크립트 API를 적극 활용하기!

4-2. 스터디 피드백 메모

  • 출력 메세지들을 객체 파일로 만들어 한 곳에서 관리하는 게 좋을 것 같다!
  • 내장 메소드를 적극 활용하자! 내 코드 중에 for each 문으로 작성한 합산 함수가 있었는데, reduce() 메소드를 이용하면 합산부터 유효성 검증 로직까지 한 메소드에서 작성할 수 있었다! 자바스크립트 내에 유용한 메소드들을 꼼꼼히 공부하자 ㅎㅎ
  • run() 에는 최대한 호출만 하기...! run() 안에 수행할 로직을 인라인으로 적지 않는 게 좋다
  • 배열 자체를 재할당 할 일이 없다면 const 로 선언하자! 배열에 요소가 추가되는 것은 재할당이 아니기 때문에 const로 해도 전혀 문제 없다!
    가령, 내 코드에서 let defaultDlm = [] 배열을 const defaultDlm = [] 으로 해도 된다는 것이다. 함수 한 번의 실행 안에서 변동되지 않을 값에 let 키워드를 사용하는 것을 지양하자!
  • https://ko.javascript.info/variables (스터디원 분이 소개해주신 사이트인데, 시간 날 때마다 읽어봐도 좋은 사이트같다!)

5. 소감

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

0개의 댓글