우테코 프리코스 백엔드 6기 (2023) 3주차 회고 (로또)
느낌
벌써 3주차도 끝났고 마지막 4주차만 남았네요.
정말 많은 리뷰를 남겼고, 덕분에 깃허브 팔로우를 해주신 분도 ㅎㅎ 생겼습니다.
연속으로 저에게 리뷰를 부탁드리는 분들은 저도 뭐랄까 내적 동질감이 생겨서 최대한 더 열심히 리뷰해드리고 도움될만한 글들을 남기려고 노력하게 되네요. 서로 잘 됐으면 좋겠단 마음입니다.
우연한 만남
그리고 사실 제일 신기한 것이, 예에에엣날에 초등학생 때 (12년? 전) 블로그를 할 때 가장 친했던 서로이웃 분과 우테코에서 만났습니다.
헉!
사실 원래 코딩을 한다는건 언젠가 어렴풋이 봤었는데, 이렇게 우테코에서 만나게 될 줄은 몰랐습니다.
굉장히 굉장히 반가웠습니다.
우테코 프리코스 3주차 미션
3주차 미션은 로또입니다.
2주차는 확실히 쉬웠고, 덕분에 빨리 끝냈었는데 이번 주차 미션은 난이도가 높아져서 조금 고생을 했습니다.
역시 사람이 자만하면 안됩니다.
시도해본 것
지난 주차에 스프링 따라한다고 Configuration, Framework를 만든건 조금 투머치했던 것 같아, 이번엔 그건 빼버렸습니다.
그리고 사용자 입력을 받는 부분에 대해 요구 사항이 조금 더 복잡해져셔, 이 부분에 대해 더 고민해보는 시간이 되었습니다.
커스텀 익셉션 사용하기
사실 이건 우테코에서는 처음 써본 아이디어지만, 개인적으로 처음 써본 건 아닙니다.
이번엔 에러가 날 때 `[ERROR]` 라는 말이 들어간 에러 메시지를 띄워야했고, 이를 공통적으로 처리하기 위해 IllegalArgumentException
을 상속받은 커스텀 익셉션을 만들었습니다.
public abstract class LottoException extends IllegalArgumentException {
private static final String ERROR_PREFIX = "[ERROR] ";
protected LottoException(String message) {
super(ERROR_PREFIX + message);
}
}
덕분에 에러 처리 요구사항 만족하기는 쉬웠습니다. 😁
엔티티에 public static으로 상수 표현하기
이건 우테코 선배님들의 레포에서 아이디어를 얻었습니다.
public class Lotto {
public static final int LOTTO_PRICE = 1000;
public static final int LOTTO_NUMBER_MIN = 1;
public static final int LOTTO_NUMBER_MAX = 45;
public static final int LOTTO_NUMBER_SIZE = 6;
private final List<Integer> numbers;
// ...
}
아무래도 사람이 private static final 상수만으론 한계가 있나봅니다.
하지만 Constants 같은 클래스를 만드는건 너무 싫었기 때문에, 이런 식으로 해봤습니다.
솔직히 크게 맘은 안 들지만, 베스트 프랙티스가 뭐였을까 아무리 떠올려봐도 떠오르지 않아 해본 차악입니다.
컨트롤러 라는 이름에 대해 고민하기
우테코의 다른 분들을 보면 다들 Controller라는 이름으로 로직을 작성하시더라구요.
뷰(콘솔 입출력)와 관련된 부분을 다루다보니 Controller가 떠오른다는건 알겠지만, 게임 전반적인 로직이 Controller에 들어있는건 어딘가 어색합니다.
그렇다고 Service라는 이름을 명명하는 것도 맘에 안 듭니다. 입출력을 하는건 비즈니스랑은 좀 머니깐요.
그래서 지난번엔 UseCase라는 이름으로 한 개의 레이어를 더 두는 방식으로 해결했습니다.
하지만 이번엔 UseCase라기보다, 이 전체적인 게임을 실행하는 (로직도 실행하고, 입출력도 해주는) 데에 필요한 클래스들을 알아서 간편하게 조작하게 해준다는 의미에서 Facade
라는 이름이 어울린다고 생각했습니다.
그래서 이번엔 퍼사드! 퍼사드라는 이름을 두었습니다.
MVC 패턴 저도 참 좋아하지만, 스프링 웹과 같은 구조와 다르게, 콘솔 게임은 비즈니스 로직을 처리하면서 계속해서 화면에 출력을 해야 하기 때문에 완전 똑같을 수는 없는 것 같아요.
입력에 에러 발생 시 다시 입력 받기
이 부분도 참 고생했습니다..
티스토리 글로 따로 정리했으니 자세한건 이걸 참고하면 좋겠습니다.
https://octoping.tistory.com/52
아이디어 떠올리고 정말 천재적이라 생각했습니다.
근데 은근 흔한 아이디어였던거 같네요.
그래도 제가 만족했으니 됐습니다
이제 Input은 테스트 할만하다
우테코 2주차 회고에서 아쉬운 점으로, Input 테스트가 어렵다고 적었죠.
2주차 미션에서, Input 받는 부분을 인터페이스로 추상화하는 방법으로 서비스 객체를 테스트하기 쉽게 만드는 일을 해냈습니다.
하지만, 여전히 콘솔로 입력 받는 그 클래스를 테스트하긴 어렵더라구요.
validation이 많은 부분인 만큼, 꼭 테스트를 작성하고 싶었습니다.
그런데 이번에 위의 '입력 다시 받기'를 하며 아이디어를 얻어, 요것도 해냈습니다.
public class UserInputAdapter {
private final Supplier<String> userInputFunction;
public UserInputAdapter(Supplier<String> userInputFunction) {
this.userInputFunction = userInputFunction;
}
// ...
}
이렇게 '유저에게 입력받는 함수'를 의존성 주입으로 받아버리자..!
new UserInputAdapter(Console::readLine)
그 다음 이렇게 '콘솔로 입력받는 함수'를 main에서 넣어준다면 된다.
이제 콘솔로 테스트하기 쉽다..!
그럼 이제 테스트 코드를 짜기만 하면 되는 상황이었는데요.
생각보다 테스트를 짜기 어려웠습니다.
로또번호로 -1을 주면 무시하고 다시 입력을 받아야 하겠죠?
그럼 처음에는 -1을 주고, 다음에는 1을 주는 `Supplier`? 어떻게 구현해야 하지?
자바스크립트라면 클로저를 사용해서 어떻게 될 것 같은데.
자바로는 어케 해야하지..? ;;
큰 고민을 하다가, 결국 Mockito 님의 은총을 받았습니다.
String tooManyNumber = "1,2,3,4,5,6,7";
String duplicatedNumber = "1,1,1,1,1,1";
String invalidRangeNumber = "1,2,3,4,5,100";
String notNumber = "a";
String duplicatedWithLottoNumber = "1";
Supplier<String> inputFunction = mock(Supplier.class);
when(inputFunction.get())
.thenReturn(tooManyNumber)
.thenReturn(duplicatedNumber)
.thenReturn(invalidRangeNumber)
.thenReturn(notNumber)
.thenReturn("1,2,3,4,5,6")
.thenReturn(duplicatedWithLottoNumber)
.thenReturn(notNumber)
.thenReturn("7");
UserInputAdapter adapter = new UserInputAdapter(inputFunction);
이런 식으로 구현했습니다.
아쉬운 점
이제 슬슬 아쉬운 점이 별로 없습니다.
매 주차마다 최선을 다했다고 느끼는데, 또 다음 주차가 되보면 뭔가 발전해있습니다.
진짜 신기해요.
아직도 Output은 테스트하기 어렵다
input은 테스트 성공했지만 아직 output은 못 해냈습니다.
output은 어떻게 해야하는걸까요..