📌 3주 차 미션: 로또
5기 프리코스 후기에서도 어렵다고 들었던 로또가 이번 주의 미션이었습니다.
구현해야 하는 기능 자체의 난이도도 올라갔지만
과제 요구사항에 맞추어 코드를 작성하는 것이 쉽지 않았습니다.🧐
✅ 3주 차 학습 목표
- 클래스(객체)를 분리하는 연습.
- 도메인 로직에 대한 단위 테스트를 작성하는 연습
✅ 추가된 프로그래밍 요구사항
1. 함수의 길이가 15라인을 넘어가지 않도록 구현한다.
- 함수가 한 가지 일만 잘 하도록 구현한다.
2. else를 지양한다.
- 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다.
- if/else, switch문을 사용하는것이 더 깔끔할 때가 있다. 이 부분은 스스로 고민해 본다.
3. 도메인 로직에 단위 테스트를 구현해야 한다.
- 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 구분한다.
⚽️ 트러블슈팅
1. 출력은 잘 되는데 테스트가 통과가 안 되는 상황
● 로또 테스트 › 기능 테스트
expect(jest.fn()).toHaveBeenCalledWith(...expected)
Expected: StringContaining "[8, 21, 23, 41, 42, 43]"
Received
1: "8개를 구매했습니다."
2: [8, 21, 23, 41, 42, 43]
3: [3, 5, 11, 16, 32, 38]
Number of calls: 17
88 |
89 | logs.forEach((log) => {
> 90 | expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(log));
| ^
91 | });
92 | });
93 |
배열을 받아 출력하는데 타입이 배열이 아닌 배열 형식 그대로 문자열을 출력해야 했었습니다.
printMessage([${winningNumber.join(', ')}]);
배열의 요소를 손수 join으로 띄어쓰기와 콤마를 붙여주었는데 이게 맞나 의심스러웠지만
다른 분들의 코드를 보니 다들 이렇게 하셔서 다행이었습니다......😮💨
아쉬운 점
const formatLotto = (lotto) => [${lotto.join(', ')}];
코드리뷰를 보다가
출력형식을 지정하는 부분이라 상수로 관리하는 것이 좋겠다고 하시며
위 코드와 같이 리팩토링하는것을 제안해 주셨습니다.
상수화는 어디까지 하면 좋을지
자칫 하면 생산성을 더 떨어트리는 행위가 아닐지 생각해 보았는데,
늘 '어플리케이션이 커지고 복잡해진다면'이라는 조건하에 코드리뷰가 오가고
유지보수를 위한 미래적 관점에서는
작은 부분이라도 하드코딩보다는 상수화하는 것이 더 좋은 방법이라는 생각이 들었습니다.
3주 차 피드백에도 값을 하드코딩 하지 말라는 피드백이 있는 것을 보니 상수화의 중요성을 더 인지하게 되었습니다.
상수화도 중요하지만 무엇보다 변수이름을 짓는 것이 가장 중요한 작업 같습니다.
너무 어렵거든요!! chatGPT가 아니었다면 영어사전에서 가장 시간을 많이 보냈을 것 같습니다.
2. 수익률 반올림하기
수익률은 소수점 둘째 자리에서 반올림해야 합니다.
12.39% -> 12.4%
12.577% -> 12.6%
소수점 반올림하는 매서드 toFixed를 사용하면 되지만
모든 숫자에 적용시킨다면 소수점이 없는 경우
12% -> 12.0%으로 출력이 됩니다.
그래서 조건문을 걸어주어 소수점이 2 이상일 경우에만 반올림을 해주었습니다.
calcualteProfit(purchaseAmount, totalPrize) {
let profit = (totalPrize / purchaseAmount) * 100;
if (profit % 0.1) {
profit = profit.toFixed(1);
}
printMessage(`${MESSAGES.profitRate}${profit}%입니다.`);
}
아쉬운 점
1. 사실 실제 코드에서는 2번째 소수점까지 반올림하였는데
요구사항을 제대로 보지 않은 제 탓입니다 🥲
2. 예시 출력문에서 3 단위마다 콤마를 붙이는데 미처 발견하지 못했습니다.
3. 코드에서 쓰인 숫자들도 하드코딩 하지 않고 상수화로 관리하여 나중에 조건이 변경되더라도 쉽게 관리할 수 있게 만들고 싶습니다.
3-1. 같은 맥락으로 printMessage 함수도 수익률만 받아서 '%입니다'를 붙여서 출력했다면 더 깔끔하고 좋은 코드가 되었을 것 같네요.
💡 배운 점
에러를 던질 때 new 연산자를 써야 할까?
mdn에 따르면 new 생성자를 꼭 쓰지 않아도 Error 객체를 반환한다고 합니다.
MVC 패턴을 적용해 보다.
처음 코드를 작성했을 때 로또 게임 시작 메서드인app.play()가 하는 일이 많았습니다.
로또 번호를 받고, 유효성 검증을 하고, 수익률을 계산하는 등
게임의 전체적인 흐름을 모두 app.play()메소드가 담당하고 있었습니다.
이전 미션에서는 간단한 로직이라 utils나 function 정도로 나눌 수 있었지만
게임이 복잡해짐에 따라 책임을 나누어 관리할 필요를 느꼈습니다.
여러 가지 디자인 패턴이 있지만
그중 제게 이름도 익숙하고 많은 분들이 사용 중인 MVC 패턴을 적용해 보았습니다.
MVC 패턴은 어플리케이션을 세 가지 역할로 구분한 개발 방법론입니다.
사용자가 Controller를 조작하면
Controller는 Model을 통해서 데이터를 가져오고
가져온 데이터를 바탕으로 View를 통해 사용자에게 보여주게 됩니다.
저는 전체적인 흐름은 모두 Controller에서 실행하였습니다.
로또 번호를 받는 메소드를 실행시키고
그 값을 가지고 결괏값을 계산하는 함수를 실행시켰습니다.
여기서 로또 번호 입력을 요청하고, 받는 부분은 view에서
입력받은 번호에 대해 유효성 검사를 진행하고
여러 가지 데이터를 가지고 계산을 하는 메소드들은 Model에 정의하였습니다.
MVC 패턴을 적용하고 난 후
app.play() 메서드에서는 약 20줄의 코드를 가진 여러 가지 역할에서
로또 게임을 시작하는 메소드만 실행시키는 역할만 갖게 되었습니다.
class App {
async play() {
const lottoGame = new LottoGameController();
await lottoGame.start();
}
}
또한 각 함수들의 역할을 나누면서 자연스럽게
학습목표였던 메서드를 분리하는 연습을 할 수 있었습니다.
Model은 데이터를 편집하고 제공하는 역할을 하기 때문에
로또 게임의 각 단계별 사용되는 메서드들을 분리해주었습니다
util함수는 프로그램 전체에서 사용할 수 있어야 한다.
프리코스 디스코드에서 클래스 내에서 만드는 게 좋은지
utils로직으로 처리하는 것이 좋은지에 대한 토론이 올라왔습니다.
저는 이전 과제까지는 모두 util함수로 모든 유효성 검사를 진행했는데요
댓글을 읽고 나서 제가 util함수를 잘못 이해하고 있었음을 깨달았습니다.
utils 폴더에 있는 함수는 어떤 함수, 기능이 든 간에 다 사용할 수 있는
만능 폴더라 가능한 지양하는 것이 좋다고 합니다.
객체지향적인 관점에서 간단한 검증은 기본 클래스에서 하고
검증해야 할 것이 아주 많아질 때만 검증 클래스를 따로 작성하는 것을 제안해 주셨습니다.
클래스 생성자 안에 검증로직을 넣으면 아래와 같은 장점이 있습니다.
1. 코드의 응집성이 높아집니다.
유효성 조건이 변경될 때를 생각하면 다른 클래스를 수정해야 할 필요가 없습니다.
2. 불필요한 코드를 줄일 수 있습니다.
객체가 생성되는 모든 곳에서 검증로직을 일일이 수행하지 않아도 됩니다.
3. 상태와 로직을 한 군데로 모아 이후에 코드를 탐색할 때 편합니다.
테스트코드를 작성해야 하는 이유는?
프로그래밍 요구사항에 도메인 로직에 대한 단위테스트 구현이 있었습니다.
로또 번호 유효성 검사를 진행하고 결과를 계산하는 로직들을 테스트 코드로 작성했을 때
다음과 같은 테스트 코드를 작성하는 이유에 대해서 생각해 보았습니다.
1. 무엇을 만들고 있는지 인지하게 됩니다.
우테코 프리코스 과제에서는
요구사항을 분석해서 기능 목록을 만들어 구현을 하게 됩니다.
테스트 코드는 구현해야 할 기능들을 정리할 수 있는 기능목록 작성과 같습니다.
예외사항을 고려하고 예제를 작성하면서 예상외의 코너 케이스를 찾을 수 있습니다.
2. 리팩토링이 수월해집니다.
저는 이번 미션에서 한 메소드에 여러 가지 로직들을 작성한 뒤
도메인별로 나누는 리팩토링을 진행했습니다.
이때 코드들을 대거 수정하면서
혹시나 기능 구현이 제대로 되지 않을까 걱정했는데
테스트 코드가 존재함으로써
빠른 피드백을 받을 수 있었습니다.
🎙️소감
지난 2주 차 소감에서 클래스를 적극적으로 사용해 보기가 있었는데
다행히(?) 이번 주 학습 목표에 클래스 메서드를 분리하는 연습이 있었습니다.
클래스로 코드를 작성하는 것이 익숙하지 않아 막연히 어렵다고 생각했는데
이 글을 읽고 클래스와 아주 조금 친해지고 나니
클래스는 각 문제를 해결하는 작은 프로그램과 같이 느껴졌습니다.
가끔 라이브러리나 다른 사람들의 코드를 뜯어볼 때 class가 나오면 외면하곤 했는데
이제는 무슨 의도로 코드를 작성했는지 조금이나마 이해를 할 것 같습니다.
단위 테스트로 진행하다 보니 mock 함수를 사용하지 못했지만
한층 더 복잡해진 게임의 기능 덕분에
다양한 matcher함수를 사용해 볼 수 있는 미션이었습니다.
늘 생각하지만 시간이 더 있었더라면 더 좋은 코드를 작성했을 텐데 하는 아쉬움이 많이 남습니다
프리코스가 끝나면 모든 코드들을 돌아볼 시간을 가져야겠습니다 :)
📚Ref
https://yozm.wishket.com/magazine/detail/1964/
테스트 코드는 왜 만들까? | 요즘IT
지금 돌이켜 생각하면 부끄러운 일이지만, 처음 테스트 코드를 마주했을 때 든 생각은 '왜 귀찮은 테스트 코드를 만들어야 하는 걸까?'였습니다. 물론 지금은 테스트 코드의 중요성을 깨달아 열
yozm.wishket.com
https://velog.io/@kmnsoo/MVC-%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4
MVC 디자인 패턴
MVC패턴. 자세하게 공부해놓으려고 정리한 글임.image모델 뷰 컨트롤러 (Model - View - Controller)는 소프트웨어 공학에서 사용되는 소프트웨어 \*디자인 패턴.보다 체계적인 틀 안에서 동적 웹을 개발
velog.io
https://opentutorials.org/course/697/3828
MVC 디자인 패턴 - 생활코딩
수업 소개 이미 CodeIgniter의 기본적인 골격인 Controller, Model, View를 살펴봤다. 이것들을 모아서 MVC라고 하는데 이번 시간에는 MVC에 대한 일반적인 정의를 간단히 알아보고, CodeIgniter에서는 MVC 패턴
opentutorials.org
'우테코 프리코스' 카테고리의 다른 글
[우테코 프리코스] 6기 종료 회고 (with 코드리뷰 스터디) (1) | 2023.12.12 |
---|---|
[우테코 프리코스] 4주 차 회고 (7) | 2023.11.28 |
[우테코 프리코스] 2주 차 회고 (3) | 2023.11.03 |
[우테코 프리코스] 1주 차 회고 (7) | 2023.10.26 |