[우아한테크코스] 웹 프론트앤드 6기 프리코스 🏎️ 2주차 미션 회고

이온·2023년 11월 1일
0

우테코

목록 보기
3/7
post-thumbnail

🚀 2주차 미션에 앞서

개인적인 사정으로 인해 1주차 미션에 완전하게 집중하지 못해 아쉬움이 컸다. 때문에 1주차 미션 종료 후 아쉬웠던 점, 보완해야할 점 등을 1주차 회고록에 작성해보았다. 그 과정을 통해 이번 미션부터는 어떤 방식으로 코드를 구현해야 할지, 어떤 학습이 더 필요할지 머리속에 정리할 수 있어서 1주차때보다 좀 더 수월하게 미션을 시작했다고 생각한다. 물론 미션을 진행하면서 막히는 부분도 많았지만 1주차 미션 때 보다는 코드를 짤 때 큰 그림을 그리는 연습을 할 수 있게 되었다.

또한 다른 분들의 코드도 많이 살펴 보고 코드리뷰도 받으며 내 코드의 문제점을 깨달았고 리팩토링에 과감하게 시간투자를 하며 2주차 미션을 마주하기 전 워밍업 단계를 거쳤다. 이번 2주차의 목표는 1주차때 적용하지 못한 MVC패턴을 효율적으로 활용하는 것이고, 이번 추가 미션이기도 한 테스트 코드 작성을 완벽하게 학습하는 것으로 일단 간단하게 생각했다. (이후에 계속해서 새로운 목표가 생겼지만!)

🪐 2주차 미션 : 자동차 경주

1. 과제 요구사항 정리

프로그래밍 요구사항은 1주차와 동일

기능 요구사항

  • 쉼표(,) 기준으로 구분하여 5자 미만의 이름으로 n대의 자동차를 입력
  • 몇 번의 이동을 할 것인지(=시도할 획수) 숫자 입력
  • 전진 조건은 0~9사이의 랜덤 값이 4 이상이 나올 경우임
  • 우승자가 여러명일 경우 쉼표로 구분해 출력
  • 입력 에러 시 [ERROR]로 시작하는 에러 문구 출력 후 애플리케이션 종료

추가된 요구 사항

  • indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다.
    - 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
    - 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메소드)를 분리하면 된다.
  • Jest를 이용하여 본인이 정리한 기능 목록이 정상 동작함을 테스트 코드로 확인한다.
    - 테스트 도구 사용법이 익숙하지 않다면 tests/StringTest.js를 참고하여 학습한 후 테스트를 구현한다.

2. 미션 진행의 흐름

✅ 1주차와 마찬가지로 git 셋팅을 먼저 진행했다.

✅ 구현에 앞서 어떻게 구현할지 기능 목록을 작성했다.
게임의 진행 순서대로 코드를 작성해나가기 위해 전체적인 코드 스케치를 한다고 생각했다.

✅ 또한 저번 미션 때 미쳐 못했던 우테코 라이브러리(@woowacourse/mission-utils) 살펴보기를 진행했다. 알맞은 기능을 모두 파악해 적재적소에 사용하는 것이 이번 목표였다.

(1) Console.readLine(query, callback)
주어진 질문을 화면에 출력하고, 사용자가 답변을 입력할 때까지 기다린 다음 입력된 답변을 인수로 전달하는 콜백 함수를 호출한다.

(2) Console.readLineAsync(query)
주어진 질문을 화면에 출력하고, 사용자가 입력한 답변을 Promise를 통해 반환하는 비동기 함수이다. 비동기라서 async를 사용하는 함수 아래에서 사용해야한다.

(3) Console.print(message)
console.log와 같은 기능을 구현한다.

async function getUserInput() {
  const input = await Console.readLineAsync("값을 입력해주세요.");
  Console.print(input); // 입력값이 출력됨
}

(4) Random.pickNumberInRange(startInclusive, endInclusive)
정해진 범위 내에서 한개의 숫자를 뽑아주는 함수이다.

(5) Random.pickNumberInList(array)
배열에 있는 숫자들 중 한개만을 뽑아주는 함수이다.

(6) Random.pickUniqueNumbersInRange(startInclusive, endInclusive, count)
숫자 범위 내에서 지정된 개수만큼 겹치지 않는 숫자를 반환하는 함수이다.

