우아한테크코스 6기 프리코스 3주 차는 로또였다.
이번에도 그전과 동일하게 회고의 느낌으로 작성할 예정이다.
내가 고민했던 부분과 적용한 점 수정했던 부분을 이유로 작성할 것이다.
미션에서 Randoms.pickUniqueNumberInRange를 사용해야 했기에 이를 먼저 분석했다. validate를 통해 검증을 거치는데, 이때 validate 메서드의 이름이 validateRange로 되어있었다. 네이밍에 대한 고민이 많던 나에게는 큰 도움이 되었다. 범위 내의 숫자를 담고 셔플을 통해 섞은 다음 필요한 크기로 잘라내는 로직이었다. 이런 역할 분리를 보고 각각의 메서드가 하나의 일만 수행하며 적절히 분리되어 있다고 느꼈다.
그동안 정적 팩토리 메서드를 사용하는 모습과 거의 동일하여 이대로 진행하면 되겠다는 확신이 들기도 하였다.
고민거리
요구사항을 처음 접했을 때, 많은 고민을 했다. 주어진 Lotto 클래스를 활용해야 했으나, 수정 가능한 정도에 대한 명확한 요구사항이 드러나지 않았다. 제공된 필드 값은 List <Integer> numbers로, 만약 로또 값이 매개변수로 전달된다면 각 숫자에 대한 검증도 Lotto 클래스에서 이뤄져야 했다. 이는 관리와 책임 측면에서 잘못되었다는 느낌을 받아 Integer를 Number로 원시값 포장하여 각 숫자에 대한 검증은 Number로 처리하게 했다. 이 방식을 사용하니 각각의 책임과 검증이 명확해졌다.
만약 원시 값을 포장하지 않는다면 각 숫자에 대한 검증로직이 Lotto에 들어가게 된다.
하지만 위처럼 원시값을 포장하면 Lotto에서는 Lotto의 사이즈와 중복검사만 진행하면 된다.
이후 각 숫자에 대한 범위 검증은 Number에게 역할을 부여할 수 있다.
추후에 리펙토링이나 확장성 측면에서도 유리하다고 볼 수 있다.
이게 바로 원시값을 포장하는 이유라고 생각된다.
고민거리
다른 고민은 이번 미션부터 발생한 예외를 처리하고 예외가 발생한 부분부터 다시 입력받는 조건이었다. 이를 try-catch로 간단하게 처리할 수 있었지만, 여기서 다시 입력받는 방법을 재귀로 할지 반복문을 사용할지 고민했다. 재귀를 사용하면 인덴트가 반복문에 비해 1단계 적다는 장점이 있지만, 스택에 쌓이기 때문에 잘못하면 스택 오버플로우가 발생할 수 있다는 단점이 있었다. 반복문은 이러한 스택 문제는 없지만, 인덴트가 1단계 증가한다는 단점이 있었다. 결국 반복문을 선택했는데, 코드의 가독성과 간결함도 중요하지만, 예기치 못한 버그는 최소화해야 한다고 생각했기 때문이다.
고민거리
로또를 생성할 때, 처음에는 랜덤 생성기를 통해 값을 정렬한 후에 로또를 생성했다. 그러나 이러한 로직은 확장성을 고려할 때, 정렬되지 않은 랜덤값이 필요할 수 있다는 생각을 했다. 따라서 이러한 정렬 로직을 어디에 놓을지에 대해 고민했다. 처음에는 dto에서 이를 수행했습니다. 이 방법은 간단히 스트림에 sorted()를 추가하면 해결되어 편리했습니다. 그러나 이 방식은 dto의 목적을 벗어나며, 앞서 언급한 문제가 발생할 수 있다는 우려가 있었다. 만약 다른 출력에서는 정렬이 필요 없다면 다른 dto를 생성하는 것이 올바른 설계가 아니라고 생각했다. 또한 dto는 데이터 전달을 목적으로 사용되는데, 데이터 가공을 한다는 느낌을 받았다. 그래서 정렬에 대한 로직을 뷰로 이동시켰다.
앞서 말한 대로 처음 고려한 로직이다. 실제 랜덤값을 생성하고 바로 정렬해서 주는 방식이다.
dto에서 정렬을 사용한 방식이었다. 코드에 sorted만 적으면 되기에 비교적 깔끔해지는 장점을 가졌지만, 위의 문제점과 동일하다.
뷰에서 정렬을 한 방식이다.
적용한 점
이번에는 Enum을 적극적으로 활용해 보았다. Rank라는 당첨금에 대한 정보를 Enum으로 만들어 관리했다. 매칭 카운트와 보너스 넘버 여부에 따라 적절한 Rank가 생성되었는데, 이를 Map의 키값으로 사용하니 명시적으로 어떤 것을 나타내는지 쉽게 파악할 수 있었다. 또한 메시지도 함께 관리하다 보니 이후 dto를 통해 메시지로 변환할 때 편리했다. 처음에는 Enum을 사용할 필요가 있는지에 대한 고민을 했는데, 확실히 사용하니 이점이 많았다.
매칭 카운트와 보너스 여부로 알맞은 당첨을 반환하도록 정적팩토리 메서드를 만들었다.
이를 아래와 같은 방식으로 계산하는 로직을 작성했다.
확실히 좋았던 점은 key 값이 Rank이기에 무엇을 뜻하는지 확실해졌다.
여기서 EnumMap을 사용해 보았다.
배운 점
기존에 EnumMap에 대해 몰랐는데, 이번에 미션을 하면서 알게 되었다.
HashMap과 EnumMap은 모두 Java에서 맵 (Map) 자료구조를 구현한 클래스이다. 이들은 키-값 쌍을 저장하고 효율적인 검색, 삽입 및 삭제를 지원한다.
HashMap
- 일반적인 유연성: HashMap은 어떠한 종류의 객체를 키로 사용할 수 있다. 이는 매우 유연하지만, 객체를 키로 사용하는 경우 해시 충돌 및 해시 계산 비용이 추가될 수 있다.
- 해시 테이블 기반: 내부적으로 해시 테이블을 사용하여 데이터를 저장한다. 빠른 검색과 삽입을 제공하지만, 일반적으로 일정한 속도와 메모리 오버헤드를 가질 수 있다.
EnumMap
- Enum 타입의 특화된 지원: EnumMap은 오직 Enum 타입을 키로 사용한다. 이는 Enum 상수의 개수에 따라 고정된 크기의 배열을 이용하여 빠른 액세스를 제공한다.
- 배열 기반: EnumMap은 내부적으로 키의 Enum 순서에 따라 배열을 사용하므로, 각 Enum 값은 배열의 인덱스로 직접 매핑되어 상수 시간 내에 액세스가 가능하다.
- 메모리 효율성: EnumMap은 연속적인 메모리 공간을 사용하여 효율적으로 메모리를 관리할 수 있다.
- 타입 안전성: 컴파일 타임에 Enum 타입을 사용하고, 따라서 EnumMap을 만들 때 key 타입을 확인하여 타입 안전성을 제공한다.
테스트의 중요성
위처럼 Enum을 개발하다가 처음 로직에서 나의 실수로 2등이 당첨돼야 하는데 3등으로 나오거나, 당첨이지만 꽝이 나오는 일이 발생했었다. 이는 테스트 코드를 작성하다가 알게 되었다.
이처럼 꼼꼼한 테스트가 중요하다는 것을 느끼게 되었다.
테스트코드를 작성하지 않았다면 잘못된 로직으로 개발할뻔했다.
마무리
아직 다른 사람들의 코드를 리뷰를 진행하지 않고 있는데, 이번주는 바쁘기에 시간이 날 때 천천히 해볼 생각이다.
이번주도 너무 재미있었고, 많은 고민을 한 것 같다.
정말 신기했던 것은 문제를 처음 보았을 때는 매우 쉽게 풀릴 것이라 생각했지만, 정말 생각할 거리가 많았다.
덕분에 계속해서 성장하는 느낌도 받는다.
코수타를 듣던 와중에 코치님께서 마지막 미션이 기존에 비해 어려울 것이라는 말씀을 해주셨다.
이 말을 들었을 때 나는 너무 설레었다.
어려울수록 더 재미있고, 많은 것을 배울 수 있다고 생각한다.
많은 고민을 하고, 마지막 미션은 좀 더 몰입하며 시간을 보낼 수 있는 시간이 될 것 같다.
이제 마지막 한 주가 남았으니까 끝까지 최선을 다하자.
'JAVA' 카테고리의 다른 글
[이펙티브 자바] 객체 생성과 파괴 (2) | 2024.06.13 |
---|---|
[JAVA] 크리스마스 프로모션을 구현하면서 배운점 (1) | 2023.11.16 |
[JAVA] 자동차 경주를 구현하면서 배운점 (3) | 2023.11.02 |
[JAVA] 숫자 야구 게임을 구현하면서 배운점 (4) | 2023.10.27 |
[JAVA] 체스를 구현하면서 배운점 (0) | 2023.07.06 |