[우아한테크코스 7기] 3주차 로또 회고

단디·2024년 11월 5일
1

서론

GitHub - minjae/precourse-7th-lotto

공통 피드백

3주차 회고를 하면서 먼저 든 생각이 있다.
공통피드백을 3주차 회고에 함께 작성하기보다는 공통피드백이 나올 당시에 피드백에 대한 회고를 바로 진행하는 것이다.
그렇게 된다면 다음 미션을 진행할 때 피드백 반영을 더욱 도울 것이라 생각이 들었다.
앞으로의 일정에서는 그렇게 하도록 노력해야겠다.

2주차 목표

함수 분리와 테스트 도구의 사용법 학습하기
다른 사람과의 비교보다는 어제의 나와 비교하며 자신의 속도에 맞추기
메타인지의 최고의 도구 중 하나인 회고를 활용하기 참고링크

2주차 10가지 피드백 회고

1. README.md 를 상세히 작성한다.

README 작성을 할 때에 기능 목록을 작성에 집중을 했다.
아쉬운 점은 해당 프로젝트가 어떤 프로젝트인지에 대한 설명을 보충을 한다면 좋을 것 같다는 점이다.
예를 들면 아래와 같은 방법을 들어서 도입부에 넣었으면 좋았을텐데 라고 생각하였다.

Lotto Program
이 프로젝트는 Java를 사용하여 로또 발매기 프로그램을 구현하는 과제입니다.
로또 번호 발행, 당첨 번호 입력, 당첨 결과 출력, 수익률 계산 등의 기능을 포함합니다.

2. 기능 목록을 재검토한다.

핵심 기능을 파악하고 목록 작성을 하는 것에 집중을 한다.
그리고 테스트를 진행하며 예외 상항을 함께 업데이트하는 것에 중점을 두자.

나도 모르게 아주 구체적으로 들어가는 것 같다.
현재 시점에 다시 읽어보니 새삼 느껴지는 것이 많다.

3. 기능 목록을 업데이트한다.

시작부터 모든 기능을 완벽하게 정리해야 한다는 부담을 갖지말자.
문서를 업데이트하는 것을 목표로 하자.

4. 값을 하드 코딩하지 않는다.

프로젝트가 커지면 생각보다 값의 변경이 많을 것 같다는 생각이 들었다.
미리미리 하드 코딩하지 않는 습관을 들이는 것이 좋겠다고 생각했다.

5. 구현 순서도 코딩 컨벤션이다.

"코드를 많이 읽어보자" 생각이 들었다.
마치 문장, 책을 많이 읽으면 문법에 대해서 체화되는 것처럼 🤗

6. 변수 이름에 자료형은 사용하지 않는다.

변수 이름은 'What'에 포인트를 두자고 생각했다.

자료형이 변경되면 변수명도 매 번 변경 할 것인가?
프로그래밍은 항상 최우선적으로 변경에 중점을 둬야 하는 것 같다.

7. 한 메서드가 한가지 기능만 담당하게 한다.

구현에 몰입할 때 이 문구를 되뇌일 필요가 있다.

8. 메서드가 한 가지 기능을 하는지 확인하는 기준을 세운다.

명확한 이름, 예를 들어 동사와 목적어 패턴이 필요하다고 생각한다.

메서드 이름에 하나의 동사와 목적어가 들어가는지 확인하기

정작 로또 미션에서 이 사항을 미리 적용했다면 어떨까? 후회가 된다. 🥲

9. 테스트를 작성하는 이유에 대해 본인의 경험을 토대로 정리해본다.

첫번 째, 구현할 기능 파악에 도움을 준다.
두번 째, 살아있는 문서가 된다.

고로 단위테스트를 통해서 구현해야할 기능에 집중을 한다.
그리고 살아숨쉬는 문서를 통해 새롭게 발생되는 문제 해결에 도움을 받는다.

10. 처음부터 큰 단위의 테스트를 만들지 않는다.

작은 단위의 테스트를 FIRST 하게 하자.

Fast: 테스트는 빠르게 실행되어야 한다.
Independent: 테스트는 서로 독립적이어야 한다.
Repeatable: 테스트는 반복 실행해도 같은 결과가 나와야 한다.
Self-validating: 테스트는 성공과 실패를 스스로 판단할 수 있어야 한다.
Timely: 기능 구현 전이나 중에 테스트를 작성해야 한다.

생각의 씨앗 심기

