본문 바로가기
우아한테크코스

우아한테크코스6기 3주차 미션 회고록

by 구본식 2023. 11. 8.

2주 차 미션때와 동일하게 공통 피드백과 크루원들의 코드 리뷰를 통해 부족한 부분을 많이 배울 수 있었다.

많은 크루원들의 코드를 보면서 MVC 패턴, 일급 컬렉션, DTO 등을 정말 잘 사용하시는 분들이 많았고 살짝 위축된 부분도 있었다.

하지만, 누구든 속도는 다르다고 생각하기 때문에, 하나씩 채우자는 마음으로 3주 차 미션을 임했다.

 

3주 차 미션도 2주 차와 마찬가지로 https://github.com/woowacourse-precourse/java-lotto-6를 fork/clone 한 후, 요구사항을 만족해 미션 코드를 작성하는 것이었다.

구매한 로또와 당첨 로또의 통계 결과를 만드는 것이 핵심이었다. 이번 미션은 이전 미션과 다르게 확연히 난이도가 올라간 것을 체감할 수 있었다.

 

제 코드는 https://github.com/woowacourse-precourse/java-lotto-6/pull/1651 에서 볼 수 있습니다.

2주 차와 마찬가지로 스스로 고민했던 부분과 그 과정에서 공부한 내용을 기록하고자 한다.


MVC 패턴 도입과 고민

이번 미션에서 핵심 로직와 UI를 담당하는 로직을 분리해서 구현하라는 요구사항이 있었다.

사실 이번 미션을 진행하면서, 클래스 역할을 최대한 세세히 분리하고자 여러 클래스를 만들었지만, Controller는 로또 Controller 하나만 존재했다.

그렇기 때문에 굳이 MVC 패턴을 도입하지 않아도 되는 상황이었다.

하지만, 다음 미션에서는 분명 여러 Model이 생겨, 그에 따라 Controller, View가 많아질 걸로 예상하여, 이번 미션부터 익숙해지고자 MVC 패턴을 도입하기로 결정했다.

 

MVC 패턴은 Model, View, Controller로 이루어진 디자인 패턴 중 하나이다.

Model은 핵심 비지니스 로직을 담당하는 레이어이며, View는 사용자 인터페이스를 담당하고, Controller는 Model-View를 연결해 주는 레이어이다.

 

초기에 로또 Controller를 여러 public 메서드로 분리하지 않고, 하나의 메서드에 여러 private 메서드로 만 분리했다.

그러다 보니, 가독성이 떨어질 뿐만 아니라 Controller를 사용한 이점이 하나도 존재하지 않았다. 

즉, Controller의 메서드를 작게 나눔으로써, 하나의 서비스를 모듈화 하고, 코드 재사용성 향상과 책임을 분리시킬 수 있는 장점을 하나도 사용하지 않은 것이다.

이처럼, Controller의 메서드를 작게 나눔으로써, 코드 가독성과 재사용성을 높일 수 있었다.

그리고, MVC 패턴을 적용하면서 가장 큰 고민이 있었다. 바로 View와 도메인 간의 의존성이다.!

현재 미션에서 등수 정보(상금, 번호 일치 개수 등)를 도메인(Model) 영역인 Rank Enum 클래스로 관리하고 있다. 

추가로 상수와 관련된 데이터를 필드로 추가해 응집도를 높일 수 있는 Enum의 장점처럼, 각 등수 별 출력 메시지를 Enum 필드로 추가하고자 했다.

처음에는 가지고 있는 게 괜찮다고 생각했다. 왜냐면 Rank와 관련된 정보를 한 곳으로 모으는 것이 유지보수성을 높일 수 있다고 생각했기 때문이다.

 

만약 이렇게 된다면, 화면에 출력하기 위해 View 영역으로 Rank 정보를 전달하고, View는 다시 Rank를 통해 출력 메시지를 받아오게 된다. Enum의 장점을 최대한 활용하고자 했기 때문에, 자연스러운 상황이라고 생각한다.

하지만, 큰 문제점이 발생한다.

Model 영역의 Rank가 View에 의존성이 생기기 때문에, 만약 View의 출력 메시지가 바뀐다면, 도메인 영역의 Rank에 변화가 발생한다.

 

MVC 패턴에서 하나의 Model이 여러 View에서 다양하게 표현될 수 있는 장점이 있어, View가 Model의 의존성을 가지지만,

