우테코: 프리코스 4주차 미션 복기

드디어 마지막 4주차 프리코스 미션을 완료하게 되었다!!! 4주차는 1,2,3주차에 쌓아온 것들을 모두 종합하여 기능을 구현하는 느낌이었다. 기능구현 설명을 실제로 메일받는 것처럼 작성해서 진짜 개발자가 된 느낌..을 받을 수 있었다..ㅎ

이번 미션은 기능구현을 어떻게 해석하느냐도 중요했던 것 같고, 미션 제출을 fork가 아니라 개인 레포를 만들어서 private로 제한하는 등의 방식으로 진행했다. 여러모로 마지막을 이전과는 달리 새로운 방식으로 진행해서 색달랐다.

4주차: 크리스마스 프로모션 Repository 4주차 프리코스 미션 성공


새롭게 배운점

비즈니스 로직과 UI 로직 분리하기

해당 내용은 3,4주차의 가장 핵심이었던 목표였다고 생각한다.
1,2주차에서는 그냥 한 곳에 다 작성했다면, 3주차에서는 UI로직과 비즈니스 로직을 분리하는 것에만 그쳤고, 4주차에서는 비즈니스 로직을 더 쪼개려고 노력했다.

4주차 미션 폴더

하나의 파일/함수에 하나의 일만 책임질 수 있도록 적당한 선에서 로직들을 분리했고, 이 분리한 로직들을 하나의 파일에 다시 모아 하나의 기능을 하도록 했다.
이렇게 하다가 느낀것은 이게 MVC인건가? 라는 생각이 들었다. 이전에는 MVC가 머리로는 이해되지만.. 어떻게 하라는 건지 잘 모르겠었다. 그래서 적용해본적이 없었는데, 4주차 미션을 진행하면서 이렇게 로직을 분리하고 있는 것이 MVC인거구나를 깨달았다.

아직은 명확하게 Model과 Controller를 분리하는 기준은 잘 모르겠지만, 이번 미션을 통해 MVC는 이래서 사용하는 거구나를 깨닫게되었다.
그리고 분리한 로직들을 하나의 파일에 모아 하나의 기능을 하도록 만든 것이 모듈이라는 것을 깨달았다. 모듈을 이전에도 알고 있었지만, 정말 명확하게 알고 사용하는 용어..는 아니었는데, 이번 경험을 통해 내가 한 행동이 모듈을 만든 것이었다는 걸 알게되었다.

필드가 많으면 왜 안좋고 어떻게 개선할 수 있을까?

3주차 공통 피드백 내용에서 필드의 수를 줄이기 위해 노력해야한다는 내용이 있었다.
필드를 줄여야 한다고 했는데, 그렇다면 필드는 왜 사용하는 것일까? 라는 의문이 들었다. 그래서 필드를 사용하는 이유를 찾아보았다.

필드를 사용하는 이유는 다음과 같았다.

  • 코드가 명확해질 수 있다.
    클래스 내부에 어떤 속성이나 메서드가 속해있는지 명확하게 표현할 수 있기 때문이다.

  • 프로토타입에 직접 추가되지 않는다.
    그렇기에 인스턴스 메모리 사용을 최적화하고 상속관계를 유지하는데 도움을 준다.
  • this 바인딩을 할 필요가 없다.
    this는 constructor 내부나 메소드내에서만 유효하다. constructor 내부어서 this를 사용하는 이유는 생성자의 패러미터 이름과 동일하여 클래스 필드임을 명확하게 하기 위해서이다.
    그렇기에 constructor 내부가 아닌 class 몸체에서는 클래스 필드를 정의할 때 this 바인딩을 사용하지 않는다.
    하지만, 클래스 필드를 참조할때는 반드시 this를 붙여서 사용해야 한다.

  • 캡슐화를 유지할 수 있다.
    클래스 내부에 private한 속성을 생성할 수 있어서 외부에서 직접 접근하지 않도록 하여 캡슐화를 유지할 수 있다.

그렇다면 이런 장점이 있는데 왜 사용을 줄이라는 것일까?
필드를 사용하는 이유를 알고 나니 해당 피드백 문서에서 필드의 수를 줄이라는 것은 이런것이 아닐까 생각이 들었다.
불필요한 필드의 수를 줄이기 위해 노력할 것.
필드는 장점이 뛰어나기에 단순히 필드의 수를 줄이라는 말이 아닌 중복적으로, 굳이 사용하지 않아도 되는 필드의 수를 줄이라는 것이 아닐까?라고 생각이 들었다.

그래서 클래스를 사용할 때, 무분별하게 필드를 사용하는 것을 자제하기 위해 해당 클래스에서 정말 이 필드가 필요한 것인가? 꼭 private 해야 할까? 라는 의문을 가지며 코드를 작성했다.
해당 피드백 문서가 아니었다면 무분별하게 클래스에 private 필드를 사용했을 것이다. 해당 내용 덕분에 필드를 사용할때 정말 신중하게 생각하며 사용했다.

배열이 아닌 객체를 사용하기

이번 미션은 가지고 있어야 할 데이터가 많았기 때문에 이 데이터들을 보다 효율적으로 꺼내서 쓸 수 있도록 배열이 아닌 객체를 주로 사용했다.

