지독한 몸살로 고생했다. 구현은 고사하고 집에선 잠만 잤다. 병원에 갔다와도 컨디션이 크게 나아지지 않아 수액을 맞았더니 좀 나았다.
시간이 없었다. 피드백 내용을 먼저 확인했어야 하는데 급한 마음에 구현 요구사항만 보고 시작했다. "객체간의 상호 협력"을 바탕으로 구현해야한다는 생각에 객체와 그 객체의 동작을 먼저 정의했는데, 해당 방식보다는 기능의 명세 후 작은 기능부터 테스트를 통해 구현하는 방식이 더 좋아보인다.
나의 방식(먼저 클래스 정의) 에서는 어쨌든 구현의 큰 틀을 정해놓고 시작하지만, 기능을 먼저 명세하고 그 기능을 테스트하는 방식은 "동작"에 중심이 되는 구현을 생각하게 하는 것 같다.
위 내용을 생각하게 된 계기는 보너스 번호에 대한 구현을 시도한 시점이었는데, 내가 설계한 객체에서는 "보너스번호"를 어떻게 처리해야 할 지 생각하지 못했다. 구현을 하면서 이 부분을 어떻게 처리해야 할 지 고민하며 다음과 같은 옵션들을 생각했다. (러프하게 정리했다)
보너스 번호?
1. 사용자의 입력으로 만들어진 "정답 로또" + 보너스번호
2. 랜덤하게 만들어지는 로또번호
so, 사실상 사용자는 7개의 숫자를 가지고, 6개의 랜덤숫자로 이루어진 N개의 로또를 판별한다.
제약조건
1. 필드에는 보너스번호를 추가할 수 없다. (로또객쳬)
2. 그러면 Lottos 객체나, Calculator 객체에 추개햐아 한다.
케이스 1. Lottos 에 둔다
단점 : 불변 컬렉션이 깨진다
장점 : 여기서 연산해서 칼큘레이터에 전달 가능하다.
케이스 2. Calc 객체에 둔다
단점 : List 를 다시 순회해야 한다
장점 : 불변 컬렉션(일급 컬렉션) 을 지킨다. 계산 후 이넘을 리턴할 때 더 깔끔하다 (5개 맞은경우에 대한 판별이)
케이스2 선택한다면?
Lottos 컬렉션은 랜덤 생성된 로또 컬렉션이다. 이 컬렉션은 유저가 입력한 값과 랜덤값 비교 정도는 해도 될 것 같다.
추가
케이스 3. 구성을 활용해서, 새로운 객체를 만든다.(유저가 입력한 로또 객체~ 이름으로)
장점 : 깔끔해진다.
단점 : 로또 객체와 다른 점이 크게 두드러지지 않는 것 같아서, 불필요한 중복이 발생하는 것은 아닐지 두렵다.
월요일 밤 잠들기 전 생각해보니, 케이스3을 선택하는 것이 제일 타당해보였다.
보너스 번호에 대해서 고민하면서, 내가 놓친 부분이 있지 않나 하는 마음에 코드 리뷰를 커뮤니티에 요청했다. 그리고 이런 코드리뷰를 받게 되었다. 코드리뷰 링크
위 내용은 내가 항상 고민하던 내용이었다. private 매소드를 테스트하려면 어떻게 해야하는지 고민하면서 해당 방법을 찾아봤는데, 해당 방법을 알려주는 블로그 주인분의 의견을 보아서나, 아니면 private 매소드를 테스트하는 방법을 봐서나(리플렉션 등) private 매소드를 테스트하는 것은 해선 안되는 일처럼 느꼈다. 그리고 이를 막기 위해 접근제어자를 protected 로 변경하는것 또한 좋지 않은일이라고 생각했다.
결국 생각한것은 해성님의 리뷰대로 객체를 분리하는 것인데, 그렇다면 또 아래와 같은 고민이 생기게 됐다.
해당 객체를 분리해서 원래 객체에서 사용하려면 어떻게 해야 할까?
라는 고민이었다.
두달 전 쯤 헤드퍼스트 디자인 패턴 이라는 책을 보면서 구성이라는 구현 팁을 얻었다. 전략 패턴에서나, 아니면 다른 디자인 패턴에서나, 어쨌든 객체지향적인 구현을 위해서 사용하는 방법인데, 클래스가 "의존"을 사용하지 않고 구성을 사용했을 때 가지는 장점들이 있다는 것이 주였다.
해성님의 조언대로 객체를 나누면서, 만들어진 새로운 객체를 유연하게 사용하기 위해 구성을 사용하기로 결정했다. 그리고 보너스번호의 문제도 위와같이 구성을 사용해서 해결 가능하다고 판단됐다.
그에 따라 새로운 UserLotto 라는 객체를 만들어서 이를 해결하기로 결정했다.
public Lotto(List<Integer> numbers) {
validate(numbers);
Collections.sort(numbers); // 랜덤값이 랜덤순서로 들어올 시 오름차순 정렬한다.
this.numbers = numbers;
}
@DisplayName("일치율(matches) 확인 - 6개 전부 일치")
@Test
void compare_ALL_MATHCES() {
Lotto lotto = new Lotto(List.of(1, 2, 3, 4, 5, 6));
Lotto lotto2 = new Lotto(List.of(1, 2, 3, 4, 5, 6));
assertThat(lotto.compare(lotto2)).isEqualTo(6);
}
위와 같은 형태로 테스트 코드를 작성했었는데, 아래와 같은 익셉션이 발생했다.
java.lang.UnsupportedOperationException
at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:142)
at java.base/java.util.ImmutableCollections$AbstractImmutableList.sort(ImmutableCollections.java:261)
at java.base/java.util.Collections.sort(Collections.java:145)
at lotto.Lotto.<init>(Lotto.java:12)
리스트가 불변이라는 뜻이었다. List.of() 매서드가 불변 컬렉션을 반환한다는 사실을 알지 못했었다.
두 가지 옵션이 있다.
2번 옵션을 선택하기로 했다. (기존 코드를 건들지 않기로)
NextStep 에서 제공하는 라이브러리를 까보니, 리턴하는 랜덤값 List 가 불변임을 확인했다. 그에 따라 new ArrayList<>(랜덤리스트).sort 를 사용해서 값을 내려주는 것으로 결정했다.
지존 글씨체