이번 과제는 패턴 상 작년에 마지막 과제였던 로또가 나올 것 같았다.
https://github.com/woowacourse-precourse/java-lotto-6
GitHub - woowacourse-precourse/java-lotto-6
Contribute to woowacourse-precourse/java-lotto-6 development by creating an account on GitHub.
github.com
설계 목표
지난주에 테스트 과도한 캡슐화로 인해 테스트 코드 작성에 어려움을 겪은 점, 클래스 간의 과한 의존성으로 책임을 분리하기 어려웠던점을 기억해 이번엔 더욱 책임을 어떻게 분리할 것인가? 에 대한 고민을 많이했다.
이러한 소프트웨어 설계적 고민에 따른 해결책이 디자인패턴으로 잘 정의되어있었고 MVC패턴이 이러한 문제를 해결해주는데 도움이 된다고 해서 우선 정리를 하고 과제를 시작했다.
우선, 비즈니스 로직과 인터페이스 로직을 잘 분리하고자 했다. InputView에선 입력만 하고자 노력했고 반대로 데이터는 최대한 Model 영역에 정의하고자 했다.
또한, 객체의 책임을 분리함에 있어 지난주차 윤주님이 코드리뷰에 해주신 필드에 인스턴스 변수를 2개이하로 사용하는게 어떻냐는 의견을 고려해 객체를 더더욱 분리하고자했다. 마침 과제 요구사항에도 메서드의 길이가 15라인을 넘지 않고 한가지 일만 하도록 설계를 요구했기에 객체를 더더욱 분리해야할 필요성을 느꼈다.
자바
자바 언어에 대해선 지난 주 리뷰를 바탕으로 두 가지 다짐을 했는데
1. 일급 컬렉션을 사용하자.
2. Enum을 적극적으로 활용하자였다.
Enum은 단순 상수의 역할외에는 사용해본적이 없었고 일급 컬렉션은 제대로 공부해보지 않았기에 불변 객체를 만들기 위한 좋은 기회라고 생각해 공부하고자 했다.
해당 개념에 대해 공부를 하고 과제를 마칠때쯤 들었던 생각인데, 두 개념 모두 상태와 행위를 한 곳에서 관리한다는 특징이 있었다. 어쩌면, 이번 3주차의 과제 목표는 `상태와 행위를 한 곳에서 관리하는 방법에 대한 고민`이었을지도 모르겠다.
설계
우선, 기능 명세를 최대한 간단하고 담백하게 하고자 했다. 2주차까지의 기능 명세는 클래스, 메서드 단위의 세세한 부분까지 정의를 하고 구현을 진행했는데 설계 단계에서 시간이 너무 많이 소요된다는 점, 설계한 메서드가 바뀔 수 있다는 점 등 문제를 많이 느꼈다. 또한 사소한 메서드하나하나의 역할을 적는 것과 기능 명세는 확실히 다르다는 것을 테스트하며 느꼈고, 2주차 과제 피드백 또한 개발 과정에서 README.md를 수정해도 된다는 내용이 포함되어 있었기에 사소한 정의보단 내가 테스트해야하는 기능이 무엇인지?, 구현할 핵심 로직이 무엇인지?에 집중한 명세를 했다.
그렇게 초점을 맞추니 로또 구입, 로또 발행, 당첨 번호 입력, 보너스 번호 입력, 수익률 통계, 예외 처리정도의 큰 범위로 구분할 수 있었다.
이전엔 메서드 단위로 명세하는게 너무 힘들었는데, 이렇게 큰 틀만 잡고 객체 분리나 구현 로직에 대한 것들을 더 고민할 수 있었다.
고민이 되었던 것도 있었는데 예외처리를 어디에 포함시킬까? 였다.
입력 및 객체를 생성하는 부분에서 예외 처리 기능을 넣을지, 아니면 따로 분리된 영역의 기능으로 넣을지 고민을 하다가 따로 분리하였다. 그 이유는 도메인 로직의 메인 기능과 떨어진 기능이라고 생각했기 때문이었다.
하지만, 3주차를 마치고 코드리뷰를 통해 다른 분들의 코드를 보니 입력, 생성 과정에서 발생할 수 있는 예외를 고려하는 것이 더 꼼꼼한 예외처리가 가능할 것 같다는 생각이 들었다.
(실제로 나는 예외를 놓쳐 음수 예외처리를 하지 못했다..)
객체 분리에 대한 고민
메인 로직과 데이터는 Controller가 아닌 Model에서 처리, 관리되어야한다는 기준을 잡고 기능 명세를 위와 같이 하니 명확한 기준이 생겨 객체 분리가 편리했다. 필요한 데이터가 무엇이 있을까? 이 객체의 필드에 3가지 이상의 값이 필요한가? 묶을 부분은 묶고 분리할 부분은 분리해야하지 않을까? 이런 고민을 많이 했다.
따라서, 객체의 책임을 더 명확하게 할 수 있었다.
당첨번호 + 보너스 번호를 포함하는 최종 게임 번호를 생성했고 이를 Controller를 통해 다른 통계 결과 객체에 넘겨 통계를 확인하는 객체를 만들자는 생각이 유연하게 들었다.
한 가지 고민이 되었던 것은 당첨번호(WinningNumbers)와 로또(Lotto) 객체의 유사성이었다.
둘 다 중복되지 않은 6개의 숫자를 가진다는 점, 1~45의 숫자를 갖는다는점에서 중복된 로직이 있었다. 같은 객체를 활용하거나, Numbers 객체를 상속받게 만드는 방법을 고민했지만, 과제 요구사항에서 사용할 Lotto 클래스를 제공해준점과 결국 둘의 책임은 명백하게 달랐기에 객체를 분리했다.
구현
메인 로직과 데이터는 Controller가 아닌 Model에서 처리, 관리되어야한다는 기준을 잡고 기능 명세를 위와 같이 하니 구현은 오히려 편리했다.
하지만, 이번주에 새로 추가된 요구사항이 있었는데 `예외가 발생하면 예외를 발생시키고 해당 지점부터 다시 입력 받고 진행한다.` 였다. 이전까진 프로그램을 종료시켰는데 이번엔 에러 복구? 재시도?라니 어려웠다.
가장 먼저 생각난 방법은 Tag를 만들고 반복문을 쓰는 것이었는데, 뭐 이런 코드가 나왔다.
private String fetchPurchaseAmount() {
String purchaseAmount = null;
boolean isValidInput = false;
while (!isValidInput) {
OutputView.printBuyAnnounce();
purchaseAmount = InputView.input();
try {
ValidateInput.validatePurchaseAmount(purchaseAmount);
isValidInput = true;
} catch (IllegalArgumentException exception) {
System.out.println(exception.getMessage());
}
}
return purchaseAmount;
}
과제 요구사항인 indent를 3개이하로 쓰라는 점을 위반하는 것은 아니었지만 딱 봐도 들여쓰기를 많이하다보니 가독성이 떨어지는 구조였고 논리적이지 않았다.
그래서 재귀를 사용하는 방법을 생각했다.
private int createPurchaseAmount() {
OutputView.printBuyAnnounce();
try {
return InputView.inputPurchaseAmount();
} catch (IllegalArgumentException exception) {
OutputView.printErrorMessage(exception.getMessage());
return createPurchaseAmount();
}
}
일급 컬렉션 활용하기
물론 Lotto 객체도 List<Integer> numbers를 활용해 컬렉션을 불변으로 관리했는데 여기선, 사용자가 구매한 로또 리스트를 관리할 Lottos 객체가 한번 더 필요했다.
이 객체에 필요한 비즈니스 로직은 게임 번호가 주어졌을때 구매한 로또들의 결과를 확인하는 것과 주어진 로또 번호를 출력하는 것이 있었는데 후자가 어려웠다.
출력을 위해선 Lottos를 OutputView로 넘겨야했다. 하지만, 일급 컬렉션의 특징 중 하나는 불변 컬렉션이라고 배웠는데 이를 다시 getter를 써서 List로 반환해도 될까? 객체 자체를 전달하지 않고 List로 전달하면 그 List가 결국 수정될 수 있는 거 아닌가? 의 고민이 들었다. 그래서 모던 자바 스터디에서 배웠던 함수형 인터페이스 Consumer를 통해 OutputView를 그냥 전달해버렸다.
public void printLottos(Consumer<Lotto> printEachLotto) {
lottos.forEach(printEachLotto);
}
하지만, 이는 정확하게 MVC를 지키는 느낌은 아니다. View에 데이터를 전달해야하는데, Model에 View를 전달하고 있으니.. 다른 분들 코드리뷰하면서 어떻게 해결하셨는지 잘 봐야겠다.
추가로 이 글을 쓰며 몇가지 일급컬렉션에 대한 글을 찾아봤는데 일급 컬렉션이 불변 컬렉션이 목적이 아니라고 한다... 다시 공부해야겠다.
https://tecoble.techcourse.co.kr/post/2020-05-08-First-Class-Collection/
일급 컬렉션을 사용하는 이유
일급 컬렉션이란? 본 글은 일급 컬렉션 (First Class Collection)의 소개와 써야할 이유를 참고 했다. 일급 컬렉션이란 단어는 소트웍스 앤솔로지의 객체지향 생활체조 규칙 8. 일급 콜렉션 사용에서 언
tecoble.techcourse.co.kr
이외에 Enum을 적극활용하라는 요구사항이 있었는데, 일급 컬렉션과 Enum의 공통점이 객체의 상태와 행동을 한곳에서 관리한다는 포인트가 있었다.
총 회고
객체의 분리에 집중했던 3주차였다. 1,2주차는 이게 맞나 싶어서 답답한 것들이 많았는데 동료들과 코드리뷰도 하고 과제 피드백을 하나하나 따라가보니 옳은 방향이라는 생각이 든다. 마지막 과제도 화이팅해서 마무리해보자
'BE > 우아한테크코스' 카테고리의 다른 글
[우아한테크코스 6기] 프리코스 - 4주차 (1) | 2023.12.06 |
---|---|
[우아한테크코스 6기] 프리코스 - 2주차 (0) | 2023.11.06 |
[우아한테크코스 6기] 프리코스 - 1주차 (0) | 2023.11.02 |
[우아한테크코스 6기] 프리코스 0주차 (1) | 2023.10.26 |