기능 요구사항에 평일 이벤트로 ‘디저트’ 주문시 수량만큼 할인을 하는 요구사항이 존재했다. 이때 ‘디저트’ 종류의 메뉴를 따로 배열로 관리하는 것이 아닌, 메뉴판을 객체로 저장해두고 해당 메뉴판에서 디저트에 해당하는 메뉴만 뽑아 쓰는 것이 좋다고 생각했다.

MENU 객체

나중에 유지보수를 진행하게 될 경우, 뽑아내고자 하는 음식의 종류만 변경하면 되기 때문이다.

객체안의 객체 사용, 객체안의 배열 사용

객체를 사용하여 데이터를 저장하고자 할 때 떠오른 의문점이 있었다.
객체안에 객체를 사용하는 경우와 객체안에 배열을 사용하는 경우는 어떤 때 일까? 메뉴판 데이터를 생성하기 전에 좀 더 알맞는 데이터 구조를 만들고자 찾아보았다.

객체안에 객체를 사용하는 경우는 구조화된 데이터가 필요할때, 읽기 쉬운 코드가 필요할 때 주로 사용한다.
객체 안에 객체를 사용하면 데이터를 계층적으로 구조화할 수 있고, 이에 따라 코드가 읽고 이해하기 쉬워진다. 이러한 경우에 객체 안에 객체를 사용한다는 것을 알게 되었다.

객체안에 배열을 사용하는 경우는 순서가 중요한 데이터거나 동적 데이터일 때 주로 사용한다.
특정 순서로 진행되어야 한다거나 데이터를 쉽게 추가하거나 제거해야 하는, 동적으로 데이터를 다뤄야하는 경우에는 객체안에 배열을 사용하는 것이 더 좋을 수 있다는 것을 알게되었다.

그래서 나는 메뉴판 데이터를 생성할 때, 객체안에 배열을 사용했다. (음?)
key에는 메뉴명을 value는 배열을 사용하여 0번째에는 가격을 1번째에는 종류를 넣어, value에서 0번째 값을 꺼내면 무조건 가격만 나오도록 했다.

사실 객체안에 객체를 사용하는 것이 더 맞지 않을까? 생각하긴 했다.
객체안에 객체를 사용하는 것이 해당 메뉴의 가격과 종류를 명확하게 알 수 있어서 현재도 이것이 가장 좋은 방법일것이라고 생각한다.

그럼에도 객체안에 배열을 선택한 것은 value로 많은 값이 들어있지 않기 때문에 배열로 관리하는 것이 더 편할 것이라고 판단했다.
value의 값이 많이 존재했더라면 객체안에 객체가 훨씬 더 좋았을 것이라고 바로 판단했을 것이다. 하지만 해당 프로젝트에서는 value값으로 가격과 종류 2가지만 가지고 있기 때문에 객체대신 배열을 선택했다.

그래도 역시 장기적으로 보았을때는 객체안에 객체가 맞는 선택인것 같다..^^ 현재의 편안함을 위해 배열을 선택한..ㅎ..
그래서 어차피 복기니까! 만약 객체안의 객체로 메뉴를 만들었다면 어떻게 만들었을까? 생각이 들어서 간단하게 작성해봤다.

const MENU = {
  양송이수프: {
    price: 6000,
    type: '애피타이저',
  },
  타파스: {
    price: 5500,
    type: '애피타이저',
  },
  시저샐러드: {
    price: 8000,
    type: '애피타이저',
  },
  티본스테이크: {
    price: 55000,
    type: '메인',
  }
  ...
};

일단 메뉴의 이름을 Key로 하고 이에 대한 value로 객체를 넣어서, 해당 객체의 key로 가격과 종류를 넣었을 것 같다. 이러면 나중에 유지보수에도 편할 것 같다. 어차피 MENU 객체는 외부에서 수정할 일이 절대! 없으니까..
과거의 나는 무슨 정신으로 객체안에 배열을 쓴거지?

재입력받기, await 재귀함수가 아니라 return await

지난 3주차에서는 App 클래스 내부에서 await 재귀함수를 통해 정해진 입력형식이 아닐 경우 다시 입력받도록 했다.
이번 4주차에서는 입력받는 함수를 App.js가 아니라 Order 모듈을 생성하여 내부에서 재귀를 통해 재입력받기를 했다.

이때, 3주차와 같이 await 재귀함수를 사용하였는데 이렇게 되면 다시 입력받았던 값을 저장하지 못하고 undefined로 값이 변경되었다.
해당 오류를 수정하고자 챗GPT에게 물어본 결과.. await를 return으로 변경하기만 하면 되는 것이었다! return으로 변경하기 원하는 방향으로 잘 작동했다.

return을 사용하여 재입력받기 코드

3주차에서는 class 내부 함수로 생성했을 때는 await로 재호출하였지만 이번에는 Order이라는 함수를 새로운 파일에 생성하여 재호출을 진행하는 것이기 때문에 return을 사용해야 됐던 것이었다.
단순히 로직이 비슷하다고 해서 이전의 방식을 그대로 차용하는 것은 큰 위험이 될 수 있다는 것을 몸소 깨달았다.. 챗GPT 없었으면 하루종일 해당 에러를 잡고 있었을 생각을 하니..ㄷ

Object.freeze()?

Object.freeze()는 지난 3주차 미션의 다른 분들의 코드를 보면서 가장 많이 보았던 메서드였다.
이번에 해당 메서드를 처음 보았고, 대부분 상수를 만들때 사용하는 것을 보았다. 그래서 이 메서드를 정확히 알아보고 사용해보고자 찾아보았다.

