이번주 계획
이번주 목표
2주차 피드백을 참고해 저번주에 아쉬웠던 점을 최대한 3주차에 반영해보자!!!
구현을 함에 있어서 목표는 다음과 같았다.
- 하나의 클래스에는 객체가 하는 일만 메서드로 사용하기
- 다양한 예외사항 생각해보기
- 객체의 원시값을 캡슐화하고 해당 객체의 메세지를 전달하는 방식으로 구현하기
지난 주에 받았던 피드백이 정말 내 2주차 미션 진행 시 했던 행동들을 CCTV로 감시받는 느낌으로 하나하나 나한테 대한 피드백들이었다. 이번주에는 좀 더 성장해봅시다.
그렇다면, 이번주 미션 진행시 주의사항은?
- Readme.md를 프로젝트 관리하는 부분 처럼 작성해보자.
- 기능목록을 너무 자세히. 즉, 메서드 명까지 적지 말아라.(변경될 수 있으니!)
- 메서드는 하나의 역할을 하도록 분리해보자.
- 객체지향에 대한 학습을 진행하고 이를 코드에 적용시켜보자.
- 테스트 단위를 너무 크게 작성하지 말자.
설계 → 구현 → 테스트
순서를 지키면서 해보자
이번주 내 활동
🔍 피어리뷰-2주차
먼저 피어 리뷰를 요청하는 글을 올리고 천사 5분께서 피어리뷰를 남겨주셨다. 내 코드를 볼 때는 객관적인 시각으로 바라보기가 어려웠는데 제 3자의 입장에서 본 리뷰를 들으니 내가 실수한 부분과 개선해야 하는 방향을 알 수 있었다.
나 또한 다른 분들의 피어 리뷰 요청에 달려가 열심히 달아드리면서 어..이 부분 조금 아쉬운데…근데 나는 그렇게 짰었나?라는 생각이 들었고 다시 내 코드를 한 발뒤에서 살펴봤을 때 내 코드의 아쉬운 부분을 많이 찾을 수 있었다.
🔍 피어리뷰 스터디-3주차
피어 리뷰를 요청하는 글은 아직 작성하지 않았지만, 지금 회고를 작성하는 시점에서 피어리뷰 스터디를 진행했다!
먼저 각자의 코드를 보고 온 후 돌아가면서 각자의 코드를 설명해주고 미리 궁금했던 부분이나 피드백들을 주고 받았다. 실시간으로 다른 분들과 드디어 소통을 진행해보니 같이 학습한다는 느낌을 많이 받았고 같은 문제를 가지고 여러 관점에서 토론해볼 수 있었다는 것이 좋았다.
📖 회고 작성… 드디어 미리미리 작성!
지난 주에는 한 번에 회고를 작성하려고 하니 너무 많은 시간을 빼았겼었다. 그리고 까먹은 내용도 너무 많았기에 이번에는 미션을 진행하면서 고민했던 부분들이나 새로 배운 것들을 미리미리 작성해두어 최종 회고를 작성할 때 참고할 수 있도록 진행했다.
3주차 로또 미션
기능 목록 작성
지난 주 기준 개선점
- 지난 주 기능 목록 작성한 것
- 컴퓨터가 랜덤으로 숫자 뽑기
- 랜덤으로 숫자를 뽑아야 함
- a를 3번 반복해야 함
- 중복이 있는 지 확인하기
- 에러(Validator)
- 플레이어 입력의 예외처리 진행
- 게임 중 입력하는 값이 3자리인지 확인
- 게임 중 입력하는 값이 숫자인지 확인
- 게임이 종료된 후 입력하는 값이 1 또는 2인지 확인
- 플레이어 입력의 예외처리 진행
- 게임 (BaseBallGame
- 게임 메세지를 관리하는 객체 만들기
- 게임 시작하기
- 사용자의 입력인 숫자 3자리를 받기
- 입력 값 에러 검사 진행
- 입력값을 정답이랑 비교해 결과 만들기
- 볼, 스트라이크 갯수 세기
- 결과 메세지 보여주기
- 다 맞추면 게임 진행할 지 말지 결정하기
- 입력 값 에러 검사 진행
- 컴퓨터가 랜덤으로 숫자 뽑기
지난 주에는 기능 목록을 보면서 코딩을 하기가 어려웠다. 또한, 기능 목록을 작성하기보단 클래스를 기준으로 작성한 느낌을 많이 받았다.
따라서, 2주차 피드백을 참고해 이번주 기능목록은 예외사항들에 대한 기능 목록도 작성하고 메서드 명 또한 미리 작성하지 않았다.
기능 목록
📌 상수값
- 로또 한 장 가격 : 1000
- 로또 번호 숫자 범위 : 1 부터 45 사이
- 로또 번호 갯수 : 6
- 보너스 번호 갯수 : 1
- 로또 게임 진행 메세지
- 로또 게임 진행 예외처리 메세지
📖 기능 목록
- 사용자는 로또 구입 금액 입력한다.
- 구입 금액 입력 문구를 출력한다.
- 구입 금액을 입력한다.
- 사용자 입력검증
예외처리
- 사용자의 입력이 숫자가 아닌 경우 예외처리
- 사용자 로또 구입 금액 입력이 1000원 단위가 아닌 경우 예외처리
- 일정 금액 이상 입력한 경우 예외처리
- 로또 구입 금액에 따라 발행된 로또 수량 및 번호를 출력한다.
- 로또 번호를 생성한다.
- 로또 번호를 오름차순으로 정렬한다.
- 발행된 로또 갯수 문구를 출력한다.
- 발행된 로또 번호를 출력한다.
- 로또 번호 생성검증
예외처리
- 가져온 로또가 6개인지 확인한다.
- 가져온 로또번호가 오름차순인지 확인한다.
- 사용자는 당첨 번호를 입력한다.
- 당첨 번호 입력 문구를 출력한다.
- 로또 당첨 번호를 입력한다.
- 사용자 입력검증
예외처리
- 숫자가 아닌 입력이 있는 경우 예외처리
- 중복된 숫자가 있는 경우 예외처리
- 1~45 사이 숫자가 아닌 경우 예외처리
- 숫자가 6개가 아닌 경우 예외처리
- 사용자는 보너스 번호를 입력한다.
- 보너스 번호 입력 문구를 출력한다.
- 사용자 입력 검증
예외처리
- 입력된 당첨 번호와 중복된 숫자가 있을 경우 예외처리
- 숫자가 아닌 입력이 있는 경우 예외처리
- 1~45 사이의 숫자가 아닌 경우 예외처리
- 당첨 내역을 출력한다
- 당첨 통계 문구를 출력한다
- 당첨 내역을 계산한다.
- 당첨 내역을 출력한다.
- 수익률을 알려준다.
- 총 수익률을 계산한다.
- 총 수익률을 출력한다.
- 로또 게임 종료
- 로또 게임을 종료한다.
지난주보다 더 구체적으로 작성을 진행했고 상수값도 미리 지정해놓았기에 참고를 해서 미션을 풀어나갔다.
전체 구조
도메인, UI, 비지니스 로직
이번에는 미션에 도메인과 UI를 분리하라는 피드백이 있었다. 미션을 진행하기 이전에 도메인에 대한 지식이 부족해 도메인에 대해 학습을 진행했다.
내가 이해하고 적용한 방식은 먼저 비지니스 로직을 작성해보는 것이 었다. 기능 목록 중 하나의 기능에 대한 비지니스 로직이 있을 것이다.
예를 들어, 1. 사용자는 로또 구입 금액을 입력한다. 라는 기능에 대한 비지니스 로직을
- 돈을 받는다
- 돈에 따라서 구입할 수 있는 로또 갯수가 나온다
- 로또를 만든다
- 로또번호를 반환받는다
를 쪼개서 이해를 해봤다.
이제 해당 비지니스 로직에서 도메인을 정할 수 있다.
- 돈을 받는다 →
Money
- 돈에 따라서 구입할 수 있는 로또 갯수가 나온다 →
Money
- 로또를 만든다 →
Lottos
- 로또번호를 반환받는다 →
view
이런 식으로 해당 비지니스 로직에 필요한 역할을 분리해 도메인을 정했다.
따라서 내가 생각한 로또 미션에 필요한 domain
은
- 보너스 번호
- 당첨 번호
- 로또
- 로또들
- 로또번호 생성
- 돈
- 로또 통계
로 총 7개의 도메인으로 나눴다.
UI 부분은 콘솔창이기 때문에 InputView
라는 클래스에서 입력과 출력을 관리했다.
Controller, Service
메서드를 작성할 때 구현부와 의미부를 나눠서 작성하려고 최대한 노력했다.
따라서, 게임 전체의 흐름을, 쉽게 말하면 어떤 일을 하라고 지시하는 Controller와 메서를 실행하는 즉, 실제로 일을 하는 Service 부분을 나눠서 관리해 구현부와 의미부를 분리하고자 노력했다.
그래서!!! 최종적으로 다음과 같은 구조로 코드를 짰다.
javascript-lotto
├─ __tests__
│ ├─ ApplicationTest.js
│ ├─ BonusTest.js
│ ├─ LottoNumberTest.js
│ ├─ LottoRankTest.js
│ ├─ LottoTest.js
│ ├─ LottosTest.js
│ ├─ MoneyTest.js
│ └─ WinNumberTest.js
├─ docs
│ └─ README.md
└─ src
├─ App.js
├─ LottoGameController.js
├─ LottoGameService.js
├─ constants
│ ├─ gameCondition.js
│ └─ message.js
├─ domain
│ ├─ Bonus.js
│ ├─ Lotto.js
│ ├─ LottoNumber.js
│ ├─ LottoRank.js
│ ├─ Lottos.js
│ ├─ Money.js
│ ├─ Statics.js
│ └─ WinNumber.js
├─ exception
│ └─ exception.js
├─ validator
│ ├─ BonusValidator.js
│ ├─ LottoValidator.js
│ ├─ MoneyValidator.js
│ └─ WinNumbersValidator.js
└─ view
└─ InputView.js
고민한 부분들이자 동시에 개선점!
내가 하고 있는 것은 객체 지향인가?
지난주 숫자 야구 게임을 진행하면서 내가 과연 객체지향을 제대로 이해한 것인가? 라는 질문에 빠지면서 이게 과연 객체지향인가… 클래스의 탈을 쓴 함수형 프로그래밍이 아닌가? 라는 생각이 들었다.
따라서 이번 미션에서는 객체를 객체답게 사용하기 위해 노력했다. 쉽게 말해서 객체의 원시값들을 캡슐화해서 객체에 메세지를 주고 받자!를 구현해보았다.
비교하기 쉽게 설명해보겠습니당😎
[설명] LottoGameService에서는 전체 게임의 동작들을 나열하고 있습니다. 따라서 한 게임에 필요한 객체들을 전부 들고 있습니다.
지난 주의 저는 다음과 같이 객체의 실제 값들을 주고 받는 형태로 구현을 했습니다.[예시]
class LottoGameService {
#money; // 8000
#lottos; //[[1,2,3,4,5,6], [4,7,13,26,32,38],...]
#statics;
#bonus; //12
#winNumber; // [1,6,13,24,34,40]
...
}
하지만 이번주에는 각 객체에 대한 값들은 객체만 알 수 있도록 객체들을 주고 받는 형태로 구현했습니다.
class LottoGameService {
#money; // new Money()
#lottos; //[new Lotto(), new Lotto(), new Lotto(),...]
#statics;
#bonus; //new Bonus()
#winNumber; //new WinNumber()
...
}
이번 미션을 진행하면서 얻은 가장 큰 수확이고 정확히 객체지향에 대해서 이해하지 못했는데 정말 누가 머리를 탁! 친것 처럼 객체에 대한 이해도가 한단계 상승했습니다.
유효성검사와 에러처리의 분리
지난 주에는 유효성 검사하는 클래스를 하나를 만들고 안에 필요한 유효성 검사 메서드를 한 곳에 몰아 넣어 구현했습니다. 하지만 이처럼 진행하면 역할의 분리가 불분명해지고 해당 메서드가 어떤 값을 유효성 검사하는 지 알기 쉽지 않아 이번에는 각 클래스에서 필요한 유효성 검사를 따로 분리해 구현했습니다.
또한, 유효성 검사를 하는 클래스 안에 에러를 던지는 메서드를 같이 두었습니다. 이또한 역할의 분리가 필요하다고 생각해 에러를 던지는 클래스를 따로 두었습니다. 이유는 입력이 들어오면 옳은 입력인지 틀린 입력인지 확인하는 과정과 최종적으로 에러를 던지는 것은 총 2가지의 역할이라고 생각했다. 입력을 하고 옳은 입력이라면 에러를 던지지 않은 경우도 있기 때문입니다.
Controller가 모든 것을 했었으면 좋겠다…
로또 게임이 시작하면 InputView
에서 돈을 받으면서 게임 동작들이 하나 둘 시작됩니다. 처음에는 Controller
에서 게임을 시작을 해서 InputView
를 받아와 다음 동작을 실행하는 방식으로 진행하고 싶었습니다. 하지만 사용자의 입력을 받아오는 동작은 비동기라서 입력값을 받기전에 다음 메서드가 실행이 되었습니다. 따라서 게임을 Controller
가 모든 입력받는 행위까지 컨트롤하는 것이 아닌 InputView
이 입력값을 받고 Controller
가 다음 동작들을 받아옵니다. 동작이 끝나면 InputView
이 다음 입력값을 받는 동작을 실행하는 방식으로 변경했습니다.
[요약] 왼쪽 구조에서 오른쪽 구조로 변경했습니다.
DI 구현
정말,, 클래스에 불필요한 동작 없이 정말 필요한 것만 있었으면 좋겠다…! 라는 고민을 하면서 정확히 각 클래스마다 어떤 동작들이 필요한지 생각해보았다.
각 클래스가 딱 필요한 동작만 하기 위해서는 클래스의 데이터들을 덜어줄 필요가 있다. 로또 미션에서는 랜덤으로 발행되는 로또 번호를 LottoNumber
클래스에서 생성을 하고 Lotto
에 주입해주는 방식으로 구현을 했고 다른 클래스들도 동일하게 구현했다.
참고한 글
의존관계 주입(Dependency Injection) 쉽게 이해하기
작은 단위로 테스트를 진행하자
테스트를 진행하면서 매번 로또를 만들어서 로또 게임의 결과까지 제대로 반환되는 지 확인해야 하나? 라는 의문 점이 생기면서 테스트를 하는 방식과 어떤 것을 테스트 하는 것인지 생각해봤다. 아마도 지난주 까지 모든 것을 큰 단위로 테스트를 진행하다보니 이런 생각을 했던 것 같다. 하지만 단위 테스트를 진행하면서
설계 → 구현 → 테스트
이 순서를 지키다 보니 정확히 어떤 것을 테스트 해야하는 지, 작은 테스트가 어떤 것을 의미하는 지 알게되었다.
결론은 해당 메서드가 잘 동작하는 지만 확인 하는 것이지 내가 거기에 스토리를 만들어서 넣어서 결과물을 도출해낼 필요가 없던 것이다. 예를 들어 Lotto
의 클래스에는 다음과 같은 메서드들이 있다.
isContain(number) {
return this.#numbers.includes(number);
}
isEqual(lotto) {
return JSON.stringify(this.#numbers) === JSON.stringify(lotto.#numbers);
}
이 메서드를 테스트 하기위해 간단한 로또를 만들고 로또가 제대로 된 값을 return하는지만 확인을 하면된다.
test("입력된 숫자가 로또 번호에 있는지 확인한다.", () => {
const lotto = new Lotto([1, 2, 3, 4, 5, 6]);
expect(lotto.isContain(1)).toBeTruthy();
expect(lotto.isContain(7)).toBeFalsy();
});
test("두 개의 로또가 같은지 확인한다.", () => {
const lottoArr1 = [1, 2, 3, 4, 5, 6];
const lottoArr2 = [7, 11, 16, 35, 36, 44];
expect(new Lotto(lottoArr1).isEqual(new Lotto(lottoArr1))).toBeTruthy();
expect(new Lotto(lottoArr1).isEqual(new Lotto(lottoArr2))).toBeFalsy();
});
내가 작성한 메서드가 제대로 동작하는 지 여부를 바로바로 테스트 코드를 작성해 테스트 해보는 것이 작은 단위의 테스트라는 것을 깨달았다.
내가 생각하는 예외상황
기존에 주어진 예외상황이 아니라 개발을 진행하면서 더 발생할 수 있는 예외상황을 만들어 유효성 검사를 진행하고 자 하나라도 더 생각해보려고 노력했다.
그 결과 나는 요구사항에서는 없는 사용자가 입력할 수 있는 최대 금액을 제한하는 것을 추가해봤다. 너무 많은 금액을 입력할 때는 프로그램에 오류가 생길 수 있으므로 사용자 입력금액을 10억으로 제한시켰다. 다음주 미션에서는 최대한 다른 고려사항도 포함시켜보도록 노력해봐야겠다.
총 회고
이번 주 미션을 진행하면서 성취감도 많이 느끼고 좌절감도 많이 느꼈다. 내가 이런거를 왜 여태까지 이렇게 했지…? 이건 도대체 어떻게 해결하는 거지..? 하다가 갑자기 해결책이 떠오르고 그 해결책이 앞서 했던 방식과는 완전히 다른 방식이라는 것을 깨달았을 때 엄청난 성취감이 몰려왔었다. 이번주에는 정말 얻어간 것이 많은 한 주였다. 기능 목록 작성법, 예외처리, 객체지향 프로그래밍, 도메인과 UI에 대한 이해 등 에서 각각 무릎을 탁 치는 듯한 깨달음이 크게 있었어 정말 재미있게 구현을 했던 1주일이었다. 특히 지난주에 아쉬웠던 점을 모두 다 포함 시켜보려고 노력을 했다. 미션 제출이 끝난 지금 이 시점에서도 많은 아쉬움이 있고 리팩토링을 하고 싶은 마음이 정말 크다… 그렇지만 정말 재미가 붙었기에 얼른 다른 분들 피어리뷰를 해서 또 개선할 점들을 찾아 정리하고 다음주 미션에 적용시켜봐야겠다.
'우아한테크코스 5기' 카테고리의 다른 글
[우테코 5기 프리코스] 우테코 5기 4주차 회고 (0) | 2022.11.25 |
---|---|
[우테코 5기 프리코스] 우테코 5기 2주차 회고 (2) | 2022.11.10 |
[우테코 5기 프리코스] 우테코 5기 1주차 회고 (0) | 2022.11.04 |
nvm으로 node 버전 변경하기 (0) | 2022.11.04 |