제목에서 유추할 수 있듯이, 기나긴 여정 끝에 멤버십 과정을 합격했고 1차 학습스프린트(1, 2, 3, 4주차)에서 경험했던 것들을 회고해보려고 한다.
멤버십 과정 합격
네이버 부스트캠프 웹・모바일 9기의 진행 순서는 아래와 같이 진행된다.
1. 서류
2. 1차 문제 해결력 테스트
3. 베이직 과정
4. 2차 문제 해결력 테스트
5. 챌린지 과정
6. 3차 문제 해결력 테스트
7. 멤버십 과정 << 여기까지 왔다!
8. 수료!
3차 문제 해결력 테스트까지 마치고, 합격 메일과 함께 멤버십 과정에 입과했다!
챌린지 회고 글에선 부정 탈까봐 적진 않았지만 챌린지 과정 자체만으로도 너무 많은 걸 배울 수 있었고 값진 경험이었다고 생각해 떨어지더라도 상처받지 않도록 마음을 비우고 있었다.
그리고 "내일쯤 결과 메일이 오겠지?" 생각하고 있던 찰나 메일함이 울렸고, 밤 9시에 합격 메일이 도착했다.
아마 긴장하지 말라는 운영진 측의 배려가 아닐까 싶다ㅎㅎ
그 덕분인지 합격 메일을 받았을 때의 기분은 기쁨보다 당황스러움이 컸다(?)
1, 2주차
1, 2주차는 미션의 난이도는 높지 않았지만, 멤버십 생활에 적응하는데 애를 많이 먹었다.
매일 미션이 주어지던 챌린지 과정과는 달리 멤버십의 학습 스프린트 미션은 2주 단위였다.
주간 계획 세우기
그에 따라 주간 계획도 세워야 했는데 이건 4주차까지 한 지금도 어떻게 나눠야할 지 모르겠다.
내가 경험이 많은 개발자라면 요구사항을 보자마자 '음 구조는 이렇게 설계하고, 이 부분은 이렇게 구현하면 되겠다.' 가 머릿 속에 그려지고 적절한 일정을 산출할 수 있겠지만, 나는 그렇지 않기에 요구사항 갯수를 기준으로 일정을 산출했다.
이렇게 하면 발생하는 문제가 요구사항 별로 난이도가 다르고 소요 시간이 다른데 단순히 갯수로 나누다보니 작업이 일찍 끝나는 날도 있고, 하루종일 매달려도 작업을 못 끝내는 날도 있었다.
멤버십 생활 적응하기
그리고 다시 멤버십 생활 적응기로 돌아와보면, 챌린지와는 다르게 마스터 분들의 수업도 있고, 외부 연사분들의 특강, 그리고 더 다양해진 동료들과의 활동 등 참여해야 할 활동들이 늘어났다. 지금은 조금 적응이 되어 자연스럽게 참여하고 있지만, 1, 2주차에는 깜빡하고 있다가 동료들이 알려주어 들어가기도 했다..ㅋㅋㅋ
3, 4주차
3, 4주차는 미션의 난이도가 급격하게 느껴졌다고 생각했다.
멘토님의 말을 빌리면 간단하게 생각하면 간단한 미션일 수 있지만, 어렵게 생각하면 정말 어려운 미션이었다.
빠삭하게 알지는 못하지만, 사이드 프로젝트를 진행하며 모듈화와 의존성 분리 등 컴포넌트화에 많은 신경을 쓰며 개발을 진행한 경험이 있기에, 그 때 가지는 장점과 단점에 대해 경험적으로 알고 있다.
MVC 패턴과 노티피케이션 센터를 통해 이벤트를 주고받다보면 처음에는 구현이 빠르겠지만, 2주 동안 진행하는 미션 특성 상 다음 주차가 되고 새로운 요구사항이 추가되면 복잡성이 증가할 것이라고 생각했다.
그리고 내가 도전했던 것은 mvvm 구조 + 단방향 플로우 + 의존성 관리 를 적용해 요구사항을 구현해보기로 했다.
단방향 플로우를 구현하기 위해 ReactorKit 라이브러리를 사용하고, 의존성 관리를 위해 Needle 라이브러리를 사용한 적은 있다. 하지만 추후 회사 과제에서 라이브러리 제한을 둔다면 어떻게 구현해야 할 지 연습해보고 싶었다.
단방향 플로우를 적용한 mvvm 구조
먼저 단방향 플로우를 적용한 mvvm 구조를 만들기 위해 뷰모델을 만들고 input과 output 이라는 상태를 가지도록 했다.
그리고 input 과 output 모두 subject들로 구성했는데, 멘토님으로부터 subject는 입출력이 모두 가능하기 때문에 단방향으로 구현하려면 어떻게 해야할지 생각해보라는 리뷰를 받았었다.
subejct로 구현하면 뷰모델 내에서 input에 값을 send 하거나 output에 다이렉트로 값을 send할 수 있다. 당장은 오히려 편할 수 있지만, 장기적으론 input에 접근이 가능한 어디서든 이벤트가 꽂힐 수 있기 때문에 사이드 이펙트로부터 안전하지 못한 환경이 된다. 실제로 프로젝트를 하며 경험하기도 했다..
그렇게 input과 output의 구성을 모두 퍼블리셔로 바꾸었고, 뷰모델에서 input과 output의 상태를 제거했다. transform 함수의 매개변수로 input을 받고, 원하는 가공과 변환을 통해 output 으로 방출되는 구조로 구현했다.
이 과정에서 UIKit 뷰 클래스들에서 발생하는 이벤트들을 Combine Publisher로 전달하기 위해 커스텀이 필요했다. SwiftUI 의 뷰 클래스들은 자체적으로 지원을 해주면서 UIKit에는 안 만들어준게 미웠다.
뷰모델 간 이벤트 소통 문제
그리고 또 하나의 문제는 이벤트를 소통해야하는 문제가 있었다.
예를 들어, 커스텀 탭바 컨트롤러와 그에 맞는 화면을 보여주는 컨텐트 뷰컨트롤러가 있다고 하자.
그러면 MVVM 구조에 따라 각각 탭바 뷰모델과 컨텐트 뷰모델이 생길 것이다.
이 때 탭바 뷰모델에서 탭이 변경되었다는 이벤트가 발생하면, 컨텐트 뷰모델에게 알려주어야 처리를 하고 컨텐트 화면을 변경할 것이다.
하지만 뷰모델이 서로를 알 방법은 없기 때문에, 방법을 선택해야 했다.
첫번째 선택지는 상위 레이어를 옵저빙하는 방법이었다.
뷰모델의 상위 레이어는 모델이다.
탭이 변경되었다는 이벤트가 발생하면, 모델을 변경하고
컨텐트 뷰모델이 모델의 상태를 옵저빙하도록 하면될 것이다.
하지만 이러면 뷰모델은 모델에 대해 의존하게 되기 때문에 결합도가 높아진다.
코드 재사용성을 높이고 테스트 가능성을 높이기 위해 프레젠테이션 레이어와 도메인 레이어는 확실하게 분리가 되어야 한다고 생각해 이 방법은 배제했다.
두번째 선택지는 소통을 위한 서비스 객체를 따로 두는 것이었다.
아이디어를 착안한건 리액터킷 라이브러리였다.
개발을 하다보면 항상 정해진 인풋을 통해서만 아웃풋이 나오는게 아니다.
외부에서도 인풋이 발생할 수 있다.
리액터킷을 사용하면서 가장 흥미로웠던 것 중 하나는 이러한 문제도 해결방법을 두었다는 것이었다.
외부에서 발생하는 사이드 이펙트들을 스트림 객체로 만들어두고 인풋과 아웃풋 사이에 결합한다.
func transform(action: Observable<Action>) -> Observable<Action>
func transform(mutation: Observable<Mutation>) -> Observable<Mutation>
func transform(state: Observable<State>) -> Observable<State>
리액터킷에선 transform 이란 함수를 통해 원하는 타이밍에 사이드이펙트를 결합할 수 있도록 했다.
다시 내 문제로 돌아와보자.
탭바 뷰모델과 컨텐트 뷰모델 사이에 공유해야 하는 이벤트들의 스트림 객체를 담는 서비스 객체를 만들고 각 뷰모델이 동일한 서비스 객체를 의존하도록 했다.
그리고 서비스 객체를 추상화하여 뷰모델과 서비스 간의 결합도를 낮추었다.
서비스 복잡성 문제
하지만 하나의 서비스를 두고 두개의 뷰모델이 서로 이벤트를 주고 받도록 하다보니 스트림 객체들을 subject로 선언할 수 밖에 없었고, 탭바와 컨텐트 모두 접근해 이벤트를 send할 수 있는 구조였다.
이 subject가 탭바에서 컨텐트로 보내는건지, 컨텐트에서 탭바로 보내는건지 헷갈리기 시작했다.
의존하는 곳이 두군데밖에 되지 않는데도 이렇게 헷갈릴 정도면 휴먼 에러가 발생하기 너무 높은 구조였다.
(이쯤에서 리액터킷을 만드신 수열님을 숭배하기 시작했다..)
그리고 뷰모델마다 서비스 객체를 따로 두기로 했다.
각각의 뷰모델은 자신이 외부로부터 수신해야하는 사이드이펙트 이벤트가 정해져있다.
예를 들어 컨텐트 뷰모델의 경우, 보내는 곳이 어딘지는 몰라도 탭이 변경되었다는 사이드 이펙트 이벤트를 수신해야 한다.
그래서 각각의 뷰모델이 각자에 맞는 사이드 이펙트를 수신할 수 있도록 각각의 서비스 객체에 의존하도록 했다.
다시 돌아온 이벤트 소통 문제
하지만 이렇게 하면 첫 문제와 비슷한 상황이 다시 발생했다.
탭바 뷰모델에서 컨텐트 서비스에게 이벤트를 전달할 수가 없었다.
컨텐트 뷰모델에서도 탭바 서비스에게 이벤트를 전달할 수가 없다.
뷰모델과 서비스가 공유하는 객체가 있어야 했다.
그렇다고 또다른 객체를 만들어 뷰모델과 서비스가 의존하게 하고 싶진 않았다.
그래서 뷰모델과 서비스가 공유할 수 있는 객체가 무엇이 있을까 생각했다.
그리고 떠올린 건 노티피케이션 센터였다.
노티피케이션 센터는 어디서든 이벤트를 보내고 받을 수 있기 때문에 새로운 객체를 만들지 않아도 되었다.
끝나지 않는 문제
하지만 노티피케이션 센터는 싱글톤이고 어디서든 접근이 가능하다는 문제가 있다.
내가 하고 싶은 건 원하는 뷰모델끼리만 소통하는 것인데, 노티피케이션 센터를 사용하면 원하지 않는 위치에서도 이벤트를 전송할 수 있다는 문제가 생긴다.
그리고 서비스에서 노티피케이션 센터에 대해 의존성이 생긴다는 문제가 있다.
이러면 서비스는 어떻게 테스트를 해야할까..
KPT
Keep (계속 이어갔으면 하는 부분)
- 코드나 구조에 대해 계속해서 고민하고 고민한 부분을 글로 작성해 공유하는 게 좋은 습관인 것 같다. 앞으로도 이어가야겠다.
- 글을 작성하는 습관이 정말 중요한 것 같다. 머릿속에서만 돌아다니던 내용들을 글로 적는 과정을 통해 정리가 되고, 더 오래 기억할 수 있는 방법인 것 같다.
Problem(개선이 필요한 부분)
- 학습 정리를 너무 못했던 것 같다. 다른 동료들의 구현량을 보며 구현에만 몰두하느라 학습 정리를 많이 못했다.
Try(시도해야할 부분)
- 단순히 요구사항을 구현만 하는건 노동일 수 있다. 새로운 방식, 객체지향, SOLID 원칙 등을 적용해 어제보다 나은 코드로 구현하는 걸 목표로 하자.
- 구현도 중요하지만 학습의 비중을 늘리자. 스스로를 돌아봤을 때 현 시점에서 구현보다 학습을 통해 얻는 게 더 많다면 학습을 하고 글로 정리하며 단단한 지식을 만들자.
'부스트캠프' 카테고리의 다른 글
네이버 부스트캠프 웹・모바일 9기 멤버십 과정 2차 학습스프린트 회고 (2) | 2024.10.19 |
---|---|
UIScene, UIWindowScene, UIWindow (1) | 2024.08.22 |
IBOutlet과 IBAction (0) | 2024.08.21 |
View(ViewController) Life Cycle (0) | 2024.08.21 |
네이버 부스트캠프 웹・모바일 9기 챌린지 과정을 마치며 (0) | 2024.08.11 |