해당 메서드는 객체를 얼릴때 사용하는 것으로 객체의 속성들을 수정할 수 없게 즉, 읽기 전용으로 만들어주는 메서드이다.
이 메서드를 활용하여 메뉴판 객체나 뱃지, 혜택 이름 리스트 등을 얼렸다. 해당 객체들을 외부에서 사용할 경우 변경되지 않는 데이터이기 때문에 Object.freeze()를 사용했다.

Object.freeze() 사용한 코드

객체를 얼리고 따로 파일을 생성하여 보관함으로, 반복되는 문구의 사용을 줄일 수 있어서 유지보수를 하는 것에 있어 큰 도움이 된다는 것을 알게되었다.

실수를 통해 배운점

객체의 길이를 알고 싶다면

객체에는 length를 바로 사용할 수 없다. 따라서 객체의 길이를 알고 싶다면 keys를 뽑아낸 배열에 length를 사용하는 방식으로 알 수 있다.

객체 길이 구하기

객체에 map함수 사용하기

map, reduce와 같은 함수는 배열에 사용하는 것이기 때문에 객체에는 바로 사용이 불가능하다. 따라서 위에 길이를 구한 방식과 동일하게 키, 값, 키-값 쌍의 형태로 배열을 출력하여 map함수를 사용하면 된다.

map함수를 객체에 사용하기

toThrow는 직접 던지면 안된다.

방문날짜 입력 테스트 실패

테스트코드를 작성할 때 toThrow를 사용했는데, 테스트 실패가 발생했다. 아무리 생각해도? 뭐가 이상한지 모르겠어서 영원한 친구 챗GPT에게 물어봤다.

toThrow 사용 에러 해결방법

음.. 그렇다! 함수를 인수로 받아 실행해야 하는데, 클래스를 직접 생성하는 코드를 전달해서 발생한 문제였다.

문제가 발생한 코드 방문날짜 입력 잘못된 테스트코드

따라서 함수를 인수를 받아 실행하도록 화살표 함수를 추가하여 테스트코드를 변경하였다.

문제를 해결한 코드 방문날짜 입력 올바른 테스트코드

어쩐지.. 다들 화살표함수를 추가하더니.. 다 이유가 있는 것이었다. 의문이 들때는 일단 실행해보고 왜 저렇게 사용하는지 알고 사용하도록 하자.

테스트코드 성공 방문날짜 입력 테스트 성공


프리코스를 마치며

이번 4주차까지 프리코스를 진행하면서 정말 많은 것을 배우고 얻고 느낄 수 있었다.
1주차때만 하더라도 포기할까 생각도 들었고 4주차까지 진행할 수 있을까 확신이 들지 않았다. 그런 내가 4주차까지 모든 미션을 예제 테스트 실행을 완벽히 하고 제출했다는 것이 믿기지가 않는다!!

불과 4주전까지만 해도 바닐라 자바스크립트로 기능을 구현해야 한다고 했을 때 정말 자신이 없었다. 클래스를 사용할 줄 몰랐고, 테스트코드를 직접 작성해본 적도 없으며, throw를 사용하여 예외를 던져본 적도 없었다. (과거의 나: throw가 뭐징?)
그러나 4주가 지난 지금, 라이브러리나 프레임워크 없이 기능 구현해라? 지금은 시간이 걸릴지라도 자신있게 완성은 할 수 있다고 말할 수 있는 사람이 되었다!

그리고 코딩 능력 말고 새롭게 얻은 것이 있다.
바로, 챗GPT 활용 능력. 이전에는 코딩하면서 사용한적이 손에 꼽을 정도로 없었는데, 이번 프리코스를 진행하면서 도저히 서칭만으로는.. 해결되지 않는… 문제가 많았기에 챗GPT를 적극 사용했다.
처음에는 물어봐도 뭔가 확실한 답을.. 얻을 수 없었는데, 4주동안 열심히 활용한 결과. 이제는 어느정도 원하는 답을 도출하기 위한 질문을 할 수 있게 되었다. 이제 챗GPT 없이는 못살앙… 내 영원한 코딩 친구💙

이 외에도, 문제 찾기 능력 (초보에서 중수는 된듯하다), 다른 사람 코드를 읽는 능력이 향상된 것 같다. 클래스 사용과 테스트코드 작성을 하는 방법을 도저히 감을 잡을 수가 없어서 다른 분들의 코드를 많이 봤었다. 덕분에 코드를 읽는 능력이 이전보다는 많이 향상된 느낌이랄까? 여러 파일과 폴더로 쪼개져있어도 이제는 코드를 보고 다음 파일을 따라가 읽는 능력이 향상됐다!
그리고 문제 찾기도.. 에러나는 곳을.. 정말.. 몇시간씩 찾았기에.. 안 늘수가 없는 것 같다.. 에러.. 이녀석.. 날 고통스럽게 했지만 얻는게 있었으니..! 봐준다(?)

이 4주는 몰랐던 것을 알고 배울 수 있는 시간이었고, 개발에 대한 두려움과 편견을 없애줬으며, 4주동안 꾸준히 미션을 진행한다는 명목으로 코딩을 하면서 개발에 대한 자신감과 꾸준히 공부해왔다는 자기만족감을 얻을 수 있는 시간이었다.
4주의 시간이 정말 아깝지 않은 시간이었고, 목요일 15시면 오던 메일이 오지 않는다는 것이 시원하면서도 그립다.