✅ 1주차에서 못한 MVC 패턴을 적용해보기로 했다.

간단하게 역할을 분담한 파일구조와 설명을 적어보았고, 더 학습한 부분은 게시글로 따로 작성했다.

📁src
├── 📁controller
│   └── gameController.js : 게임의 전반적인 흐름 담당
├── 📁domain
│   ├── 📁dto
│   │   ├── carDto.js
│   │   ├── carsDto.js
│   │   ├──
│   │   └──
│   ├── car.js : car 객체 생성
│   ├── cars.js : cars 객체 생성
│   ├──
│   └── 
└── 📁view
    ├── inputView.js : 사용자 입력시 보여지는 화면
    ├── inputConverter.js : 사용자 입력값 변환
    ├── inputValidator.js : 사용자 입력값 검증
    └── outputView.js : 결과 출력

✅ 타입을 헷갈려서 계속 에러가 나고 실수를 많이해서 타입 및 에러체크를 위해 JS doc을 사용하기로 했다.

아래와 같이 알고 싶은 부분에 마우스를 올리면 타입, 리턴값을 알려주고 직접 설명도 쓸 수 있어서 헷갈리는 부분을 쉽게 이해할 수 있게 되었고 잘못된 부분은 바로 에러가 떠서 바로 에러 수정이 가능하다는 부분이 너무 속이 시원했다.

dto를 생성해 더 꼼꼼하게 관리하기로 했다.

예를 들어 Car에서 CarDto를 생성하고 반환받는 코드를 구현했다. 그 밖에 모든 데이터의 dto 사용으로 어떤 데이터를 어떤 형태로 적용하는지 가독성을 높였다.

2-1. 트러블 슈팅

미션을 진행하며 수많은 에러들을 마주했지만 기억에 남고, 에러 해결하는데 시간이 오래 걸린 부분 위주로 정리해봤다.

(1) 유효성 검증의 코드 위치 실수

사용자가 입력한 자동차 이름들의 유효성 검증을 두번 나눠서 진행했다. 먼저 inputValidator에서 전체 인풋이 무엇인지 검증했고, 다음으로 car안에서 각 자동차 이름 하나하나를 다시 검증했다.

간단한 실수이지만 자동차 이름이 5글자 제한을 두는 유효성 검증은 car안에서 각 자동차 이름을 하나하나 검증할 때 들어가는 로직이어야 했다. 나는 실수고 전체 사용자 인풋에서 5글자 제한으로 검사를 했는데 qwe,asd,zcx 라고 사용자가 입력하면 당연히 5글자가 넘어가는게 맞았다.

처음에 검증 로직이 잘못된 줄 알고 한참을 고민했는데 의외로 맥빠지는 실수여서 이런 실수는 다시 하지 않기 위해 기록한다.

(2) 간단한 오타나 개행 여부에 대한 에러도 반드시 꼼꼼하게 살펴보자. 실수가 너무 많았다.

(3) findWinner(): 우승자 찾기 로직 부분

(3-1) winnerDto에 winner를 넣기 위해 winner를 Winner[]로 생성하는 것이 목적이었는데 Car[]가 만들어져서 그 값을 winnerDto에 전달할 수 없었다. Car[]가 만들어졌다는 것은 JsDoc에서 처음에 타입을 잘 지정해주었기 때문에 금방 발견할 수 있어서 다행이었다.

처음에는 carList 즉 Car[] 배열을 map으로 돌며 그냥 car를 반환한 상태에서 winnerDto에에 넣어주려해서 당연히 오류가 났었다.

따라서 그냥 car를 반환하는 것이 아니라 car 클래스 내에 존재하는 makeWinnerDto() 메서드를 호출해 WinnerDto에 알맞는 양식으로 dto를 생성해서 넣어주었다.

(3-2) 또 findWinner 함수를 호출하면 winner에서 Car[] | undefined라고 타입이 표시되었다. findWinner 함수 내에 if문 때문이라고 판단해 로직을 새로 다시 구현했다.

carList를 먼저 필터링을 하는 로직을 추가했다. 자동차의 이동 거리가 최대 이동거리인 자동차(=우승자)를 먼저 필터링해주고, 그 car들을 각각 dto로 만들어주는 흐름으로 구현했다.