Model이 View를 의존하는 것은 단점 밖에 없다고 생각한다. (실제 MVC 패턴 활용 원칙 중 하나라고 한다.)

개인적으로는, View도 Model를 의존하지 않는 것이 가장 좋다고 생각한다. Model이 변했을 때, View가 변할 수 있기 때문이다.
이는 MVC 패턴의 단점이라고 생각한다. 이를 해결하고자 DTO 등을 사용할 수 있겠지만, MVC 패턴 순수 개념에 집중하고자 DTO는 사용하지 않으려고 했다.

 

이러한 이유로, Rank는 오로지 데이터에 대한 순수 비즈니스 로직만 정의하고, 출력 메시지는 포함하지 않았다. 

그리고, View에게는 출력에 필요한 데이터 인, 랭킹 별 당첨 개수를 단순히 넘겨주는 방식을 사용했다.


정적 팩터리 메서드 도입

먼저 정적 팩터리 메서드 패턴이란, Static Method를 통해 간접적으로 생성자를 호출해 객체를 생성하는 디자인 패턴이다. 즉, 생성자를 직접 호출하지 않고, 객체 생성을 별도의 메서드를 통해 간접적으로 이루어지는 것을 의미한다.

 

이펙티브 자바를 읽으면서, 생성자 대신 정적 팩터리 메서드를 도입했을 때 많은 장점을 배웠다. 이번 미션에서 이런 장점이 발휘될 수 있는 상황이 많다고 판단했고, 정적 팩터리 메서드를 도입하기로 결정했다.

 

아래는 학습한 정적 팩터리 메서드를 사용하는 이유이자, 내가 사용한 이유를 정리한 내용이다.

1. 생성 목적에 이름 표현이 가능하다.

다양한 반환 타입 객체와 다른 파라미터를 받는 객체를 생성할 때, 일반적으로 생성자를 오버로딩(다중 정의)하여 사용한다. new 키워드를 통해 객체를 생성할 때, 파라미터 순서와 내부 구조를 알아야 하고, 이는 내부 구조를 노출시키는 행위일 수 있다. 또, 기존 생성자 방식은 매개변수 유형과 개수를 알려줄 뿐이지, 어떠한 편의성을 제공하지 않아 반환될 객체의 특성 제대로 표현하기 힘들다.

이번 미션에서 이러한 상황이 있었다.!

로또를 구매할 개수만큼, 로또를 자동생성해야 했고, 또 구매 가격만큼 자동 로또를 구매해야 했다.

일반 생성자를 사용했다면, 반환 객체의 로또가 자동으로 생성됐는지 알 수 없고, 이를 알기 위해서는 내부 구조를 뜯어봐야 했을 것이다.

따라서, 정적 팩터리 메서드를 사용해 자동 로또 생성임을 명시했고, 반환될 객체의 특성도 유추할 수 있도록 했다.

(지금 생각해 보니, 널리 사용되는 통용된 이름을 사용하는 것이 더 괜찮았을지도 모르겠다...ㅎㅎ)

 

2. 인스턴스에 대해 통제 및 관리에 용의 하다.

생성자가 아닌, 메서드를 통해 객체를 생성하기 때문에 객체 생성 및 통제 관리를 할 수 있다. 대표적인 예로써는 싱글톤 패턴과 캐싱이 있다.

하지만, 이번 미션에서는 이러한 예가 아닌 로또 생성 방식을 관리하기 위해서 사용했다. 미션 내, 로또 번호를 랜덤으로 생성해야 하는 요구사항이 있었다.

초기에는 Lotto 클래스의 메서드로 만들어 사용하려고 했다. Lotto 클래스는 자신의 로또 번호를 관리(유효성 검사, 번호 비교 등)하는 책임을 가진 상태에서, 랜덤 번호 생성 책임까지 가진다면 비만 클래스가 될 수도 있다고 생각했다.

프리코스 동안 항상 메서드는 하나의 기능만 담당하고 작게 유지시키는 것을 강조했다. 클래스도 마찬가지로 해당된다고 생각했다. 결과적으로, 유틸리티 클래스를 사용해 로또 생성 방식을 하나의 클래스로 관리하고자 했다.

만약, 반자동과 같이 새로운 로또 생성법이 생긴다면, 해당 클래스 정적 메서드로 추가함으로써 유지보수성에 용의 할 것이라고 생각했다.


Stream이 항상 좋을까?