누군가에게 프리코스를 할까말까 고민된다면 나는 자신있게 도전해보라고 하고싶다!
프리코스는 우테코에 선발되지 않아도 많은 것을 배울 수 있는 기회니까.
한달동안 열심히 공부하며 성장했다는 것을 느낄 수 있는 기회니까!

학습 참고자료
객체에 해당 key값이 존재하는지 확인하는 방법
TIL | 객체 안의 객체 접근, 객체 안의 배열 접근
💙챗GPT💙

우테코: 프리코스 3주차 미션 복기

3주차 프리코스는 1,2주차 동안 활용해야겠다 생각했던 것들을 모두 활용할 수 있었던 미션이었다. 해당 미션부터는 클래스 활용과 테스트코드 작성이 정말 필수적이었기 때문이다.

그래서 기능 구현을 하기 전, 하루를 오로지 공부만 해야 했다. 이전에는 클래스를 사용해본적이 없기 때문이다. 학습하면서 어렵다고 느껴지고 이해가 완벽히 되는 느낌은 아니었지만, 미션 기능구현을 하면서 학습했던 개념들을 가지고 직접 코드를 작성하니 이해가 훨씬 잘 되었다.

프리코스를 마친 현재에도 해당 내용들이 잊혀지지 않고 진짜 ‘나의 지식’이 되었다는 느낌이 든다.

3주차: 로또 Repository 3주차 프리코스 미션 완료 캡쳐


새롭게 학습하고 적용한 점

reduce

2주차에 고차함수를 많이 활용하여 기능을 구현했지만 reduce는 사용하지 않았다. 이번 미션에는 마침 reduce를 사용하기 딱 좋을 기능이 있어서 활용해보았다.
reduce는 다른 고차함수랑 다르게 반환값이 배열이 아니라 값 하나이다. 그래서 수익률을 계산할때(ReturnOnInvestment.js) 해당하는 값 하나만 얻으면 됐기에 reduce를 활용하여 당첨금액을 구했다.

reduce를 활용한 코드

이전에는 reduce는 사용하기 어렵고 이해하기 힘들다!라는 느낌이 너무 강해서 사용하기 꺼려졌었다. 하지만 이번에 고차함수를 마스터해보고자 남은 하나인 reduce도 3주차에 사용하게 되었는데, 막상 사용해보니 전혀 어려운 개념도 아니고 활용하기 까다롭지도 않았다. 오히려 필요한 값만 딱 return하니 굉장히 편한 함수였다..!
그간 내가 편견을 가지고 있었다는 것을 알게 되어서, 앞으로도 이런 편견을 가지고 있던 것들을 하나씩 부숴봐야겠다 생각이 들었다.

Class

1,2주차 프리코스를 하면서 기본적으로 주는 App.js파일이 class App으로 시작하는데, class를 활용해야 겠다~ 생각만 하고 개념만 찾아봤지 직접 사용하지는 못했었다.
하지만 이번 3주차는 정말 class를 사용해야 했기 때문에 JAVASCRIPT.INFO를 정독하면서 학습했다. 해당 문서를 읽을때 완벽히 이해가 된다거나 어떻게 활용을 해야겠다 라는 느낌이 오지 않았었지만, 일단 개념을 머리에 넣어두고 기능 구현을 시작했다.

기능을 구현하면서 작성되어있는 Lotto.js 파일에서 클래스를 어떻게 활용하고 있는지, LottoTest.js 파일에서 요구하는 작동 범위가 뭔지를 먼저 파악했다. 해당 파일에서 사용한 class 사용 방식과 class를 적극 활용하신 다른 분의 2주차 코드를 참고하면서 코드를 작성하니 어느새 class를 나름 사용할 수 있는 수준이 되었다.

아직은 조금 부족하지만 클래스를 처음으로 활용한 코드! 클래스를 활용한 코드

처음에는 prefix, 클래스 필드가 어떤 건지 전혀 몰랐고, 1주차 코드리뷰에서 validate를 활용하여 예외 처리를 하는게 어떻냐는 의견을 받았을 때 validate를 뭘 어떻게 쓰라는 건지, 유효성 검사라는 서칭 내용만 뜨고 js에서 활용하는건 어떻게 하는지 전혀 몰랐었다.
이런 상태였지만 이번 3주차 프리코스를 마치니 이제는 prefix가 뭘 말하고 클래스 필드는 뭔지, validate를 어떻게 사용해야 할지를 알게되었다!

도메인 로직

도메인 로직에 단위 테스트를 구현해야 한다라는 프로그래밍 요구사항이 존재했는데, 단위 테스트는 들어봤어도 도메인 로직은 이번에 처음 들었다. 그래서 도메인 로직이 뭔지 검색했다.

내가 이해한 도메인 로직이란! 프로그램이 돌아가는데 중요한 역할을 수행하는, 이 프로그램에서 A라는 기능이 빠지면 제대로 프로그램이 돌아가지 않는다!라면 A는 도메인 로직이다 라고 이해했다. 또한 도메인 로직은 비지니스 로직과 동일한 용어라는 것도 알게되었다. (제대로 이해한거 맞겠지?!)