(⬇️ 에러 난 이전 코드: winners에 마우스를 올리면 엉뚱한 Car[]라고 뜸)

(⬇️ 에러 해결한 이후 코드: winners에 마우스를 올리면 WinnerDto[]라고 제대로 뜸)

(4) 구조분해 할당 에러

winners dto안의 winner 객체를 가져와 그 안에 있는 winner 이름을 가져오려고 했다. winner 객체라고만 생각해 {winner}라고 구조분해 할당으로 바로 winner 이름을 가져와 쓰려고 했다. 하지만 사실 winner는 getter라서 구조분해 할당이 되지 않는것이 당연했다. 그래서 winner.winner로 사용함으로 에러를 해결했다. 어떻게 보면 간단하지만 객체와 getter에 대한 이해도가 더욱 올라가는 경험이었다.

(사실 vs code에서는 자동완성해서 되는것 같아보여서 실행하기 전까지 문제점을 몰랐다.)

(⬇️ 지금은 바꾼 부분의 코드이긴 하지만 아래 구조분해가 안된다는 에러 메세지를 볼 수 있다)

(5) 예제 테스트코드 통과 안되는 문제

알고보니 class파일의 이름은 대문자로 시작해야하는 Java의 규칙이 프론트에도 적용된 것이라 계속 오류가 난 것이었다. 아무리 해도 테스트 통과가 안되어서 디스코드 커뮤니티에 다른 분들은 어떤 식으로 해결했는지 참고하다가 어떤 분의 오류 해결 경험 공유로 인해 나도 도움을 받아 해결했다. 파일 명이 문제였다니! 너무 맥빠지는 에러 경험이었다..

결국 통과!

