이전 주차에서 느낀 점까지 포함해서 회고를 적도록 하겠다.
현실적인 부분들과 맞닥트리게 되는 과제
우테코 프리코스의 과제는 다른 분들이 짠 코드를 찾아보지 않는다면 눈치채지 못할 만한 부분들이 있다.
다른 코딩테스트에 익숙한 분들이라면 과제를 받았을 때 그냥 절차형으로 코드를 나열해서 풀려고 하시는 분들이 많을 것 같다.
하지만 프리코스는 말 그대로 성장하기 위한 과정이지 코딩 테스트가 아니다. 다른 코딩 테스트는 재귀, DP, 수학적 기법 등을 사용해서 획기적인 풀이를 찾아내는 것이 목적이라면 우테코 프리코스는 좀 더 현실에 가까운 기능 구현과 코드 구조에 대한 고민이 중점인 것 같다. 어떤 식으로 클래스를 나누는지는 각자의 자유지만 많은 분들이 MVC 구조를 채택한 모습을 보이셨다.

이번주에는 이사를 진행해서 바빴는데 되려 프로젝트가 더 빠르게 구현되었다. 일단 2주차 때 헥사고날 아키텍쳐를 체화하는것이 거의 마무리되었던 것이 크게 작용하기도 했고, 되려 바빠서 기능구현을 먼저 한 뒤에 리팩토링을 한 것이 효율성을 높인 듯 하다. 사실 회고록 쓰는것도 부담스러웠는데 일단 이번에 느낀 대로 줄글로 쭉 쓰는것부터 하는 중이다. 또한 함께 나누기에 Mockito 설명 글도 올렸고, 이전주차 코드리뷰도 조금 늦게 진행하였음에도 이번 주차 프로젝트는 만족스럽게 진행하였다. 다만 마지막에 전략 패턴을 도입하면서 테스트까지 만들기엔 시간이 좀 부족하긴 했다.
코드를 구현할 때보다 내부 구조를 고치려고 할 때 배우는 점이 더 많았던 것 같다.
전략 패턴을 도입하면서 LottoGame 객체를 수정하려다 보니 충돌이 일어나서 내부 구조가 너무 LottoGame에 집중된 것이 아닌가 생각해보게 되었다.
헥사고날에 익숙해지기
이전주차 코드리뷰에서 다른 분들의 풀 리퀘스트를 보면서 느낀 것이 있다.
전체적인 구조나 중점 설명이 추가되어 있으면 코드리뷰하기가 편하다는 것이다.
그래서 내 프로젝트의 구조를 그림으로 그려보았다.

일반적으로 아키텍쳐는 웹 프로젝트를 통해서 배우다보니 순수 자바로 구현하다보면 대충 넘어갔던 개념들이 시험받게 된다.
1주차 때에는 헥사고날 구조에 대한 구체적인 관념도 부족했고, 모든 구조가 좀 확신 없이 구성되었는데
2주차 때에는 헥사고날 구조로 코드를 짜는 것이 익숙해지면서 어느정도 확신이 생겼고
이번 주차에는 회고록을 써도 괜찮겠다 싶을 정도로 확립되었다.
헥사고날 아키텍쳐는 일단 객체지향적 프로그래밍을 하는 입장에서는 꽤나 친숙하게 받아들일 수 있는 개념이다.
클래스를 나누고 객체 간의 상호작용을 통해서 프로젝트를 구성하는 것은 똑같고, MVC 구조(레이어드 아키텍처)를 안다면 사실 크게 다를것 없이 느껴질 수 있다. 함수형 프로그래밍처럼 완전히 결이 다른 패러다임은 아니라는 것.
도메인 주도 설계
위 그림에서 제일 안쪽의 영역은 도메인이라고 부른다. 프로젝트에서 제일 핵심적인 개념을 구현화한 클래스들이 저 곳에 들어간다.
구매 금액 또한 도메인 영역에 넣었는데 일반적인 레이어드 구조에서는 굳이 이렇게까지 하진 않을 것이다.
그냥 서비스 단에서 Integer로 처리하지 않은 이유는, 구매 금액이 1000원의 배수여야 한다는 규칙이 존재하기 때문이다.
도메인 주도 설계에서는 검증 규칙이 존재하는 개념은 가능하면 단순 값이더라도 객체로 만들라고 한다. 이를 값 객체(VO)라고 한다.
반대로 난수 생성 함수를 써서 로또 객체의 리스트를 만들어내는 LottoMachine클래스는 서비스에서 하는 일에 가깝다고 여겼기에 도메인에 넣지 않았다. 여기서 랜덤과 같은 개념은 인터페이스를 통해 주입하는 것이 좋다고 해서 그렇게 하였다. 헥사고날의 핵심은 프로그램에서 이런 구현체를 항상 인터페이스를 통해서 사용하는 것이다. 이를 통해 내부 도메인은 최대한 수정할 필요 없도록 하고 외부는 쉽게 갈아끼울 수 있도록 만들었다. 물론 핵심 규칙이 바뀐다면 어쩔 수 없이 도메인을 손봐야 하지만.

