[우아한테크코스 5기] 프리코스 2주차 미션 회고
![[우아한테크코스 5기] 프리코스 2주차 미션 회고](https://cdn.hashnode.com/res/hashnode/image/upload/v1705310260669/d2a7ebca-fa2b-43fd-8869-b788be0cff79.png)
벌써 프리코스의 절반이 지났습니다. 2주동안 생각보다 많은 점을 배우게 되었습니다. 앞으로 남은 2주동안에는 얼마나 많은 것들을 깨닫게 될지 벌써부터 기대가 됩니다!
PROBLEM
시작 전
1주차와 2주차의 차이점
indent depth 제한
들여쓰기를 2개까지만 허용합니다. 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2가 됩니다. 들여쓰기를 줄이기 위하여 내장함수를 활용하고 기능별로 함수를 쪼개야 합니다. 제가 1주차에 for문을 4번 연속(...) 사용했었는데 2주차에서는 상상도 할 수 없는 코드입니다 하하하.
함수를 최대한 쪼개기
솔직히 이 부분은 자신이 있습니다. 1주차에서도 기능별로 함수를 잘 쪼갰다고 생각했기 때문에 2주차에서도 늘 하던대로 하면 될 것 같습니다. 함수를 잘 쪼개기 위해서는 기능 구현 목록부터 잘 작성하면 되겠습니다.
Jest를 활용
낯설다 너란 Jest. 하지만 제시된 파일 중에 테스트 도구 사용 예시가 있기 때문에 이 부분을 열심히 익히면 직접 테스트 코드를 작성하는데 어렵진 않을 것입니다.
MissionUtils 라이브러리 활용
낯설다22...😂 이 부분에도 다행히 사용 예시가 주어졌기 때문에 기능에 맞게 잘 활용해주면 되겠습니다. 하지만 그게 생각처럼 쉬울까요?
https://github.com/woowacourse-projects/javascript-mission-utils#mission-utils
기능 구현 목록 작성 필수
1주차에서는 필수가 아니었습니다. 하지만 1주차에서부터 다른 참가자들도 작성하길래 그저 따라했을 뿐입니다..ㅎ 2주차는 필수니까 더욱 꼼꼼하게 목록을 작성할 필요가 있습니다.
제한 사항이 주어지지 않음
1주차에서는 문제마다 제한된 사항이 있었지만, 2주차에선 모든 예외 상황을 직접 생각해야 합니다. 1주차 끝나고 한 코수타(코치와 수다 타임)에서 사용자들은 우리가 생각하는 것보다 다양한 예외 상황을 맞닥뜨린다고 말씀해주셨습니다. 사용자 입장에서 야구 게임을 하다보면 잘 찾게 되지 않을까요?

아니 뭐가 이렇게 긍정적인거지? 다 잘할 수 있을 것 같대...
만발의 준비(?)
자바스크립트 내장함수 공부
다른 참가자들의 1주차 코드를 보고 가장 놀란 점은 내장함수를 기깔나게 사용하신 것입니다. for문과 if문 떡칠은 코드 길이만 늘리고 가독성은 훨씬 좋지 않습니다. 하지만 저는 map, reduce, filter와 같은 기본적인 함수도 다룰 줄 몰랐습니다. 자바스크립트 책을 처음부터 끝까지 공부할 시간은 없기 때문에 당장 사용할만한 내장함수만 정리하였습니다.
Array 함수(https://velog.io/@yeonsubaek/JavaScript-Array%EB%B0%B0%EC%97%B4-%EB%82%B4%EC%9E%A5%ED%95%A8%EC%88%98)
Jest 맛보기 그리고 포기 선언
테스트 구현을 처음해보는 것이기 때문에 __tests__/StringTest.js를 참고하여 공부를 해보았습니다.
describe("문자열 테스트", () => {
test("repeat 메서드로 문자열을 여러번 반복", () => {
const input = "abc";
const result = input.repeat(3);
expect(result).toEqual("abcabcabc");
});
test("repeat 메서드에 음수 값을 넣었을 때 예외 발생", () => {
const input = "abc";
const result = () => input.repeat(-1);
expect(result).toThrow(RangeError);
});
});
이걸 자바스크립트 문법적으로 접근을 하면 절대 감당할 수 없겠다고 느꼈습니다. 문법은 다 집어치우고 대충 어떻게 동작하는지만 알아보았습니다.
test("repeat 메서드로 문자열을 여러번 반복", () => { // 이런 테스트를 할거구나
const input = "abc";
const result = input.repeat(3); // input을 3번 반복할거구나
expect(result).toEqual("abcabcabc"); // 둘이 동일하면 테스트 통과구나
});
test("repeat 메서드에 음수 값을 넣었을 때 예외 발생", () => { // 이런 테스트를 할거구나
const input = "abc";
const result = () => input.repeat(-1); // -1번 반복..? 할거구나..?
expect(result).toThrow(RangeError); // 말도 안되는 소리. toThrow()해서 끝내면 테스트 통과.
});
링크(https://jestjs.io/docs/expect)에에) 들어가면 더 많은 Jest의 활용법을 볼 수 있습니다.
뭐야 Jest 별로 어렵지 않네? 그럼 바로 ApplicationTest.js를 뜯어보러 가볼까.

😨 mokQuestions, mocRandoms, getLogSpy ... ?
우선 침착하고 나무 대신 숲을 보기로 하였습니다.

js파일과 MissionUtils 라이브러리 모듈 불러오기
배열을 가져와 하나씩
MissionUtils.Console.readLine에 넣기 = 사용자 입력 값배열을 가져와 하나씩
MissionUtils.Random.pickNumberInRange에 넣기 = 컴퓨터의 랜덤 값출력값 반환
배열에 값을 지정하고 2-4번 함수 실행하고 js파일 실행하고 테스트 확인
이런 전개로 이해를 하였습니다. 이것을 이해하기까지 꼬박 이틀이 걸렸고 "내가 오늘도 성공 못하면 우테고 포기한다"라고 말하자마자 해냈습니다. 포기란 없어요😉
본격적인 시작
에라 모르겠다. 일단 메세지부터 띄워보자!
ApplicationTest.js은 모두 주석처리를 하고 우선 첫번째 기능인 게임 시작 메세지를 출력해보았습니다.
startGame() {
const START = "숫자 야구 게임을 시작합니다.";
console.log(START);
}

그리고 기능 목록이 정상 동작함을 확인하기 위하여 기능 하나를 완성할 때마다 테스트코드를 구현해보기로 하였습니다.
test("시작 메세지 출력", () => {
const logSpy = jest.spyOn(console, "log");
logSpy.mockClear();
const app = new App();
app.startGame();
expect(logSpy).toHaveBeenCalledWith("숫자 야구 게임을 시작합니다.");
});
🤭 테스트 성공 🤭
나 좀 잘하는 듯? 😎
간단한 기능 테스트 구현을 성공하고 저는 더욱 자신감을 가지고 기능 구현 목록을 해치워 나가기 시작했습니다. 고난과 역경을 헤매는 과정도 재밌으면 전 어떡하죠? (계속 개발하시면 됩니다.) 네, 감사합니다.
확실히 기능별로 테스트를 실행해보니 제가 짠 코드에 더욱 확신을 가지게 되었습니다.
또한, 자바스크립트 객체를 기가막히게 사용해보았습니다.
checkOverlap = (number) => {
const numberList = number.split("").sort(); // 1
const validNumber = [...new Set(numberList)]; // 2
if (validNumber.length < NUMBER.LENGTH) {
throw ERRORS.OVERLAP;
}
};
split()으로 문자열을 배열로 변경 &sort()로 오름차순 정렬[...new Set()]으로 중복된 요소 삭제
이전 같았으면 2번은 상상도 못하고 for문만 몇번 돌렸을텐데 말입니다. 이렇게 새로운 객체를 사용할 때마다 공부한 보람이 느껴집니다.
calculateScore = (computer, user) => {
let ball = 0;
let strike = 0;
const intersection = [...computer].filter((number) =>
[...user].includes(number)
);
intersection.forEach((number) => {
// 생략...
filter()를 사용해 컴퓨터 숫자와 사용자 숫자 중 공통된 숫자를 따로 저장해보았습니다. for문을 사용했다면 코드 길이가 아찔해질 뻔 했습니다.
code runner가 뭔데?
프리코스 슬랙을 둘러보다가 vscode의 code runner라는 익스텐션을 알게 되었습니다. 그동안 npm test를 통해 Jest만 돌려보았는데 code runner를 사용하면 Console.print와 Console.readLine을 출력하여 제가 만든 게임을 직접 실행해볼 수 있습니다.


익스텐션을 설치하고 Run In Terminal에 체크를 하면 준비 끝! (이 작업을 하지 않으면 키보드로 입력을 받을 수 없습니다.)Ctrl + Alt + N 혹은 우측 상단의 재생 버튼을 클릭하면 코드가 돌아갑니다.

이렇게 게임을 진행할 수 있습니다.
이런, 출력 방식이 잘못되었군.
code runner를 사용하고 제 출력 방식의 문제점을 발견하였습니다. Jest를 통해서는 Console.readLine에서 주어진 질문과 입력 값이 출력되지 않았습니다. 고로 저는 console.log(질문 + 입력값)과 같이 출력 메소드를 또 작성하였습니다.
하지만 code runner에서는 Console.readLine이 올바르게 출력되었기 때문에 제가 따로 주었던 출력 메소드는 더이상 필요 없게 되었습니다.
MissionUtils.Console.readLine("숫자를 입력해주세요 : ", (num) => {
// console.log(`숫자를 입력해주세요 : ${num}`);
this.isUserError(num, computer);
});
그리고 요구사항에서 주어진 라이브러리 활용법에서

Console.print를 활용하라고 떡하니 써져있었지만, 전 지금까지 console.log를 사용한 것이었습니다. 하지만 저도 이걸 쓴 나름의 이유를 갖고 있었습니다!
const messages = [ "낫싱", "3스트라이크", "1볼 1스트라이크", "3스트라이크", "게임 종료" ];
messages.forEach((output) => {
expect(logSpy).
(expect.stringContaining(output));
});
전 Console.print로 출력된 값이 output 값과 동일해야 테스트가 통과되는 줄 알았습니다. 따라서 저는 message를 출력하는 부분에만 Console.print를 사용하고 나머지 출력은 console.log를 사용한 것 입니다. 나중에서야 toHaveBeenCalledWith은 특정 매개변수가 잘 불러와졌는지 확인하는 역할을 한다는 점을 할게 되었습니다. (제가 의도한 대로 테스트코드를 작성하려면 toHaveBeenCalledWith 대신에 toEqual을 사용했어야)
함수의 늪에 빠져버렸다.
기능별로 함수를 나누다보니 함수가 총 16개가 되었습니다. 파일 하나에 함수 16개... 상식 밖을 벗어난 느낌이 들었습니다. 어떻게 하면 여러 함수를 보기 좋게 나눌 수 있을까 이것저것 떼보고 붙여보고 refactor 커밋만 20개를 넘긴 것 같습니다.
최종적으로 저는 함수 모듈화를 하게 되었습니다. 사실 모듈화는 Sass로 css 작업을 했을 때 사용했던 방식인데 자바스크립트에서도 사용하게 될 줄은 몰랐습니다.
모듈화 방법은 생각보다 간단한데요, 우선 간단하게 메세지와 고정된 값들의 constants 작업부터 해보았습니다.
constants.js
const NUMBER_LENGTH = 3;
const SCORES = {
BALL: "볼",
STRIKE: "스트라이크",
NOTHING: "낫싱",
};
// 중략...
module.exports = {
NUMBER_LENGTH,
SCORES,
};
상수로 값을 선언해주고 하단에 module.exports로 내보내줍니다.
APP.js
const { NUMBER_LENGTH, SCORES } = require("./constants");
class App {
play() {
// 중략...
}
}
module.exports = App;
require를 사용해 constants.js를 가져오고 상수명으로 사용해 잘 활용해주면 됩니다. 참 쉽죠잉~😉
이런 식으로 리펙토링을 하다보니

이게 맞는건가? 2주차 끝나고 다른 참가자들의 코드를 살펴보면서 더 올바른 모듈화 방식을 연구해봐야겠습니다.
최종적으로 App.js 속 코드는
const { startGame } = require("./modules/startGame");
const { playGame } = require("./modules/playGame");
class App {
play() {
startGame();
playGame();
}
}
module.exports = App;
11줄 만에 끝이 났습니다. 👏👏👏👏👏👏👏
끝난 줄 알았지?

분명 code runner랑 테스트에서도 성공적이었는데 예제 테스트에서 빌드를 실패하였습니다. 이건 제 시나리오에 없었던 결과라 당황스러웠습니다. 우선 자고 마감일에 오류를 해결해야지 생각했지만 전 오류를 두고 잠을 잘 수 없는 병에 걸렸습니다. 10분만... 10분만... 붙잡다가 1시간 30분이 흘렀습니다.
여러가지 고민을 해보았는데,
테스트코드를 파일 하나로 합치면 어떨까? 저는 기존에 있는
applicationTest.js와 제가 작성한functionsTest.js가 충돌하여 오류가 발생한 것이 아닐까 생각을 해보았습니다. 두 파일에는 동일한 이름의 변수가 선언되었기 때문입니다. 하지만 이 방법으로는 해결할 수 없었습니다.옵션을 잘못 선택했을 때 게임이 종료되는 과정에서 잘못된걸까? 저는 위의 상황에서 다음과 같이 코드를 작성했습니다.
if (option === END) {
Console.print(MESSAGES.END);
return;
}
throw new Error(ERRORS.OPTION);
조건문에 return을 주긴 했지만 이것을 무시하고 throw가 된 것은 아닌지 생각보았습니다.
if (option === END) {
Console.print(MESSAGES.END);
}
if (option !== RESTART && option !== END) {
throw new Error(ERRORS.OPTION);
}
위와 같이 코드를 변경하고 다시 결과를 확인했지만 아직도 성공을 하지 못하였습니다.
- 게임이 제대로 종료된거 맞아? 저는 테스트와
code runner를 다시 차근차근 실행해보았습니다. 사용자가 최종으로 게임을 종료하였을 때, 게임 종료 메세지만 뜨고 완전히 종료되지 않은 모습을 발견하였습니다. 지금까지는 아무 생각 없이ctrl + C를 하여 터미널을 강제종료 하였는데 예제 테스트 실행에선 강제종료를 할 수 없나 봅니다.
if (option === END) {
Console.print(MESSAGES.END);
Console.close();
}
따라서 위와 같이 Console.close()를 사용하여 완전히 종료를 시켜주었습니다.

처음엔 이 미션을 제대로 제출할 수 있을지 걱정을 많이 했지만 저는 결국 이렇게 해냈습니다.
1주차에서는 그동안 제가 백준 문제를 풀었던 것과 유사해 미션 과정이 그리 어렵지는 않았습니다.
하지만 2주차에서는 그동안 경험해보지 못한 테스트구현과 리펙토링(모듈화)를 해보면서 또 다시 새로운 Hello World에 입문한 기분이었습니다.
3주차에서는 2주차보다 더욱 깔끔하고 바람직한 테스트구현과 모듈화를 실행해보고 싶습니다.

![[JavaScript] 이미지 사이즈 압축해서 용량 줄이기](https://cdn.hashnode.com/res/hashnode/image/upload/v1731718582763/ed9e5687-ee04-44d6-ae39-8e5a809bdf73.png)
![[JavaScript] 이미지 수정 시 브라우저 캐싱 방지하기](https://cdn.hashnode.com/res/hashnode/image/upload/v1727921664973/6e3f32b0-2df4-44f9-bc12-69589dffbcb7.png)
![[React] 렌더링 관점에서 보는 react-hook-form (feat. useRef)](https://cdn.hashnode.com/res/hashnode/image/upload/v1725535323097/421ac5cd-a7af-401a-80f3-24b15fcd6087.png)