3. 이번 미션을 통해 배운 점 (글로 자세히 기록)

  • MVC 패턴 : 이 부분은 블로그에 게시물로도 작성하며 학습했다. 이전 미션에서 나는 App.js파일 안에 모든 로직을 구현했었다. 따라서 한 파일 안에 코드의 길이가 너무 길어졌고 한 클래스 안에 많은 것들이 한번에 들어가 있어 클래스 명에 대한 의미가 없었었다. 반면 다른 분들의 코드를 보면 MVC패턴을 적용한 코드들이 꽤 많이 보였고 훨씬 코드가 전문적이고 퀄리티가 높다고 느꼈다. MVC 패턴을 적용해 파일 구조를 디자인하면 화면에 보여지는 요소나 비즈니스 로직을 잘 분리할 수 있는데, 이를 통해 기능 별로 코드를 각각 작성할 수 있어 유지보수에 용이하다는 것을 알게 되었다. 1주차 미션이 종료된 후 나의 코드의 심각성을 깨닫고 MVC 패턴에 대해 바로 학습해 적용했던 것 같다. 그 효율성을 깊게 깨달았고 앞으로 MVC 패턴을 수도 없이 사용할 것 같은 예감이 들었다.

  • 일급 컬렉션 : 최근 객체 지향적인 코드를 구현하는 방법에 대해 학습하던 중 일급 컬렉션이라는 개념을 알게 되었다. 일급 컬렉션은 클래스를 만든 뒤 원하는 기능만 메서드에 추가해 할 수 있는 일을 명확하게 제약을 둔 것이라는 것을 알게 되었다. 예를 들어 나의 코드에는 Cars라는 도메인 객체가 있는데 이 클래스 안에는 차를 움직이는 메서드, dto를 만들어주는 메서드 등 필요한 메서드만 구현했다. 또한 이 일급 컬렉션 내에 같은 타입의 데이터를 한 곳에 모으고 관련된 상태와 로직을 한 곳에서 관리해 코드의 응집성을 높였다. 이 외에도 일급 컬렉션은 비즈니스 로직에 종속적인 자료구조를 만들어서 관리할 수 있으며 값을 변경하거나 추가하는 메소드를 사용하지 않고 읽기 전용 모드인 불변 컬렉션을 만들 수 있다는 것을 알게 되었다. 이 부분은 아직 100% 완벽하게 이해했다고 당당하게 얘기하지는 못해서 3주차 미션 중 꾸준히 학습하며 더 유연하게 적용하는 연습을 해야겠다고 다짐했다.

  • Dto : 이전에 타입스크립트를 잠깐 공부할 때 dto를 접한 경험이 있기는 했었다. 하지만 당시 다른 학습에 중점을 두고 dto의 필요성에 대한 이해는 내려놓고 넘어갔었다. 따라서 이번 기회에 미션을 진행하며 dto에 대해 깊게 공부하면 좋을 것 같다고 생각했다. Dto는 데이터 전송 객체를 말하고 정보를 노출시키지 않고 시스템 간 통신을 훨씬 직관적으로 알 수 있게 해준다는 것을 알게 되었다. 또한 dto 내에서 getter를 사용하여 멤버 변수들을 읽기모드로 만들어 불변성을 유지하는 방법 또한 새롭게 배워 바로 적용할 수 있었다.

  • getter: getter에 대해서는 사실 디스코드 커뮤니티에서 처음 접했다. 또한 1주차 미션 시 다른 사람들의 코드를 보다 보니 getter를 사용한 사람이 꽤 있다는 것을 알았고 나도 바로 적용해보기 위해 학습을 시작했던 것 같다. getter나 setter는 객체 지향 프로그래밍에서 클래스를 생성할 때 데이터를 보호하기 위해 사용되는 개념이라는 것도 알게 되었고, 객체 지향 프로그래밍에 대한 중요성을 다시 한 번 느끼게 되었다. 보통 객체 내 속성값을 바로 접근할 수 있으면 그 값을 변경하거나 훼손할 위험성이 있는데, 속성 값을 반환하는 get 어쩌고() 라는 메서드를 통해 한 단계 거쳐서 접근하도록 설정하면 객체의 정보를 보호할 수 있다는 것을 알게 되었다. 생각보다 이해가 쉬운 개념이라 바로 적용할 수 있었으며 다음 미션때도 유용하게 사용할 수 있을 것 같았다.

  • JS doc : dto를 사용하고 여러 배열과 문자열을 다루다 보니 타입 에러가 계속 나고 실수가 잦아졌다. 에러 해결에 너무 많은 시간을 쏟는 것은 아닐까 걱정하던 중 Js Doc가 떠올랐다. 이전에 들어본 적은 있었지만 타입 스크립트도 아닌데 굳이 타입을 써야할 필요를 느끼지 못해서 사용은 안 해본 것이었다. 처음에는 코드가 지저분해 보이고 너무 길어져서 부정적인 느낌을 받았지만 js doc을 사용하기 위해 여러가지 게시물과 공식문서 등을 살펴보니 이점이 훨씬 많다고 느꼈다. 현재 내가 느끼는 불편함을 해소하기 위해 사용했고 결과는 대성공이었다! 마주한 수많은 에러들 중 타입을 유심하게 따지지 않으면 안되는 에러들도 많았는데 js doc 덕분에 바로 문제점을 파악할 수 있었고 빠르게 해결 가능했다.

  • Object.freeze()의 사용 이유에 대해서도 이해하게 되었다. 이전에는 굳이 사용하지 않았었는데 이를 통해 코드의 완성도를 한층 높였다. CarDto를 생성할 때 cars에는 배열이 들어가는데 객체나 배열은 요소가 변경될 가능성이 높아서 위험하다는 것을 알게 되었다. 따라서 불변성을 위해서 Object.freeze()를 사용했는데 이를 통해 나중에 요소를 바꾸려고 해도 이전 고정된 값에서 바뀌지 않게 되었다. Getter도 그렇고 freeze의 학습을 통해 코드의 불변성이 얼마나 중요한지 다시금 느꼈다.

  • 테스트 코드를 위한 랜덤 값 관리 : 가장 어렵다고 느낀 부분이었다. 테스트 코드를 짜다 보니 랜덤 값을 설정하는 부분에서 테스트가 통과 될 때도 있고 안될 때도 있다는 것을 알게 되며 큰 문제가 생겼다. 나같은 경우에는 "car를 움직이면 distance가 1 증가한다"라는 부분이 문제였는데 자동차가 랜덤하게 움직이기 때문에 예상되는 결과값이 매번 달라졌다. 따라서 Car객체의 move메서드 부분의 로직을 따로 분리하기로 했다. 움직임을 결정하는 MoveDecider라는 클래스를 새로 생성했다. MoveDecider에는 두가지 버전을 만들었는데 랜덤하게 움직이는지, 아니라면 고정 값으로 움직인다고 할지 말지 결정하는 것이다. 두가지 메서드는 Static으로 만들어 인스턴스 객체 생성 없이 그냥 클래스명.메서드()로 사용 가능하게 했다. 또 기존 move메서드 로직을 가져와 랜덤으로 움직일지 말지 참,거짓을 반환하는 메서드를 추가로 이 곳에 생성했고, 그 메서드가 주는 값에 따라 랜덤인 경우 자동으로 이동 여부를 생성하고, 랜덤이 아닌 경우 수동으로 이동 여부를 생성해주는 경우의 수를 나눠주었다. 그리고 기존 move메서드 이름을 MoveDecider에 의해 움직임을 결정한다는 뜻으로 좀 더 직관적이게 moveBy()라고 변경해주었고 여기서 MoveDecider의 결과 움직이게 된다면 이동 값을 더해주는 로직만 구현했다. 결론적으로 랜덤하게 움직이는 부분은 게임 실행 시 이용했고 고정 값으로 움직이는 부분은 테스트를 할 때 이용하게 되었다. 이 부분을 리팩토링하면서 너무 복잡하다고 생각했는데 코드를 전부 구현한 이후 테스트가 잘 돌아가자 너무 뿌듯했고 이러한 코드가 나중에 큰 프로그램을 짤 때 분명히 도움이 될 것이라고 느꼈다.

  • 미션을 진행하며 만난 여러 에러들 중 기억에 남는 에러들을 블로그에 따로 정리하는 시간을 가졌다. 처음에는 기록을 위해 캡쳐만 해 두었는데 나중에 보니 분명 내가 해결한 에러인데 어떻게 했는지 기억이 안나는 일이 생겼다. 따라서 이 부분을 개선하기 위해 에러들을 좀 더 자세히 기록해서 복습하는 시간을 가졌다. 정리를 하다 보니 어떤 부분이 막히고 어떤 부분에 대한 이해도가 부족했는지 다시한번 느낄 수 있었다. 앞으로 에러 기록도 꾸준히 하면 좋을 것 같다고 느꼈다.

  • 이 외에도 자잘하게 알게 된 부분이 많았고 다시한번 배움의 끝은 없다고 느끼게 되었다. 프론트도 객체지향적인 코드가 필요할까? 라는 생각을 이번 기회에 많이 하게 되었고 객체 지향적인 프로그래밍에 대해 깊은 학습을 할 수 있었다.

