모눈종이에 사각사각

[프리코스] 3주차 회고 본문

활동기록

[프리코스] 3주차 회고

모눈종이씨 2022. 11. 17. 12:49

0. 시작

이번 3주차 미션은 정말 코드를 많이 수정했던 한 주였다.

다 작성하고 테스트 코드를 돌렸는데 예상치 못한 곳에서 오류가 발생했고,

이를 해결하려다 보니 전체적인 구조를 바꿔야 했었다.

이번주는 2주차 미션의 목표인 함수 분리와 테스트 작성에 더해서

클래스를 분리하고, 도메인 로직에 대한 단위 테스트를 작성하는 연습을 하는 것이 목표였다.

1. 기능 요구사항

기능 요구사항
입출력 요구사항
실행결과예시

다음은 내가 작성한 구현할 기능 목록이다.

세세하게 조건을 맞춰줘야 하는 부분이 많아서 지난주보다 많아졌다.

구현할 기능 목록 - docs/README.md

2. 프로그래밍 요구사항

위의 사진은 지난주 까지의 요구사항이고, 

이번주에는 이렇게 추가된 요구사항이 있었다.

 

Java Enum을 적용해야 했기에 Enum에 대한 공부도 다시 하고, 적용하려 노력했다.

그리고 도메인 로직에 단위 테스트도 구현해야 했다.

 

제공된 Lotto 클래스를 사용해야 했다.

3. Enum 적용하기

등수를 저장하는 Prize Enum을 만들었다.

각각 맞은 로또 번호의 개수(MatchCount), 상금, 출력 메시지가 들어있다.

 

처음에는 이렇게 설계했었는데 winningCount와 bonusCount의 조합은 MatchCount 클래스와 같기 때문에 MatchCount클래스로 바꾸어주었다. (그러나 이게 더 좋은 방법인지는 살짝 의문이다.)


Prize의 자율성 높이기

 

Prize 내부 메서드 수정 전

수정 전 Prize enum
수정 전 Result class
수정 전 Result class

 

수정 전에는 get 메서드로 Prize값들을 호출했다.

특히 getAllPrize 메서드를 통해 Prize내부의 모든 리스트도 가져올 수 있었다.

이렇게 가져온 값들로  Result클래스에서 로직을 처리했다.

 

이렇게 코드를 다 작성한 후 살펴보니, Prize enum의 자율성이 떨어지는 것처럼 보였다.

따라서 다음과 같이 수정해주었다.

 

수정후

수정 후 Prize enum

Prize 내부의 get메서드들의 접근제어자를 private으로 바꾸어주어 내부에서만 쓸 수 있도록 했다.

 

수정 후 Prize enum

그리고 Result클래스에서 처리하던 로직들을 Prize 내부에서 처리하도록 메서드를 이동시켜주었다.

 

수정 후 Rsult class

Result 클래스에서는 Prize 내부에서 처리한 값을 가지고 구현하도록 했다.

4. MatchCount, 로또 번호 비교 결과 다루기

MatchCount 클래스는 다음과 같이 맞은 당첨 번호 개수와 보너스 번호 개수로 구성되어 있다.

 

Prize enum에는 해당 등수에 맞는 MatchCount가 저장되어 있다.

 

해당 등수에 맞는 MatchCount가 있다면, 저장 개수를 올려주는 방식으로 코드를 구현했다.

 

그리고 테스트코드를 작성하여 돌린 결과, 일치하는 게 하나도 없다고 나오는 것이다!

MatchCount는 객체이기 때문에 equals로 비교하면 같은 결과가 나오지 않는 다는 것을 간과했다.

 

 

그래서 equals와 hashcode를 오버라이드 해서 코드를 작성해주었다.

 

결과가 잘 나오나 싶었더니 또 하나의 문제가 발생했다. 

 

예를 들어 당첨 개수가 3개, 보너스 개수가 1개면 둘 다 똑같이 5등인데, 

equals로 비교하다며니 당첨 개수가 3개, 보너스 개수가 0개인 것만 5등으로 판단하는 것이었다.

 

그래서 다시 코드를 바꾸어주었다.

당첨 개수가 다르면 무조건 다른 결과이고,

당첨 개수가 같을 때 번호 5개가 일치할 경우만 bonusCount를 따져주면 되므로, 다음과 같이 구현했다.

5. Validation 클래스의 분리

당첨번호를 가진 객체인 LottoNumber 클래스와

로또 번호를 생성하는 Lotto 클래스의 validation로직의 중복이 상당히 많음을 발견했다.

 

입력 값과 로또 발행의 조건만 봐도, 겹치는 게 많다.

 

그래서 Validation 클래스를 따로 만들어주었다.

 

일단 수정 전의 상황을 보면

왼쪽: 수정 전 Lotto 클래스 안 , 오른쪽: 수정 전 LottoNumber 클래스 안
수정 전 UserInput 클래스 안

 

 

또한 이렇게 함으로써 고민하고 있던 다른 한 부분이 해결되었다.

사용자에게 입력을 받는 UserInput 클래스가 있고, 당첨 번호를 저장하는 LottoNumber 클래스가 있다.

사용자에게 입력을 받아 LottoNumber 클래스에 번호를 저장한다.