2주 차 미션까지도 Stream을 적극적으로 사용했다.

조건이 많은 상황에서 Lambda와 함께 코드 가독성을 높여줄 뿐만 아니라, 다양한 기능 제공, indent 줄이기에 용의 하기 때문이다.

하지만, 모든 상황에서 Stream을 사용하는 것은 오히려 독이 될 수 있다고 생각했다.

위 코드는 2주 차 미션에서 작성했던, List의 이름을 중복 검사하는 메서드이다.

처음에는 직관적이고 가독성이 높다고 판단했던 코드였지만, 보면 볼수록 오히려 가독성이 떨어진다고 생각했다.

 

3주 차 미션에서도 같은 중복 검사 기능이 필요했고, Stream을 사용하지 않고 아래와 같이 작성했다.

오히려 Stream을 사용할 때보다, 코드가 간결해지고 가독성이 높아진 것을 느낄 수 있었다.

 

처음 Stream과 Lambda를 공부하고 사용했을 때, 코드 생산성이나 편리성을 높여주어 Stream 사용을 항상 우선시했다. 하지만, 오히려 독이 될 수 있다는 것을 알았고, 상황에 따라서 적절히 선택해서 사용하는 게 중요한 것을 배울 수 있었다.


장점이 가득한 Enum

이번 미션에서 Java Enum을 사용하라는 요구사항이 있었다.

자바를 공부하면서 자바 Enum은 다른 언어의 Enum보다 장점이 확실히 많다는 것을 알고 있었고, 단순히 상수를 사용하는 것보다 많은 장점을 갖고 있다는 것을 알고 있었다.

 

Enum은 상수별로 인스턴스를 만들어 public static final 필드 형태로 제공하는 메커니즘이다.

또한, 장점으로는 싱글톤 보장, 컴파일 시점 형 안전성, 메서드/필드 추가 및 인터페이스 구현 가능, 문자열 변환 등 무수히 많다. 그러니 사용할 이유가 명확하다.!

특히, 필드와 메서드를 추가함으로써, Enum 상수와 연관된 데이터의 응집도를 높일 수 있다.

 

이처럼, enum 타입 Rank 클래스를 통해, 상수와 관련된 정보와 기능을 하나로 관리함으로써 유지보수성과 응집도 높아진다고 생각한다.

 

아래 링크는 내가 공부할 때 도움이 많이 되었던 자료이다.

https://sedangdang.tistory.com/240

 

상수 대신 Enum을 사용하라

* 이펙티브 자바 2/E를 읽고 공부하기 위해 기록한 게시글입니다. 30. int 상수 대신 enum을 사용하라 열거타입(enumerated type)은 고정 개수의 상수들로 값이 구성되는 자료형이다. enum 자료형이 등장하

sedangdang.tistory.com


마치며

많은 요구사항으로 인해 구현하다 보니, 생각보다 많은 커밋이 쌓였다. 또, 다양한 예외를 처리하고 정규 표현식 및 MVC 패턴을 사용하면서 정말 재미있었던 미션이었다.

 

2주 차 공통 피드백에서, 문제를 작게 나누고, 그중 핵심 기능에 가까운 부분부터 작게 테스트를 만들어 나간다.라는 내용이 있었다.

이전까지는 처음부터 클래스, 메서드를 최대한 상세히 분리하고, 모든 기능을 정의하고 구현을 시작하려고 했다. 이러다 보니 시작이 어려워지고, 막상 구현을 쉽게 하지 못하는 딜레마에 빠졌었다.

이번 미션에서는 클래스를 처음부터 분리하지 않고, 단순히 요구사항에 필요한 기능을 위주로 정리하고 구현을 시작했다. 그러다 보니 오히려 이전보다 클래스/메서드가 작은 단위로 분리되고, 클래스 분리가 더 자연스럽게 이루어지는 것을 느꼈다.

또, 미리 설계한 클래스의 고정관념에 사로 잡혀 구현하는 것보다 더 많은 방법들이 떠올랐고, 기능 목록 정리의 정확한 의미를 느낄 수 있는 미션이었다.

 

미션 난이도가 높아져서 인지, 이번주에 시간이 없어서였는지 구현을 완료하는데 시간이 이전보다 늦어졌던 거 같다.

다음 미션 때는 구현 시간을 측정해 보고, 시간 관리를 하는 연습도 함께 해봐야겠다.