결과적으로 도메인의 사이즈는 커졌는데 서비스단의 코드는 단순해지게 되었다.
보너스 번호라는 변칙적인 룰
이 프로젝트에서 제일 감초 역할을 하는 요소가 보너스 번호라고 생각한다.
그냥 맞힌 번호 개수에 따라서 당첨 등수를 매기고 출력하면 편한데, 보너스 번호 때문에 코드가 복잡해진다.
그런데 만약 이후에 이런 식의 변칙 룰들이 더 생겨난다면 어떨까? 특정 이벤트 기간에만 보너스 번호를 두개 준다던가.
객체지향적 설계나 도메인 주도 설계 자체가 이런 변화에 대응하기 쉽게 만들어주는 부분이 있지만 더더욱 유연성을 가져가려면 어떻게 해야 할까?
이런 경우 전략 패턴이 쓰일 수 있다.
private Prize evaluate(Lotto lotto) {
return AppConfig.winningPolicies().stream()
.filter(policy -> policy.matches(lotto, winningNumbers, bonusNumber))
.map(WinningPolicy::getPrize)
.findFirst()
.orElse(Prize.none);
}
당첨 객체 리스트에서 일치하는 당첨 객체 중 제일 앞에 있는 당첨 객체의 등수를 반환하는 함수이다.
public interface WinningPolicy {
boolean matches(Lotto lotto, Lotto winningLotto, BonusNumber bonusNumber);
Prize getPrize();
}
당첨 객체의 인터페이스이다. 구현체로는 일반당첨과 보너스당첨이 있다.
보너스당첨 객체는 getPrize 함수에서 2등이라는 등수를 반환한다.
당첨 객체 리스트는 AppConfig에서 관리한다. 일반 당첨 객체들을 1등, 3등, 4등, 5등 순으로 넣고 3등보다 앞에 보너스당첨 객체를 넣어줬다.
더 생각해볼 점
과연 이것이 최선이었을까?
당첨 번호와 보너스 번호를 필드로 가지는 클래스를 따로 만들었으면 어땠을까.
LottoGame 클래스가 너무 많은 역할을 하는 것이 아닐까?
과연 전략 객체를 도입한 것만으로 변화에 유연하게 대응할 수 있게 되었을까? 실제로 전략 객체를 도입할 때에도 너무 변화의 파장이 커서 AppConfig에서 static 함수를 써서 임시변통으로 막았지 않은가?

'우테코 프리코스' 카테고리의 다른 글
| 우테코 8기 프리코스 오픈미션 회고 (0) | 2025.11.24 |
|---|---|
| 우테코 프리코스의 assertRandomNumberInRangeTest 함수에 대하여(feat.Mockito, ByteBuddy) (1) | 2025.10.29 |