도메인 로직이 뭔지 알게되니 도메인 로직에 단위 테스트를 구현하라는 요구사항은 프로그램이 돌아가는데 중요한 역할을 하는 코드가 잘 실행되는지 테스트하라!는 말이라는 걸 알게되었다.

jest 테스트코드 작성

위의 도메인 로직 개념을 학습한 후, 테스트 코드를 작성하려고 하니 어떤 파일을 테스트 해야할지 단번에 알 수 있었다. (이것이 바로 학습의 힘. 아는 만큼 보인다.)
2주차 미션을 진행할 때는 전혀 감을 잡을 수 없어 얼렁뚱땅 테스트 코드를 작성했었던 것에 비해 엄청난 발전이었다.

2주차까지는 테스트 코드를 어떻게 작성해야 할지 머리는 이해했지만 가슴은 이해하지 못해 잘 작성하지 못했다. 이런 상태였지만 3주차는 테스트코드를 작성하는 것도 매우 중요한 사항이었기 때문에 어떻게든 가슴도 테스트코드 작성 방식을 이해하고자 LottoTest.js 파일과 ApplicationTest.js 파일의 코드를 정말 하나하나 뜯어가며 이해하려 했다.

생각보다 쉬웠던 테스트코드 작성 테스트 코드

이로인해 예외 테스트와 원하는 결과가 잘 나오는지 기능 테스트 코드를 작성할 수 있었다. 이번에 테스트 코드를 직접 작성하면서 느낀것은 생각보다 테스트 코드 작성하는 것은 별거 없었다.. 그냥 내가 무서워하고 있었구나를 깨달았다.

테스트코드 실행 결과

테스트코드 실행 결과


실수했던 점

예외 반환 문구랑 기능테스트 문구에서 테스트 실패

예외 반환시 [ERROR]가 잘 포함되어 출력되는데 테스트 실패가 되어서 무슨 문제일까 엄청 찾았다. 문제는.. 제공하는 Console 사용안하고 그냥 console.log로 error를 출력하고 있어서 테스트 실패가 됐던것이었다. (진짜 바보냐..ㅠ)

기능테스트 문구(ApplicationTest.js)에서도 테스트 실패가 발생했었는데, PrintValue 클래스에 작성한 출력 문구에 띄어쓰기를 하나 더 해서.. 테스트 실패가 됐던 것이었다.^^;

위와 같은 실수를 통해 작은 실수가 1시간 그 이상을 허비하게 되니 코드를 작성할 때 신중하게 작성해야겠다는 것을 뼈저리게.. 배울 수 있었다. (진짜 신경써서 한줄 한줄 작성하자.)


마치며

3주차는 1,2주차에 가볍게 넘어갔던 개념들을 적극 활용해야 했었다. class와 jest 테스트코드 작성은 이번 3주에 프로그래밍 요구사항으로 명확히 주지 않았더라면 사용하는 방법을 아직도 잘 몰랐을 것이고 어렵다는 편견을 가지고 있었을 것이다.
하지만 3주차 미션을 통해 편견을 부술 수 있었고 이제는 온전히 나의 지식이 되었다. (진짜 뿌듯하다…!!!) 앞으로도 학습에 편견을 가지지 말고 도전하려고 노력할 것이다. 지금 당장 떠오르는 건 무서워서(?) 미뤄뒀던 Redux 사용하기이다. 이번 프로젝트 리팩토링때 사용해보고자 한다!

학습 참고자료
javascript: Array.reduce() 사용 방법 정리
자바스크립트 고차 함수(Higher-Order Function) 이해하기
class(클래스)에 대해 알아보자
JAVASCRIPT.INFO / CLASS
자바스크립트 클래스 필드와 이벤트 핸들러
프로토타입과 클래스
도메인 로직이 뭐지 🧐
jest로 테스트 작성하기
TDD 시작하기 (Unit Test, Jest)
테스트 코드로 JS 의 기능 및 로직 점검하기

우테코: 프리코스 1주차 미션 복기 (feat. git 대소문자 구분 설정)

2주차가 곧 끝나가는 시점이지만.. 지금이라도 1주차 미션을 복기해보려고 한다.

나는 그간 React를 조금 한 것 말고는 바닐라 JS로 기능을 구현해본 적이 없다. 있다면 그나마, 학교 강의시간에 간단한 기능 구현정도만? (정말 간단한 기능들…)

그렇기에 처음 1주차를 시작할때는 정말.. 쉽지 않았다. 솔직히 좀 어려웠다. 그나마 프로젝트를 진행한다고 node나 git 등을 다뤄봐서 다행이지, 한번도 사용해보지 않았더라면 프로젝트 설정에서 막혔을 것이다.

그리고 처음 보는 것들이 많았어서 그거에 대한 궁금증 때문에 정작 기능구현은 그리 빨리 시작하지 못했다. 1주차를 하면서 정말 크게 느낀 것은 선택과 집중을 잘 해야 한다는 것이었다.
다른 것을 신경쓰다가 가장 중요한 기능 구현은 실패헀을 수도 있다..

다행이도.. 그간 코딩좀 타탁거렸던 실력으로.. 정말 많은 난관들이 있었지만.. 잘 해결해서 테스트 실행을 통과시킬 수 있었다.

1주차: 숫자 야구 Repository 스크린샷 2023-10-31 23 56 56