1, 2, 3주차 미션을 진행하면서 본격적인 미션 시작을 늦게 한 점이다.
배울 것이 많고 다양한 할 일 들이 존재한다.
코드 리뷰, 기술적 지식들 등 하지만 앞으로는 나의 퍼포먼스를 위해서 우선 순위에 집중하기로 했다.

예를 들면, "무슨 일이 있더라도 우선 화요일에 리드미 작성과 개념까지는 그려놓고 다른 짓을 하기" 처럼 😼

왜냐하면 중요한 부분을 머리에 먼저 담아두면, 무의식적으로 아이디어가 발전하기 때문이다!

본론

요구 사항에 대한 회고

간단한 로또 발매기를 구현한다.

과제 진행 요구 사항

  • 기능을 구현하기 전 README.md에 구현할 기능 목록을 정리해 추가한다.
  • Git의 커밋 단위는 앞 단계에서 README.md에 정리한 기능 목록 단위로 추가한다.
    - AngularJS Git Commit Message Conventions 을 참고하여 작성한다.

커밋 메세지 작성을 부족한 영어실력에도 불구하고 영어를 사용하였다.
진행을 하면서 당연하게도 어려움을 겪었다.
커밋을 하는 것 자체가 어색한 단계인데 여유가 생길 때까지 한글을 사용하기로 마음을 먹었다.
테스트 메소드도 영어로 작성을 했다...그러다보니 점점 커밋과 테스트 코드 작성에 손길이 안 간 점 😭
다음 목표는 거침없이 작성해보는 것이다.

궁금한 사항이 생겼는데 커밋을 무조건 많이 자주하는 것이 좋은 것일까?
테스트를 많이 만드는 것이 좋은 것일까? 에 대한 의문이 생겼다.
여러분의 생각들은 어떠한지 시간이 되시면 댓글로 알려주세요 😊

기능 요구 사항

  • 로또 번호는 1부터 45까지의 숫자 중 중복되지 않은 6개로 구성됩니다.
  • 사용자가 입력한 구입 금액에 따라 발행된 로또 수량을 출력합니다.
  • 당첨 번호와 보너스 번호를 입력받아, 사용자가 구입한 로또 번호와 비교하여 당첨 결과와 수익률을 출력합니다.
  • 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다.
    • Exception이 아닌 IllegalArgumentExceptionIllegalStateException 등과 같은 명확한 유형을 처리한다.

실행 예시

구입금액을 입력해 주세요.
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) - 14개 일치 (50,000) - 05개 일치 (1,500,000) - 05개 일치, 보너스 볼 일치 (30,000,000) - 06개 일치 (2,000,000,000) - 0개
총 수익률은 62.5%입니다.

워크플로우를 먼저 작성을 했다.

  1. 구입금액 입력
  2. 당첨번호 입력
  3. 보너스번호 입력
  4. 발행한 로또 수량 및 번호를 출력(오름차순)
  5. 당첨내역 출력
  6. 수익률 출력

이제는 입출력 예시를 보고 워크플로우를 작성을 하는 것을 가장 먼저 진행하고 있다.
하지만 4번이 먼저 나와야하는데 당시에 잘못 작성을 하여 테스트 코드 및 구현에 실제로 난항을 겪었다.
입출력 예시를 더 꼼꼼히 보면서 논리적으로 생각해 보는 것이 중요하다고 느꼈다.
다음 번 미션에서는 워크플로우로 핵심 기능을 작성을 하고나서 기능과 예외사항에 대한 문서 업데이트를 자주 하는 것을 목표로 해야겠다.

draw.io 처음 활용해보고 README 이미지 올리는 것을 이번 기회에 배우게 되어서 좋았다. 😁

내가 놓친 부분

수익률은 소수점 둘째 자리에서 반올림한다. (ex. 100.0%, 51.5%, 1,000,000.0%)
' , ' 사용하여 출력하는 구현을 못해서 아쉽게 생각한다.

사용자가 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다.
- Exception이 아닌 IllegalArgumentExceptionIllegalStateException 등과 같은 명확한 유형을 처리한다.
"그 부분부터 다시 입력을 받는다" 이 요구사항을 초기에 읽지 못하여 많은 고생을 하였다.
상태 예외 처리에 대해서 활용을 못한 것 같아 아쉽다.
코드 리뷰를 통해 배워봐야겠다.