그러면 사용자에게 입력을 받은 값의 검증은 어디서 해야하는 걸까?

 

먼저 UserInput에서 하면, 입력값에 대한 검증이니까 위치가 적절한 것 같은데, 

다른 사용자가 혹시 UserInput을 통해 LottoNumber을 생성하는 것이아니라,

그냥 LottoNumber을 생성하려 한다면...? 바로 에러가 발생할 것이다.

 

LottoNumber 클래스 안에 입력값 검증 로직을 넣자니, 다른 데서도 쓰일 것 같은 이 중복된 로직을 

LottoNumber 클래스 안에 넣어버리는게 마음에 걸렸다.

 

이것이 모두 Validation 클래스를 생성하며 해결이 되었다.

6. 에러 처리

사용자가 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 종료한다.

라는 요구사항이 있었다.

 

이에 관해서 어려움을 겪고 있었다.

 

분명히 에러 메시지 잘 출려하고, 에러를 던졌는데, 테스트 코드에서는 왜 실패할까,,,

 

한참을 고민했다. 슬랙에도 나와 같이 생각하는 사람들이 많아서, 나만 이런 고민을 하는 게 아니구나 위안을 얻으며 생각하고 있었다.

 

그러던 중 우테코에서 추가 메일이 왔다.

이걸 보는 순간 깨달았다.

throw를 던져서 상위 클래스에서 처리해야하는구나!!

 

바로 main 메서드에 이렇게 처리해주니 해결되었다.

7. static 메서드

static 메서드로 인해 테스트 코드를 돌려 게임을 연속으로 진행하면 초기화되지 못해 에러가 발생했다.

 

main 메서드는 static이기 때문에 static 메서드와 static 멤버밖에 호출할 수 없었다. 

그래서 발행한 로또를 static으로 관리하고 있었다.

static List<Lotto> lottoTickets = new ArrayList<>();

 

 

문제 해결을 위해 Play 클래스를 만들어 main메서드 안에서 실행하던 로직들을 Play 클래스에서 실행하도록 변경하였다.

 

 

8. 단위 테스트

메서드 하나에 대해 정상로직과 비정상 로직을 모두 검증하였다.

 

처음에 비정상 입력을 이렇게 다 다르게 해서 테스트 코드를 작성했지만, 

한 번에 할 수 있는 방법을 알게 되었다.

 

@ParameterizedTest를 통해 argument를 여러 개 반복해서 입력할 수 있다.

source를 넣는 여러 방법 중 @MethodSource를 사용했다.

 

MatchCount를 확인하는 테스트도 위와 같이 수정해주었다.

 

private 메서드는 해당 메서드를 사용하는 public 메서드를 통해 검증하였다.

 

9. 소감

제출 완료!

 

지난주보다 복잡해졌던 로직에 중간에 구조를 많이 변경해야 했지만, 구조를 변경하게 된 것은 코드를 작성하면서 클래스의 분리의 필요성을 느꼈기 때문이다. 미션을 진행하면서 자연스레 클래스를 분리할 수 있게 되었다. 또한 단위 테스트를 거치면서 에러를 수정하고, 자신감을 얻으며 미션을 진행할 수 있었던 한 주였다.

 

+ 추가

 

제출이 끝난 후 슬랙에 올라오는 내용들을 읽으면서 많은 부분을 놓쳤음을 인지했다.

첫째, 숫자 자료형에 관한 부분이다. 

파이썬이 주 언어였다 보니 숫자의 자료형에 관해 신경쓰지 않는 습관이 생겼다.

이번에도 long을 생각하지 못하고 int만 사용했었다. 

int는 -2,147,483,648 ~ 2,147,483,647 를 허용하기 때문에 6개 일치하는 로또가 2개 이상이면 바로 에러가 발생한다....

왜 테스트 코드를 돌릴 때 6개 모두 일치하는 로또를 하나밖에 테스트해보지 않은 걸까...

파이썬에 익숙했던 나에게 "long" 자료형이 깊숙하게 들어온 실수였다. 이번을 계기로 앞으로 절대,, 잊어버리지 말아야지. 

 

둘째, 경곗값에 관한 부분이다.

사용자가 금액을 0이라고 입력한다면...?

왜 이 부분을 생각하지 못했을까

음수 값이나 최소 경곗값, 최대 경곗값 등 경곗값에 대한 테스트를 진행하자.

 

셋째, 요구사항을 "꼼꼼!" 하게 읽자.

예시만 보지 말고 요구사항을 읽자.

로또 번호를 오름 차순으로 출력했어야 했는데 이부분을 놓쳤다.

 

사실 중간에 구현하면서 수익률 출력 부분에서 둘째자리에서 반올림인데 둘째자리까지 반올림하여 출력하는 건 줄 알고 코딩해 고쳤었고, 수익률에 콤마를 찍지 않아 또 수정했었다.

처음부터 꼼꼼하게 읽었어야 했는데 그러지 못했다.

 

벌써 프리코스의 마지막 미션밖에 남지 않았다.

실수한 내용들은 절대 잊지 말고 다음에 꼭 반영하고,

한 주도 힘내서 해봐야겠다.

Comments