서론은 여기서 마치고 1주차 미션을 진행하면서 발생했었던 일들에 대해 작성해보고자 한다.

테스트 실패


아무리 이상하다 싶은 코드를 수정하고, chatGPT랑 Bard에게 물어봐도.. 테스트에 실패하는 원인을 찾을 수가 없었다. 이거 찾는게 진짜 굉장히 오래 걸렸다.. 근데, 이 원인은 나의 바보같은^^.. 행동으로 인해 발생한 것이었다.

  • pickUniqueNumbersInRange()의 달콤한 유혹

    나는 처음에 랜덤 숫자를 추출할때 pickUniqueNumbersInRange()를 사용하였다. pickUniqueNumbersInRange 사용 코드

    모든 기능 구현을 완료한 후, 로컬에서 테스트를 실행하니 모든 테스트가 실패했다. 무엇이 문제일지 한참 고민하다가, ApplicatioinTest.js에 작성된 코드를 하나 하나 잘 읽어보니 랜덤 숫자를 추출할때 pickNumberInRange()를 사용하고 있었다.
    설마..? 사용한 함수가 달라서 테스트가 실패하는 것은 아닐까..? 라는 생각에 pickNumberInRange()를 사용하도록 코드를 변경하니..
    테스트가 잘 실행되어 테스트에 성공했다..

    그렇다. 이 사항은 README 프로그래밍 요구사항에 적혀있었다. 리드미파일 프로그래밍 요구사항 … 꼭.. 요구사항은.. 진짜.. 꼼꼼히 살펴야 겠다고 몸소 느꼈다..
    이것때문에 몇시간을 버린거야 진짜…

  • error 출력문 문제

    이상하게 예외 테스트만 실행하면 아래 사진과 같은 에러가 계속 발생했다.

    예외 테스트 실패

    이 에러만 이틀?정도 계속 찾았던 것 같은데, 에러의 원인은 정말^^.. 허무했다.

    에러 원인 코드

    위 사진에서 어떤 코드가 에러를 발생시킨 것일까?
    두번째 else if문을 보면.. throw를 new Exception으로 던지고 있다. 여기서 Exception은 위에 내가 만들었던 것이다.
    그렇다. new Error가 아닌 new Exception으로 에러를 던지고 있기에 “[ERROR]”형식이 맞지 않다는 이유로 해당 테스트가 실패했던 것이다..
    new Exception은 처음 throw를 적용하면서 봤던 글에서 사용하던 방식인데, 이게 나의 발목을 잡을 줄이야.. 앞으로는 조심하도록 하자..

예기치 못한 오류로 인하여 실행에 실패하였습니다.


그렇다. 나는 일명 ‘예기치씨’라고 부른다. 위 문구는 이번 미션에서 날 환장하게 만든 문구이다.
왜 날 환장하게 만들었냐. local에서 test를 실행했을 때는 잘.. 예제 테스트 실행이 되었지만, 제출을 한 후에 페이지에서 에제 테스트를 실행하면 위 문구가 뜨면서 예제 테스트에 실패했다..
정말 해당 문제를 해결하느라 힘들었다..

  • new App() 코드문 삭제

    빌드가 실패된 첫번째 이유는 new App() 코드를 주석처리했기 때문이다.
    로컬에서 test를 할 때에는 해당 코드가 필요 없다. 그래서 주석처리를 해뒀었는데, 이로 인하여 우테코 페이지에서 예제 테스트를 실행했을 때 빌드가 실패한 것이다.
    절대.. 빼먹지 말고.. 작성하자..

  • 파일명 변경으로 이전 파일명이 적용

    빌드 실패의 두번째 이유는 파일명이 엉켜 import에 실패한 것이다.
    처음에 파일명이 InputNumber.js였다고 한다면, 리팩토링을 진행하면서 파일명을 inputNumber.js로 변경했다. 지금에서야 알게된 사실은 변경할 필요가 없었다..

    파일명이 변경되면서 App.js에 import 했던 경로의 파일명도 변경해줬다. 그리고 로컬에서 test를 실행했을 때 테스트에 성공했다. 그러나 빌드에서는 실패했다.

    로컬에서는 변경한 이름(inputNumber.js)을 이전 이름(InputNumber.js)과 동일하게 여겨 import를 진행하여 테스트가 통과됐던 것이었다. 이 사실을 알고 git status를 확인했을 때, 변경한 이름이 아닌 이전 이름(InputNumber.js)으로 변경사항 일어났다고 되어있었다.
    즉, 나는 파일명을 파스칼케이스에서 카멜케이스로 변경했지만 로컬에서는 파일명 변경이 이뤄지지 않았다고 판단한 것이다!!

    나: 파스칼 -> 카멜로 파일명 변경 (철자는 동일, 대소문자만 변경)
    나: App.js에도 import할때 파일 경로 카멜케이스로 작성한 이름으로 변경
    로컬: App.js에 inputNumber.js로 작성했네? inputNumber.js 파일 존재하네! ㅇㅋ inputNumber.js 파일 불러오겠음
    git: inputNumber.jsInputNumber.js랑 철자 동일한데? 난 대소문자 구분할 줄 몰랑. 그러니 파일명 변경된거 없음ㅇㅇ! ( InputNumber.js로 저장)
    => 결론, pull request에는 파일명 바뀐거 없음

    그러나 우테코 페이지에서는 pull request에 반영된 마지막 커밋을 가지고 빌드를 진행하는데, App.js에 import한 경로가 inputNumber.js로 작성되어져 있고 저장된 파일명은 InputNumber.js로 되어있으니, 해당 파일을 찾을 수 없다면서 빌드 실패(예기치씨 등장)가 된 것이다.

    진짜 최종 결론, git 글로벌 설정으로 대소문자를 구분하자

    • git 글로벌 대소문자 구분 설정

      git config core.ignorecase false
      단, 이미 push된 파일에는 해당 설정으로 꼬일 수 있다. 따라서 아래 방법을 사용하자.

    • 원하는 파일만 이름 변경

      git mv user.js User.js
      user.js에서 User.js로 변경됨