프로그래밍 요구 사항

  • Java Style Guide
  • 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라
  • JUnit5, AssertJ 이용하여 정리한 기능 목록이 정상 작동하는지 테스트 코드로 확인하기
  • Java Enum을 적용하여 프로그램을 구현한다.
  • camp.nextstep.edu.missionutils에서 제공하는 Randoms 및 Console API를 사용하여 구현해야 한다.
    • Random 값 추출은 camp.nextstep.edu.missionutils.Randoms의 pickUniqueNumbersInRange()를 활용한다.
    • 사용자가 입력하는 값은 camp.nextstep.edu.missionutils.Console의 readLine()을 활용한다.
Randoms.pickUniqueNumbersInRange(1, 45, 6);

Lotto 클래스

  • 제공된 Lotto 클래스를 사용하여 구현해야 한다.
  • Lotto에 numbers 이외의 필드(인스턴스 변수)를 추가할 수 없다.
  • numbers의 접근 제어자인 private은 변경할 수 없다.
  • Lotto의 패키지를 변경할 수 있다.

1. Java Style Guide

포맷터 사용에 대해서 구글링을 하여서 조금 더 공부가 필요한 것을 느꼈다.
우아한테크코스 전용으로 만들었는데 공백과 탭중에 무엇을 사용할 지를 고민을 하였고 탭을 선택했다.
하지만 공백을 주로 디폴트로 사용한다는 이야기를 듣기도 했다.

여러분은 어떤 방식으로 스타일 관리를 하고 계신가요? 🧐

2. JUnit5, AssertJ

JUnit5, AssertJ 에 대해서 이번에 제공해주신 PDF 파일을 이용해서 공부를 했다.
지금은 테스트 흐름은 JUnit으로 관리를 하고 AssertJ를 이용한 검증을 하는 식으로 나만의 간단 정리를 했다.

이번 미션에서 테스트를 선 진행을 하고싶었다.
하지만 인터페이스가 어떠한 지 생각하는 것이 어려웠고 타입과 네이밍을 테스트 코드에서 먼저 생각하는 것이 어려웠다.
간단한 테스트를 작성하고 구현에 들어가서 구현을 많이 진행하고 나오는 경우가 많았다.
이 방법에 대해서는 앞으로도 많은 연습이 필요할 것 같다.

3. Enum

ErrorMessage 와 Lotto Rank 라는 Enum 클래스를 만들어서 사용을 했다.
Rank에 따른 금액과 메세지들까지 함께 관리를 하여 사용을 했는데 특히 개인적으로 메세지 출력 관리에 있어 편리했다.

미션을 진행하며 EnumMap 의 존재에 대해서도 알게 되어서 사용을 했다.
출력이 6등부터 나오고 있어 출력 순서에 대해서 헤매고 있었는데 EnumMap을 사용을 하면서 출력 순서를 편하게 관리했다.

EnumMap은 배열을 기반으로 구현되어 고정된 인덱스 매핑을 활용한다고 한다.
Hash 충돌 관리가 필요없어서 HashMap 보다 속도가 빠르다는 장점이 있다.
Enum을 사용하는 경우 EnumMap을 이용하는 것이 더 빠르고 메모리 낭비를 줄이는 결과를 보이는 것 같다.

4. 라이브러리 제공 메소드

Randoms.pickUniqueNumbersInRange(1, 45, 6);

이전 미션과 같은 메소드로 보고 넘어갔다.
자세히 읽지 않은 내 잘못이 크다.
다른 메소드를 이용해야하는데 이전 랜덤번호생성하는 메소드를 사용했다.
하나의 랜덤 번호를 생성하고 중복되지 않은 6개의 숫자가 생성될 때까지 while 문을 사용을 했다.
그 결과 기능 테스트에서 문제가 계속해서 발생하여 고생을 했다.

미리 좋은 경험을 하여 앞으로 요구사항에 대해서 경각심을 가지고 내 머릿 속 캐시를 자주 비우며 읽게 될 것 같다.
말처럼 쉽진 않을 것 같지만 회고를 통해 자주 돌이켜보면 좋은 결과가 있지 않을까 싶다. 🤭

5. 로또 클래스 제한 사항

  • 필드를 추가할 수 없다.
  • numbers의 접근 제어자 private 변경 불가
  • 패키지 변경 가능

이러한 제약 사항으로 인해 Lotto 클래스에 신중하게 접근하게 됐다.
특히 로또 번호 자체에 초점을 두고 설계를 진행하게 되어, 로또 번호의 데이터와 규격을 표현하는 클래스라는 본질적인 역할에 집중할 수 있었다.

