이번 2주차는 알고리즘 문제가아니라 기능문제였기에 특히 배운것도많고 많이 성장했다고 느꼈다.
1주차와 마찬가지로 기능목록을 구현하고 우리가 어떤 기능을 만들어야될지 설계를 한 뒤 각자 생각하는 근거에따라서 작업물을 커밋하고 푸시하는 방식으로 진행됐다.
그리고 소스코드에 테스트 파일이 있는데 이 테스트케이스를 통과해야 점수를 취득 가능했다.
이 테스트 코드에서 App이라는 객체의 Play Method를 기점으로 진행됐기에 이번 기능은 클래스로 설계를 진행했다.
https://github.com/woowacourse-precourse/javascript-baseball
자세한 설명은 위에 링크를 참조해주길 바란다.
제일 먼저 기능목록을 만들고 해당작업을 푸시하는걸로 시작했다.
이번에 기능구현을 콘솔창으로 진행하는 문제였는데 이런 기능 개발은 처음이었고, 테스트코드(Jest)도 작성해보고 싶어서 도전해봤는데 이또한 처음이었기에 많이 애먹었다.
또한 이전에는 코드를 분할해서 파일을 import한적이 없어서 몰랐는데 파일을 Import 하려고 보니, require()를 이용한 CommonJs방식 기존 리액트에서 default인 ESModule방식이 있다는걸 알게 됐고 이것부터 공부를 시작했다. (Jest의 디폴트가 CommonJs라는것도 알게됐다.그래서 애먹었다..)
제일먼저 docs폴더에 README를통해서 기능목록을 담아뒀다.
그 후 메인으로 기능을 사용하는 App.js컴포넌트가 있고 이때 App.js에서 굳이 필요하지 않은 함수들을 담아두는 Utils.js를 Utils폴더안에 담아두고 사용했으며, 이번에 게임이다보니 고정적으로 출력되는 String을 상수로써 정의하고 따로 파일을 정리해뒀다.
root
├── package-lock.json
├── package.json
├── __test__
| ├── 메인테스트.js
| ├── 함수1테스트.js
| └── 함수2테스트.js
├── docs
| └── README.md
└── src
├── App.js
├── constant
| └── constant.js
├── utils
└── utils.js
자세한 설명은 내가 이해를 하는데 도움이 됐던 블로그로 대체하겠다.
https://yceffort.kr/2020/08/commonjs-esmodules
요약하자면 ESModule에서는 리액트에서 한것처럼 파일을 export하고 해당 모듈을 다른 파일에서 import해서 사용하는 방식이고 CommonJs는 해당 파일을 module.exports를 통해서 우리가 원하는 값을 객체로 내보낸뒤 해당값을 require()를 이용해서 값을 사용 할 수 있다.
그래서 기존에 했던 ESModule방식으로 Import해서 사용했었는데 Jest의 Test코드에서 에러를 띄웠다.
Cannot use import statement outside a module
const CJS = CommonJs, const ESM = ESModule // 계속 글에서 반복되는것 같아서 왼쪽처럼 정의 해봤습니다. ㅋㅋㅋ
에러 내용을 유추해보니 모듈의 문제겠구나 싶어서, 위처럼 CJS와 ESModule에 대해서 공부하게 됐고 찾아보니 Jest는 기본값이 CJS라서 저런 오류를 띄웠던것이었다.
그래서 궁금했던게 그러면 CJS방식을 ESM방식으로 Jest에서 진행할 수는 없을까?였고 라이브러리를 사용해서 해당부분을 가능하게 해줬다.
하지만 이번 프리코스 진행에서 package.json파일을 수정하면 안되기도 했고, CJS방식으로 공부를 해보고자 이번 프리코스에서는 CJS방식으로 진행했다.
// 간단 사용 예시
// utils.js
module.exports = {
computerUniqueThreeNumbers() {
const computer = [];
while (computer.length < MAX_NUMBER) {
const number = Random.pickNumberInRange(1, 9);
if (!computer.includes(number)) {
computer.push(number);
}
}
return computer;
}
}
...
// App.js
const utilFun = require("./utils/utils"); // 이렇게 파일을 받아서
utilFun.computerUniqueThreeNumbers()를 하면 해당함수를 사용 할 수 있다.
기본 컨벤션은 아래 주소를 적용했다.
https://gist.github.com/stephenparish/9941e89d80e2bc58a153 깃 컨벤션 가이드
거기에 더해 평소에 작업할때 VsCode Extension인 Gimoji를 사용했었을때 팀원들에 만족도가 높았고 나또한 그랬었기에 이번에도 사용했다.
✨ feat($기능4) : 기능목록 4 구현 //사용 예시
기능4 야구 게임 시작 $기능3:
- 기능이 추가됨으로써 추가 상수를 constant에 선언
- 게임을 승리할 baseballGameWin method를 통해 유저의 재실행 여부를 묻는다.
- ApplicationTest부분 통과
이번에 기능구현 하면서 commit을 얼마나 쪼개야될지 정말 고민을 많이했다.
왜냐하면 이번에 기능목록에 따라서 기능을 구현하다보니 여러 파일을 손봐야되는 경우가 생겼고, 이 파일을 하나하나씩 작업후 커밋해야할지, 작업양이 많지않으니 한번에 커밋해야할지 고민을 많이했다.
그래서 테오의 프론트엔드 카톡방에 질문을 남겼더니 친절하게 답변주셨다.
Q) a라는 기능을 만들기 위해서 b와c파일을 수정하고 a를 만들어야 한다면 b작업후 commit c작업후 commit a를 만들고 commit 이후 푸시가 맞을까요 ?
아니면 모든 작업하고 한번에 커밋해서 메시지를 잘 작성후 push하는게 맞을까요?
A) 커밋 크기에 따라 다르지 않을까요? B, C 작업의 크기가 크지 않다면 같이 해도 된다고 생각합니다. 물론 ABC 전부 나눠서 하는것도 좋은 것 같아요.
그래서 나는 작업양이 많지도 않고 내가 작업하는양은 커밋메시지로 커버될것같아서 한번에 작업한걸 add하고 commit해서 기능하나 완성될때마다 push했다.
이후에 유사한 경험이 생긴다면 작업양이 많을경우 각각 커밋을 진행하고 기능이 완성되면 push해야겠다고 생각했다.
이번에 처음으로 테스트코드를 만들어보고 적용해봤다.
여러 라이브러리가 있었지만 이번에 미션테스트를 Jest로 작성해서 템플릿으로 주기도 했고, Jest관련 자료가 많다고 생각해서 망설임없이 Jest로 작성했다.
처음 Jest를 사용하기도 했고, 이번 미션 중점사항인 함수에 테스트를 적용해보라는 조언을 보고, 내가 만들었던 함수에 단위테스트를 적용했다.
처음엔 고민했다. 내가 만든 모든함수에 단위테스트를 적용해야하는가?
하지만 많은 아티클을 보고 TDD방식도 읽어봤을떄 불확실성이 높을 때 적용하면 효과를 얻을 수 있다고 명시돼있었기에 불확실성이 높은 함수들에 단위테스트를 적용했다.
그래서 나는 함수들중에서 유저와 관련된 부분이 불확실성이 높다고 생각했고, 관련 함수에 테스트코드를 작성했다.
describe("유저 입력값 validation", () => { //이 함수의 이름
test("받은 값이 올바른 값인지", () => {
const userInput = ["107", "5732", "ad2", "112", "a"];
const errorMessage = [
"1부터 9사이의 정수를 입력해 주세요.",
"세자리 숫자를 입력해주세요!",
"숫자를 제외한 문자를 입력하셨습니다",
"서로 다른 세 숫자를 입력해주세요!",
"숫자를 제외한 문자를 입력하셨습니다",
];
for (i = 0; i < userInput.length; i++) {
expect(() => utilFun.checkUserValid(userInput[i])).toThrow(
errorMessage[i] // expect()우리가 테스트할값, to-- 기댓값
);
}
});
});
라이브러리를 리액트로 시작했고, 내가 시작한시기에 리액트는 훅을 이용한 함수형코딩이 유행했던터라 나는 함수형코딩으로 자연스럽게 코드를 작성했다.
그래서 클래스는 무엇이고 SOLID원칙이 있으며 객체지향형코딩방식은 어떤건지정도만 알고 있었지만 이번에 Class에대해서 많이 공부했다.
공부할때 드림코딩강의를 많이 들었었기에, 강사님이신 Ellie강의를 참고해서 공부했다.
이번 기능에 대한 피어리뷰를 진행하면서 클래스에서 JS는 다른언어와 달리 Private이없고 JS에서는 해당Property앞에 #을붙임으로써 같은 역할을 한다고 말씀해주신 동료분의 관점에서 생각해보니, 이번 기능에서 App.js의 App이라는 Class에서 Play라는 Property말고는 사용하면 안되는게 맞는것 같아서 후에 다른 모든 Property에 #을 붙이는 방식으로 리팩토링했다.
class App {
constructor() {
this.computerRandomThreeNumber = 0; //값들을 constructor에 보관
this.userRandomThreeNumber = 0;
}
#init() {
this.computerRandomThreeNumber = utilFun.computerUniqueThreeNumbers();
}
#baseballGameWin() {
...
play() {
this.#init();
Console.print(ANNOUNCEMENT_MESSAGE.START);
Console.print(this.computerRandomThreeNumber);
this.#getUserNumbers();
}
}
1주차때와는 달리 점차 체계적으로 코딩하는것 같다.
1주차때는 기능목록을 만든다뿐이지 기능을 설계해가면서 코딩한다는 느낌은 받지 못했는데, 이런 방식이 적응되고 테스트코드까지 적용해봄으로써 체계적으로 한다는 느낌을 많이 받았다.
더해서 설계를 계속하다보니 내가 어떤식으로 설계하면 좋을지 + 설계하는 과정이 점점더 매끄러워지는것 같다.
테스트코드가 주는 장점
이번에 처음으로 테스트코드를 적용해보고 내가만든 함수들을 테스트해봤다.
테스트코드를 작성하면서 내가 기존에 만든 함수들이 잘 짜여졌는가, 설계에 문제는 없었는가에 대한 부분을 고려하게 됐고 함수를 잘 분리해서? 혹은 함수가 복잡한 기능을 하지 않아서였는지 모르겠지만 문제는 없었다.
그리고 처음에 템플릿으로 주신 이번 야구게임을 테스트하는 코드를 많이 뜯어보고 공부하면서 내가 이 기능을 다만들었을떄 종합테스트를 진행했을때 올바른 결과가 나왔을때 희열감 또한 있었던것 같아서 다음에 불확실성이 높은 부분에서는 부분적으로 테스트코드를 적용해볼 예정이다.
함께 성장하는 기쁨
이번에 커뮤니티를 적극 이용하면서 피어리뷰도 하고 모각코도 참여했다.
그리고 기존에 기능을 다 만들고 서로 리뷰를 하는 스터디도 있었는데, 이런 모든걸 적용해보면서 내가 부족한 부분도 캐치하고 동료의 부분도 캐치해주면서 서로 성장한것 같다.
이렇게 느꼈던 이유가 첫주차때 사람들의 코드와 같이 스터디하신 코드, 내 코드를 둘째주차와 비교해보면 많이 달라진것 같았다.
그리고 사람들이 열심히 참여하는 모습을 보면서 많은 자극을 받고, 동기부여를 하면서 이번에 무사히 2주차를 끝낼 수 있었던것 같다.
리액트는 정말 잘 만들었구나
항상 CRA로 잘 세팅된 템플릿으로 작성하면서 JS에 깊이 알지 못했다.
하지만 이번에 파일을 내보내고 받아오는것도 적용해보고 Babel도 찍먹해보니 기본 JS가 많이 부족했고 나는 JS코더가 아닌 리액트만 쓸줄 아는 코더라는 느낌을 많이 받았다.
그래서 아 리액트를 왜 사람들이 많이 이용하고 정말 잘 만든 라이브러리라는 생각이 듬과 동시에 JS를 더 공부해야겠다는 생각을 많이 했다.
이제 2주차도 끝나고 4주차가 끝날때까지 얼마 남지 않았는데 끝까지 성실하게 완주하고자 한다.
그리고 내가 주도적으로 진행해보고 싶어서 지금 스터디를 슬랙 커뮤니티를 이용해서 하나 만들었다.
그리고 위에서 작성했던것처럼 기본 JS를 중점으로 공부 할 생각이고 돌아만 가는 코드가아닌 완성도 높은 코드를 연습해서 이런 과정이 디폴트가 되는 개발자가 되고싶다.