미션을 진행하면서 배운점


  • typeof Number('string')의 결과값

    플레이어에게 값을 입력 받을 때, 여러 예외처리를 해야 했다. 이때, 입력 받은 값은 무조건 문자열로 들어오게 되는데 이 입력받은 문자열을 숫자로 변환되는지 안되는지를 가지고 예외처리를 하려고 했다.
    그래서 '안녕하세요'를 입력받았을 때 해당 값을 숫자로 변경하면 NaN을 반환하기를 기대했다.
    그래서 확인하기 위해서 typeof Number('string')을 실행했더니 값이 NaN이 아니라 number로.. 나오는 것이다..?!
    이 결과가 도저히 이해가 안돼서 우리의 영원한 두번째 친구, Bard에게 물어봤다.

    bard의 답변

    음 그렇다! typeof Number(아무거나) 실행하면 Number()로 인해 무조건 number로 결과가 나오는 것이다.

    이런 결과를 이번 미션을 진행하면서 처음 알게 되었다.
    이 외에도 새로 알게된 사항은 글로 적어뒀다.

  • 좋은 깃 커밋 메세지
  • npm permission denined 에러
  • git reset -hard 경로 : 원하는 시점으로 되돌리기
  • throw를 통한 예외처리
  • 기명 함수표현식?

코드리뷰


1주차 미션이 종료되고 내가 작성한 코드를 다른 분들에게 리뷰를 받을 수 있는 기회가 생겼다! 그래서 용기를 내서 코드리뷰를 부탁드렸다.

코드리뷰를 처음 받아보는데, 이번에 느낀 것은 코드리뷰는 기회가 생긴다면 꼭 받아야한다! 였다. 내가 보지 못했던 부분들을 짚어주시고 더 좋게 보완할 수 있는 사항들을 알려주셨다.

코드리뷰 정리

  • 변수 네이밍컨벤션
    const를 사용한다고 무조건 상수 네이밍컨벤션이 아닌, lowerCamelCase를 사용하는 변수 네이밍켄벤션을 사용할 것

  • 메서드 안의 지역변수라면 대문자 스네이크케이스보다 카멜케이스를 사용하기

  • boolean형 변수들 이름은 is__형식을 주로 많이 사용

  • for문은 break나 return 사용이 아니라면 고차함수 (forEach / map / filter / reduce)를 사용하는 것을 추천

  • 매직넘버 사용하기
    의미 전달이 확실하기에 사용을 매우 추천

  • print를 할때 변수가 없다면 ``가 아닌 ‘’ 사용

  • 변경되지 않는 값이라면 변수로 만들어서 명시적으로 알릴 것

  • Set 자료구조를 이용하여 중복 제거하기

코드리뷰를 해주신 친절하신 분들 덕분에 정말 많이 배울 수 있었다..💜
처음 알게된 개념들도 많았고 내가 놓치고 있던 개념들도 다시 공부하게 되는 계기가 됐다.
실제로 2주차 미션을 진행하면서 리뷰 받았던 사항들을 거의 다 적용해봤다! 덕분에 코드가 한결 이해하기 쉬워진 것 같다.

마무리


1주차 프리코스 미션을 진행하면서 정말 많이 성장할 수 있었다. 비록 일주일이라는 길고도 짧은 시간이었지만 새로 알게된 내용들이 엄청나게 많았다. 그동안 내가 모르고 해왔던 나쁜 습관들도 잡을 수 있게 되었고, 새롭게 알게된 내용을 통해 코딩을 하는 것에 있어 많이 자신감을 얻을 수 있었다.

이번 1주차 미션을 진행하면서 느낀 것은 우테코에 최종 선발이 되면 너무너무 좋겠지만, 선발되지 않더라도 프리코스를 통해 배운점이 너무나도 많기 때문에 후회되지 않은 정말 뜻 깊은 시간이었다라고 생각할 내 미래의 나의 모습이 벌써부터 보였다.

그리고 우테코에서 왜 코딩테스트를 하기 전에 프리코스라는 기간을 두는지의 이유와, 이 프리코스가 단순히 선발을 위해서가 아닌 많은 사람들에게 성장의 기회를 주기 위한 것이라는 걸 몸소 느꼈다.
프리코스라는 성장의 기회를 준 우테코에게 정말 고맙다는 말을 전해드리고 싶다! (선발돼서 직접 말씀드릴 수 있게 되면 좋겠다ㅎ)

그럼 앞으로 남은 3주 (글 작성 현재 2주) 화이팅하자!

학습 참고자료
Git: 대소문자 구분 이슈
git 파일명 대소문자 변경했을 때 제대로 인식하지 못하는 문제

