아래와 같이 1주 차, 2주 차 회고에 이어 3주 차 회고를 작성하려고 한다.
[우테코] 2주 차 미션 풀이(+코드 리뷰), 회고
[우테코] 1주 차 미션 풀이(+코드 리뷰), 회고
일단, 이번 주 미션은 미완성인 채로 제출했다.
미완성으로 제출한 원인은 코드를 작성할 시간이 부족했기 때문인데, 그 이유로 두 가지를 들 수 있다.
모르는 개념을 정리하는데 너무 많은 시간을 썼다. 아래의 미션 수행 기록을 보면 3일에 걸쳐 모르는 개념을 정리한 것을 알 수 있다. 10일에는 문제 정리, 2주 차 회고, 3주 차에 공부해야 할 것 등을 정리했고 11일, 12일에는 모르는 개념을 공부하고 매일 2개의 블로그 포스팅을 했다. 결론적으로 미션을 수행하는 데 주어진 일주일의 반을 개념 정리에 쓴 것이다.
class에 대한 지식이 부재했다. 저번 주와 마찬가지로 이번 문제에서도 class가 기본적으로 주어졌다. 2주 차와 다른 것이 있다면 class 안에 class를 써야 한다는 것이었다. 분명히 포스팅에 정리도 하며 이해했다고 생각했지만, 아니었다... 특히 constructor 개념을 제대로 이해하지 못해 class를 나눠 코드를 작성하는 데 시간이 많이 걸렸다.
목차는 아래와 같다. 피어 리뷰에 대한 내용은 없는데, 이번 주는 미완성된 코드를 제출했기 때문에 받지 않았다.
- 문제 분석 및 정리
- 로또 미션 풀이 과정
- 3주 차 회고
매주의 미션은 메일을 통해 전달받는데 이 메일을 꼼꼼하게 읽는 것이 미션을 해결하는 첫 번째 단계라고 생각해서 3주 차 미션으로 받은 메일을 정리해 보았다.
지난 2주 차 미션의 목표가 함수 분리
와 함수별로 테스트를 작성
이었다면, 3주 차 미션에서는 2주 차에서 학습한 것에 2가지 목표가 추가된다. 따라서 이번 주 목표는 아래와 같다.
'도메인 로직과 단위 테스트와 같은 용어들이 낯설 수 있지만, 작은 기능부터 테스트를 작성하는 연습을 시작해 보는 것입니다. 위에서 언급한 숫자 야구 피드백 강의 후반부에 단위 테스트를 작성하는 내용이 있으니 이를 참고해 주세요.'
나는.. 메일을 제대로 읽지 않아서 지금까지 과제 제출시에 내는 소감문에 학습한 과정을 잘 드러내지 않았다. 블로그에는 그렇게 자세히 정리해놨으면서 왜..!! 피눈물 흘리는 중💧🔥 하지만 이제부터 잘하면 되지!!
정말 메일을 꼼꼼하게 읽고 요구사항을 정리해야겠다고 생각했다.
2주 차 미션 저장소(깃허브)를 보고 문제 요구사항 등을 정리해보았다.
- 로또 번호의 숫자 범위는 1~45까지이다.
- 1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑는다.
- 당첨 번호 추첨 시 중복되지 않는 숫자 6개와 보너스 번호 1개를 뽑는다.
- 당첨은 1등부터 5등까지 있다. 당첨 기준과 금액은 아래와 같다.
- 1등: 6개 번호 일치 / 2,000,000,000원
- 2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원
- 3등: 5개 번호 일치 / 1,500,000원
- 4등: 4개 번호 일치 / 50,000원
- 5등: 3개 번호 일치 / 5,000원
8개를 구매했습니다.
[8, 21, 23, 41, 42, 43]
[3, 5, 11, 16, 32, 38]
[7, 11, 16, 35, 36, 44]
[1, 8, 11, 31, 41, 42]
[13, 14, 16, 38, 42, 45]
[7, 11, 30, 40, 42, 43]
[2, 13, 22, 32, 38, 45]
[1, 3, 5, 14, 22, 45]
3개 일치 (5,000원) - 1개
4개 일치 (50,000원) - 0개
5개 일치 (1,500,000원) - 0개
5개 일치, 보너스 볼 일치 (30,000,000원) - 0개
6개 일치 (2,000,000,000원) - 0개
구입금액을 입력해 주세요.
8000
8개를 구매했습니다.
[8, 21, 23, 41, 42, 43]
[3, 5, 11, 16, 32, 38]
[7, 11, 16, 35, 36, 44]
[1, 8, 11, 31, 41, 42]
[13, 14, 16, 38, 42, 45]
[7, 11, 30, 40, 42, 43]
[2, 13, 22, 32, 38, 45]
[1, 3, 5, 14, 22, 45]
당첨 번호를 입력해 주세요.
1,2,3,4,5,6
보너스 번호를 입력해 주세요.
7
당첨 통계
---
3개 일치 (5,000원) - 1개
4개 일치 (50,000원) - 0개
5개 일치 (1,500,000원) - 0개
5개 일치, 보너스 볼 일치 (30,000,000원) - 0개
6개 일치 (2,000,000,000원) - 0개
총 수익률은 62.5%입니다.
const app = new App();
app.play();
package.json을 변경할 수 없고 외부 라이브러리(jQuery, Lodash 등)를 사용하지 않는다. 순수 Vanilla JS로만 구현한다.
- .gitignore
에 package.json을 추가하여 커밋되지 않게 하면 될 것 같다.
JavaScript 코드 컨벤션을 지키면서 프로그래밍 한다.
프로그램 종료 시 process.exit()를 호출하지 않는다.
프로그램 구현이 완료되면 ApplicationTest의 모든 테스트가 성공해야 한다. 테스트가 실패할 경우 0점 처리한다.
프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 이름을 수정하거나 이동하지 않는다.
indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다.
- 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
- 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다.
함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라.
Jest를 이용하여 본인이 정리한 기능 목록이 정상 동작함을 테스트 코드로 확인한다.
MissionUtils 라이브러리에서 제공하는 Random 및 Console API를 사용하여 구현해야 한다.
- 사용자의 값을 입력 받고 출력하기 위해서는 MissionUtils 라이브러리에서 제공하는 Console.readLine, Console.print를 활용한다.
- Random 값 추출은 MissionUtils 라이브러리의 Random.pickUniqueNumbersInRange()를 활용한다.
- 예) const numbers = MissionUtils.Random.pickUniqueNumbersInRange(1, 45, 6);
Lotto 클래스
- 제공된 Lotto 클래스를 활용해 구현해야 한다.
- numbers의 # prefix를 변경할 수 없다.
- Lotto에 필드를 추가할 수 없다.
class Lotto {
#numbers;
constructor(numbers) {
this.validate(numbers);
this.#numbers = numbers;
}
validate(numbers) {
if (numbers.length !== 6) {
throw new Error();
}
}
// TODO: 추가 기능 구현
}
# 기능 목록
### 클래스 나누기 ⭕
- App.js, Lotto.js의 클래스를 나누기 ⭕
- Lotto.js는 하나의 로또의 통계를 계산하는 기능 ⭕
- App.js는 Lotto.js의 기능을 제외한 모든 기능 ⭕
### 로또 구입금액 입력받기 ⭕
- 입력을 요구하는 문자열 출력하기 ⭕
- 1,000원 단위의 숫자로 입력받기 ⭕
- (예외) 1,000원으로 나누어 떨어지지 않는 경우 예외 처리 ⭕
- (예외) 사용자가 잘못된 값을 입력할 경우 throw문을 사용해 예외를 발생시키고,
"[ERROR]"로 시작하는 에러 메시지를 출력 후 종료 ⭕
### 로또 발행하기 ⭕
- 구입 금액에 해당하는 만큼 로또를 발행한다(로또 1장의 가격은 1,000원) ⭕
### 당첨 번호 입력받기 ⭕
- 쉼표로 구분된 번호 당첨 번호 6개 입력받기 ⭕
- 6개의 숫자는 중복되지 않는다 ⭕
- 번호의 숫자 범위는 1~45 ⭕
### 보너스 번호 입력받기 ⭕
- 하나의 숫자를 입력받는다 ⭕
- 당첨 번호와 중복되지 않는다 ⭕
- 번호의 숫자 범위는 1~45 ⭕ (여기까지 완료함)
### 1등~5등 당첨 여부 계산하기
- 1등: 6개 번호 일치 / 2,000,000,000원
- 2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원
- 3등: 5개 번호 일치 / 1,500,000원
- 4등: 4개 번호 일치 / 50,000원
- 5등: 3개 번호 일치 / 5,000원
### 출력
- 로또 수량 출력
- 로또 번호 출력
- 오름차순 정렬
- 당첨 내역 출력
- 3개 일치 시에 몇 개가 3개 일치했는지
- 4개 일치 시에 몇 개가 4개 일치했는지
- 5개 일치 시에 몇 개가 5개 일치했는지
- 6개 일치 시에 몇 개가 6개 일치했는지
- 수익률 출력
- 소수점 둘째 자리에서 반올림
- (예외) 예외상황 시 에러 문구 출력("[ERROR]"로 시작)
2주 차 피어리뷰를 하고 몰랐던 것이 무엇인지 알게되었다. 그래서 3주 차 미션에 들어가기 전에 모르는 것을 공부해야겠다고 생각했고, 아래와 같이 정리하게 되었다.
결론적으로, 공부해야겠다고 생각한 것은 거의 모두 공부하게 되었다! 심지어 공부한 후, 블로그에 포스팅까지 했다. 그래서,, 코드를 작성할 시간이 매우 부족해졌다는 것만 빼고는 잘 한 것 같다.
- 자바스크립트 컨벤션 쭉 읽기 ⭕ 👉 이곳에자바스크립트 컨벤션 정리함(11.11)
- class 공부 ⭕ 👉 이곳에 class 정리함(11.12)
- 테스트 공부 ⭕ 👉 이곳에 jest test 정리함(11.13)
- 클린 코드 공부
- 내장 함수 공부 ⭕ 👉 이곳에 내장함수 정리함(11.12)
- 최소한 한 번씩이라도 직접 코딩해면서 공부하기
- forEach, reduce 필수
- 2주 차 공통 피드백 자세히 읽기 ⭕ (11.11)
클래스 App과 클래스 Lotto는 문제에서 꼭 쓰라고 요구한 사항이었다.
이것부터 정말 ... 어려웠다!!!! 실행이 안돼서.. 진짜 실행을 하는데 아무것도 안 나와서 한나절 동안 고민했는데 결국 어떻게 실행을 하게 됐다. 이유는 기억이 안나서 적지 못하는게 좀 아쉽다.
class App {
lotto = new Lotto();
play() {
this.lotto.getParchaseAmount(GAME_MESSAGE.parchaseAmount);
}
}
let app = new App();
app.play();
/Constants.js에 정기적으로 출력되는 문구를 변수로 저장해두기로 했다.
const GAME_MESSAGE = {
parchaseAmount: '구입금액을 입력해 주세요.\n',
guessNumbers: '당첨 번호를 입력해 주세요.',
bonusNumbers: '보너스 번호를 입력해 주세요.',
statics: '당첨 통계\n---',
};
module.exports = { GAME_MESSAGE };
이런식으로!
아니 실행을 하는데 자꾸 GAME_MESSAGE
가 없다고 뜨는 것이다. 그래서 진짜 몇시간 동안 버그를 찾았는데, module.exports = { GAME_MESSAGE };
를 안해서 그런 거였음..^_^ 눈물이 찔끔 났다..
처음에 사용자로부터 구입 금액을 입력받는 함수를 만들었다. 그리고 validateMoney 함수를 이용해 유효한 값인지 인증했다.
getParchaseAmount(message) {
Console.readLine(message, (inputMoney) => {
validateMoney(inputMoney);
this.printNumberOfLottos(inputMoney);
})
}
getParchaseAmount 함수 안에 넣을 validatateMoney 함수를 만들었다.
입력받은 금액이 유효한 금액인지 확인하는 함수이며 아래의 조건으로 확인하고자 했다.
1) 입력받은 금액이 숫자로만 이루어져 있는지
2) 입력받은 금액이 1000의 배수인지(로또 하나를 살 수 있는 금액이 1,000원 이므로)
1), 2)번을 거르기 위해 정규표현식을 사용했다. 정규표현식으로 배수를 걸러내는 특별한 방법은 없지만, 1,000의 배수는 마지막 숫자가 000이니까 정규표현식으로 만들 수 있었다. 정규표현식으로 간략한 코드를 짠 것 같아 뿌듯했다!!!
function validateMoney(inputMoney) {
if (!(/^[1-9][0-9]*[0]{3}$/.test(inputMoney))) {
throw new Error('[ERROR] 입력값이 잘못되었습니다.')
}
}
사용자가 입력한 금액에서 1000을 나눠(로또 하나의 금액이 1,000원임) 몇 개를 구매할 수 있는지 출력하는 함수를 만들었다.
printNumberOfLottos(inputMoney) {
const numberOfLotto = Math.floor(inputMoney / 1000);
Console.print('\n' + numberOfLotto + '개를 구매했습니다.');
this.printLottos(numberOfLotto);
}
구입한 로또의 개수만큼의 로또 번호를 출력하는 함수인 printLottos를 만들었다.
문제의 요구사항인 Random.pickUniqueNumbersInRange
를 써서 1과 45 사이의 겹치지 않는 수 6개를 뽑아 배열에 담아 출력했다.
printLottos(numberOfLotto) {
const lottoArrays = [];
for (let i = 0; i < numberOfLotto; i++) {
this.#numbers = Random.pickUniqueNumbersInRange(1, 45, 6);
this.validate(this.#numbers);
lottoArrays.push(this.#numbers);
}
lottoArrays.forEach((numbers) => {
return Console.print(numbers);
}
)
}
여기서 numbers를 this.#numbers
가 아니라 계속 numbers
혹은 #numbers
라고 써놓고 #numbers를 왜 인식을 못하냐며 계속 삽질을 했었다.
그리고 이건 지금 삽질하고 있는 건데, 자꾸 TypeError: Cannot read property 'length' of undefined
이런 오류가 난다. 이건.. 어제부터 났던 오류로 유구한 역사를 가지고 있다. 뭘 어떻게 고쳐도 계속 이런 오류가 난다. 아래에서 자세히 알아보자.
TypeError: Cannot read property 'length' of undefined
- class constructor에 대해서 몰라서 생긴 문제결국 질문했다. 이틀이나 고민했는데 안풀렸으므로, 질문을 해서 알아가는게 낫다고 판단했다.
👉 내가...........모르고 있는게 정말 많았구나....................................
일단 2주 차 때도 constructor(생성자)가 뭔지 진짜 나는 아무것도 모르는데 그냥 써있으니까 썼다는 걸 알게되었다. 2주 차 때는 constructor에 대해 몰라도 됐는데, 이번에는 class Lotto에서 constructor를 쓰므로 이는 꼭 알아야 하는 개념이었다.
누가 예시를 들어서 설명해주셨다. 일단 class와 constructor는 아래의 코드와 같이 쓰인다고 한다. 행성(Planet)은 이름이 지구, 화성일 수 있고 사람(Person)은 사람마다 성별, 이름, 키 등이 다들 수 있다. 이를 모두 하나의 틀에서 커스텀해서 사용할 수 있게 하는 것이 class이다.
그리고 constructor는 new가 선언되면 만들어진다.
class Person {
constructor(gender, name, height) {
this.gender = gender;
this.name = name;
this.height = height;
}
hello() {
console.log("hi, my name is", this.name);
}
sayHiTo(anotherPerson) {
console.log("hi", anotherPerson.name, "my name is", this.name);
}
}
class Planet {
constructor(name) {
this.name = name;
this.people = [];
}
add(person) {
this.people.push(person);
}
print() {
console.log("People in", this.name);
for (let i = 0; i < this.people.length; i++) {
const person = this.people[i];
person.hello();
}
}
}
const earth = new Planet("Earth");
const mars = new Planet("Mars");
const hamham = new Person("female", "hamham", "165");
const minha = new Person("male", "minha", "175");
earth.add(hamham);
earth.add(minha);
mars.add(minha);
earth.print();
mars.print();
// People in Earth
// hi, my name is hamham
// hi, my name is minha
// People in Mars
// hi, my name is minha
다.. 바꿔야 할 것 같다. class Lotto의 constructor에 numbers 인수가 들어가므로, class App에서 이미 numbers를 받아와야 하는 것이 맞다.
그래서 Lotto에 만든 함수들을 다 빼고 App에 넣고, Lotto는 로또 하나의 통계를 계산하도록 해야겠다고 생각했다.
일단 문제의 요구사항대로 로또번호가 정렬되도록 하기 위해 sort를 사용했고, 기존 배열을 바꾸기 위해 forEach 대신 map으로 변경하였다. (어제 forEach와 map에 대해서 공부했는데, 배운 다음 날인 오늘 이곳에 forEach가 아니라 map을 써야한다는 걸 단번에 알아서 매우 행복했다😊)
printLottos(numberOfLotto) {
const lottoArrays = [];
for (let i = 0; i < numberOfLotto; i++) {
const lottoArray = Random.pickUniqueNumbersInRange(1, 45, 6);
lottoArrays.push(lottoArray);
}
lottoArrays.map((lottoArray) => {
lottoArray.sort((a, b) => (a - b));
Console.print(lottoArray);
}
)
this.getGuessLotto();
}
사용자에게 로또 번호를 입력받는 함수 getGuessLotto를 만들었다.
여기서 class Lotto를 호출하여 Lotto의 validateLotto 함수를 실행할 것이다.
getGuessLotto() {
Console.readLine(GAME_MESSAGE.guessNumbers, (guessLotto) => {
guessLotto = guessLotto.split(',').map(string => Number(string))
let lotto = new Lotto(guessLotto);
lotto.validateLotto();
this.lottos.push(lotto);
})
}
그리고 Lotto.js에서 아래와 같이 당첨 번호를 받고 6자리인지 검사하는 것까지 성공했다!
validate(numbers) {
if (numbers.length !== 6) {
throw new Error("[ERROR] 로또 번호는 6개여야 합니다.");
}
}
// TODO: 추가 기능 구현
validateLotto() {
this.validate(this.#numbers);
// Console.print('당첨 번호를 받는 것까지 성공했습니다.');
}
잘못 생각해서, class를 다시 나눠야 한다. 현재까지 '사용자가 로또 번호를 입력할 때' -> 'class Lotto를 호출'했었는데, 이게 아니었다.
그게 아니라, 컴퓨터가 만든 로또 번호 한 줄에 대해 class Lotto를 호출해야 하는 거였음...!!!!! 다시 나눠보자.
사용자로부터 로또 번호를 입력받는 함수 getGuessLotto를 만들었다.
getGuessLotto() {
Console.readLine(GAME_MESSAGE.guessNumbers, (guessLotto) => {
guessLotto = guessLotto.split(',').map(string => {
return Number(string);
})
this.getGuessBonusLotto(guessLotto);
})
;
}
console로 입력받은 guessLotto를 출력해보니, [1, 2, 3, 4, 5, 6]
이 아니라 [ NaN, 2, 3, 4, 5, NaN ]
이 뜨는 것이다.
그래서 디버깅을 해봤다. 아래와 같이 콘솔을 두개 찍어봤는데, 2번 콘솔에서 무엇이 잘못되었는지 알 수 있었다.
getGuessLotto() {
Console.readLine(GAME_MESSAGE.guessNumbers, (guessLotto) => {
// 1. console.log(guessLotto);
guessLotto = guessLotto.split(',').map(string => {
// 2. console.log(string);
return Number(string);
})
this.getGuessBonusLotto(guessLotto);
})
;
}
2번 콘솔을 출력하면 아래와 같이 뜬다. 그러니까, 맨 처음과 끝이 "1", "6"이 아닌 "'1", "'6"이 되어서 NaN이 되는 오류가 난 것이다.
'1
2
3
4
5
6'
나는 입력값으로 '1, 2, 3, 4, 5, 6'
이라고 입력했는데, 사실 1, 2, 3, 4, 5, 6
으로 입력해야 하는 것이었다.
주어진 테스트코드에서 아래와 같이 1, 2, 3, 4, 5, 6
이 따옴표 안에 있길래 그대로 출력하는 줄 알았는데 그냥 input으로 입력을 하면 문자열로 취급이 되기 때문에 자동적으로 쌍따옴표가 생긴 것이었다.. 몰랐음
// ApplicationTest.js
mockQuestions(["8000", "1,2,3,4,5,6", "7"]);
그래서 결론적으로 '1, 2, 3, 4, 5, 6'
이 아니라 1, 2, 3, 4, 5, 6
을 입력해서 해결했다.
로또 통계를 구하는 부분을 구현하지 못하고, 시간 부족으로 이대로 제출했다. 예제 테스트 결과는 2개 중 1개 통과였다.
3주 차는 일단 미완성인 채로 낸 것이 가장 아쉽다. 초반에 개념 정리에 시간을 덜 썼으면 혹시 다 풀 수 있지 않았을까, 하는 생각도 든다.
그래도 자바스크립트 컨벤션, jest로 테스트하기, class, map&forEach에 대해 공부해볼 수 있었던 좋은 기회였다.
그리고, 앞으로 남은 4주 차 미션 회고는 없을 예정이다. 건강상의 이유로 3주 차를 마지막으로 프리코스를 중단하기로 했다. 이에 관해서는 다른 포스팅에 따로 정리해볼 예정이다.
3주 동안 최선을 다한 나에게 잘했다고 칭찬해주고 싶다! 그리고 그동안 짧게나마 이야기를 나눠본 스터디 팀원들과 3,300여 명의 지원자들, 그리고 코치님들께 감사의 인사를 전하고 싶다🥰
안녕하세요..! 만약에 미제출로하면 다음 4주차 프리코스는 참가 못하나요?? 커뮤니티도요!!!