패키지 변경 가능성을 고려해 Lotto 클래스를 도메인 패키지로 이동하여 관리했습니다.
초기에는 검증 패키지로 옮기는 것도 가능할까 생각했지만, 제한 사항을 반영하며 구현을 진행하다 보니 Lotto 클래스는 로또 번호의 규격과 데이터 자체를 나타내는 도메인 객체라는 결론에 도달했다.
검증 관련 기능은 Lotto 클래스 외부에 위치한 LottoNumberValidator와 같은 별도의 검증 객체에서 수행하도록 하여 단일 책임을 명확히 했다.

처음에는 Lotto 클래스에서 보너스 번호를 함께 관리하는 구조를 생각했지만, Lotto 클래스의 본질을 고려해 이를 WinningNumbers 객체로 분리했다.
이를 통해 보너스 번호와 당첨 번호를 별도로 관리하게 되었고, 각 객체가 역할에 충실한 구조로 개선됐다.

이번 경험을 통해 제약 사항이 설계와 구현 방향에 큰 영향을 미칠 수 있음을 체감했다.
제한 덕분에 Lotto 클래스의 역할이 더욱 명확해졌고, 이를 통해 필요한 기능을 다른 객체로 분리하여 설계하는 것의 중요성을 배울 수 있었다.

결론

가장 기억에 남는 점은 가장 나를 힘들게 했던 점들 같다.

테스트를 선 작성 후 구현 (캐싱 문제)
잘못된 입력일 때 다시 입력 받기
랜덤 라이브러리, 제공 메소드 사용하기

테스트를 선 작성 후 구현을 시도하다가 return 0L; 을 이용을 했다.
실패를 하도록 작성을 하고 진행을 했는데 성공을 했다.
내 예측상 이런 결과가 나오면 안된다고 생각했는데 다른 결과가 나와서 당황을 했다.
구현 코드의 리턴 값도 변경 된 후라서 생각을 못했던 것 같다.
제공해준 테스트 ./gradlew clean test 를 진행했고 캐싱되는 것이 문제였다고 판단된다.
메모리가 저장되는 것을 생각을 하는 계기가 됐다.

요구 사항들을 놓쳐서 발생했던 문제로 마지막에 대대적으로 코드 변경이 일어났다.
원인을 빠르게 알아내는 디버깅 실력이 중요한 것 같다.
단위 테스트의 중요성도 이러한 문제점을 해결할 때 도움이 될 것 같다.

테스트 작성을 하면서 테스트를 위해 본 코드를 변경하고 싶었던 적도 있고 구현이 변경되면서 테스트 코드들이 함께 변경해야 하는 경우도 경험을 할 수 있었다.
미션에 들어가기 앞서 먼저 핵심 기능에 대해서 파악을 한다.
그리고 작게 나눠서 테스트 작성과 구현을 동시에 하는 것이 올바른 방향이라고 느껴졌다.

내가 하고 있는 것들이 정답이고 올바른 방향인지 지금의 나로선 알 수 없지만 계속해서 한 걸음씩 나아가고 싶다.
초입에 공통 피드백에서 해준 말을 떠올리며 이번 회고를 마치고 싶다.

다른 사람과 비교하다 보면 조바심이 생길 수 있습니다.
그렇지만 다른 사람과의 비교보다는 어제의 나와 비교하며 자신의 속도에 맞추어서 마무리하는 것을 목표로 삼아 보세요.
이번 경험이 좋은 프로그래머로 성장하는 중요한 역량을 키우는 과정임을 기억해 주세요.

지금 과정이 나에게 녹록치 않다.
어려운 길을 걷게 한다.
그것이 어제의 나와 지금의 나의 차이를 만드는 것 같다.
큰 차이를 만들자.

Stay Hard!

profile
협업, 문제해결, 지속적 학습을 추구하는 개발자 지망생 단디입니다.

1개의 댓글

comment-user-thumbnail
2024년 11월 7일

회고 잘 읽었습니다!
3주차를 진행하면서 여러 내용을 고민하고 배우신 것 같아요 👍
3주차 과제 고생하셨고 마지막까지 화이팅해봐요!! 🙌

+)
이미 학습하셨을 수도 있지만 함수형 인터페이스를 활용하면 재입력 로직을 간결하게 작성할 수 있답니다!
제가 정리해둔 글을 살포시 홍보하고 가겠습니다.. 총총..
함수형 인터페이스와 표준 API

답글 달기