JavaScript: 함수 선언식, 함수 표현식, 기명 함수 표현식

프리코스 1주차를 하면서 코드 컨벤션 가이드에 ‘함수 선언식 대신에 기명 함수표현식을 사용하세요’ 라는 문구가 있었다.
이때, 함수 선언식과 표현식의 차이가 뭐지…?라는 생각이 들었고 찾아보았다.

함수 선언식


함수 선언식은 호이스팅의 영향을 받는다. 따라서 블록문 밖에서 호출이 가능하다.

호이스팅(hoisting): 코드가 실행되기 전, 변수 및 함수선언이 해당 스코프의 최상단으로 끌어 올려지는 것

name(); // 함수가 선언되지 않았어도 정상적으로 작동한다.
function name() {
	// 내용
}

함수 표현식


함수 표현식은 호이스팅되지 않는다. 따라서, 정의된 범위에서 로컬 변수의 복사본을 유지할 수 있다.

name(); // 함수가 선언되지 않았으므로 에러가 발생한다.
let name = function () {
	// 내용
};
  • 클로저

    클로저는 함수를 실행하기 전에 해당 함수에 변수를 넘기고 싶을 때 사용된다.

    사용예시

    function tabsHandler(index) {
    	return function tabClickEvent(event) {
    		// 바깥 함수인 tabsHandler() 의 index 인자를 여기서 접근할 수 있다.
    		console.log(index); // 탭을 클릭할 때 마다 해당 탭의 index 값을 표시
    	};
    }
    
    let tabs = document.querySelectorAll('.tab');
    let i;
    
    for (i = 0; i < tabs.length; i += 1) {
    	tabs[i].onclick = tabsHandler(i);
    }
    
  • 인자 전달

    일반적으로 함수 표현식은 임시변수에 저장하여 사용한다. 그러나 임시 변수에 할당 할 필요없이 함수에 직접 전달할 수 있다.
    아래와 같이 콜백함수로 사용할 수 있다.

    사용예시

    let arr = ['a', 'b', 'c'];
    arr.forEach(function () {
    	// ...
    });
    

기명 함수 표현식


함수 표현식으로 함수를 정의한 것에 이름이 존재한다면 이를 기명 함수 표현식이라고 부른다.

const name = function information() {
	// 내용
};

기명 함수 표현식이 일반 함수 표현식과 다른점은 다음과 같다.

  • 이름을 사용해 함수 표현식 내부에서 자기 자신을 참조할 수 있다.

  • 기명 함수 표현식 외부에선 그 이름을 사용할 수 없다.

let sayHi = function func(who) {
	if (who) {
		alert(`Hello, ${who}`);
	} else {
		func('Guest'); // func를 사용해서 자신을 호출한다.
	}
};

sayHi(); // Hello, Guest

// 하지만 아래와 같이 func를 호출하는 건 불가능하다.
func(); // Error, func is not defined (기명 함수 표현식 밖에서는 그 이름에 접근할 수 없다.)

참고
객체로서의 함수와 기명 함수 표현식
함수 표현식 VS 함수 선언식
함수 표현식 vs 함수 선언식

Git reset: 원하는 시점으로 되돌리기 (feat. git push -f)

1주차 프리코스 기능 구현을 완료하고 리팩토링을 진행하고, 우테코 홈페이지에서 예제 테스트를 실행하니 “예기치”씨가 등장했다…
어떤 것이 빌드를 실패하게 만든 것인지 찾기 위해 계속 커밋하고, 테스트 돌리고, 커밋하고를 반복했으나 찾을 수 없었다.
그래서 나는… 예제 테스트가 잘 돌아가던 시점으로… 돌리기로 했다…

git reset


이동하고 싶은 커밋 시점으로 되돌려주는 git 명령어이다.

  • git reset (--mixed)

    기본 명령어로, 돌아간 이후 변경 내역이 남아 있으나 인덱스 값이 초기화된다.

  • git reset --soft

    원하는 시점으로 돌아가고 이후의 커밋을 유지한다.

  • git reset --hard

    원하는 시점으로 이동하고 이후의 커밋들은 모두 사라진다.

  • 직전의 커밋으로 되돌리기

    git reset --hard HEAD^

  • 원하는 커밋으로 되돌리기

    git reset --hard <원하는 커밋의 해시값>

이동하고 싶은 커밋의 해시값 확인하기


  • git log

    git의 커밋 및 푸시 내역을 확인할 수 있다.

  • Github 레포지토리에서 확인하기

    해당 저장소의 Commits에서 원하는 되돌리고 싶은 커밋의 해시 값을 간편하게 찾을 수 있다.

    깃허브 로그 화면캡쳐

git reset 후 pull 에러 해결


git reset이 잘 실행되어 이전으로 되돌아간 것을 확인한 후, push를 하려고 했더니 에러가 발생했다..
push 하기 전에 pull 하라고 해서 했지만 그래도 응… 안됐다. 그래서 찾은 방법. 강제로 push 해버리기!

강제로 push 하기


git push -f origin <branch> push 명령어를 실행하지 못해도 강제로 원격 저장소에 push 한다.

참고
Git에서 특정 커밋 시점으로 되돌리기
Git 커밋 취소(reset), 커밋 되돌리기(revert), 덮어쓰기(amend)