4. 종합적인 마무리 후기

로컬에서 테스트 코드도 다 돌아가고 게임 실행이 잘 됐는데 마지막 제출 때 예제 테스트를 통과하지 못해서 너무 시간을 쏟았던 것이 아쉬웠다. 원래 pr 이후에 더 자세한 테스트 코드를 작성하려고 했는데 에러를 잡다가 테스트 코드를 좀 더 자세하게 작성해보지 못한 부분이 아쉬웠다. 다음주에는 테스트코드도 더 세분화해서 돌려보는 연습을 해야겠다고 생각했다.

1주차때 받은 코드리뷰가 큰 도움이 됐기 때문에 이번 기회에 한 단계 더 성장할 수 있었다고 생각한다. 코드리뷰를 많이 망설였고 부끄럽게 생각했는데 나의 코드의 문제점을 객관적으로 판단해주는 시선은 반드시 필요한 것 같다. 코드리뷰의 중요성도 깨달은 2주차였다. 이번에는 더 능동적으로 코드리뷰를 주고받을 계획이다.

마지막으로 1주차때 못한 부분을 단기간에 습득해 구현하려고 하니 너무 어렵고 힘들었지만 그래도 이번 미션 시작 전에 목표한 부분을 다 끝냈고, 미션을 진행하며 새롭게 알게 된 개념들도 추가로 학습하고 넘어가게 되어서 아주 뿌듯했다. 덕분에 1주차때 154줄의 코드가 전부였던 것에 비해 이번 2주차에는 거의 1000줄 가까이 되는 코드를 짜게 되었으며 이것이 나의 성장을 증명한다고 생각한다. 또 이번 미션 때 배운 것이 아주 많아서 다음 미션때는 더 잘할 수 있을 것 같다는 자신감도 조금 들었다!

154 -> 946줄!

profile
👩🏻‍💻

0개의 댓글