눈 깜짝할 사이에 우아한테크코스(이하 우테코) 레벨 3 교육 과정이 마무리되었다.
6월 6일, 마지막 포스팅 날짜를 보면 거의 2달 반 동안 포스팅을 작성할 시간이 부족했음을 알 수 있다.
(이에 대한 원인은 아래에서 다룰 것이다.)
레벨 3 교육 과정을 아래에 보이는 기승전결 그래프로 요약할 수 있다.
그래프에서 보이는 것처럼, 문제 발생으로 우리 팀은 다른 팀에 비해 부족한 시간을 가지게 되었다.
지금부터 커디 프로젝트에 대해 기승전결로 풀어나갈 것이다.
기(起) : 화목한(?) 프로젝트의 시작
우테코는 레벨3 교육 과정에서 이전 단계에서 배운 지식들을 바탕으로 팀 프로젝트를 진행한다.
여러 크루들은 레벨 2 교육이 종료되기 전에 아이디어를 제출하며,
선정된 아이디어에 따라 크루들을 랜덤하게 배치하는 구조이다.
우리 팀은 백엔드 4명, 안드로이드 3명으로 구성되었다.
처음 주제는 무신사의 제품 가격 변동을 추적하는 서비스를 제공하는 것이었다.
주요 문제 해결 목표는 다음과 같았다.
1. 무신사에서 할인 제품이 실제로 할인 제품인지 확인하기. (할인으로 표기된 제품이 실제로 할인되었는지)
2. 구매를 고려하는 상품이 미래에 할인될 가능성이 있는지, 과거 가격 추이를 확인하고 싶었다.
솔직히 말하면, 아이디어는 좋았지만 무신사 대신 쿠팡이나 11번가와 같이 여러 상품 가격을 비교할 수 있는 플랫폼을 타겟으로 하고 싶었다.
사용자 입장에서 여러 상품 가격을 비교하고 저렴한 제품을 찾을 수 있는 서비스가 더 관심을 끌 것이라 생각했기 때문이었다.
하지만, 팀 내에서 무신사를 선호하는 멤버들은 무신사 사용자도 충분히 많을 것이라고 판단했다.
내가 무신사를 사용하지 않아서 다른 서비스가 더 효율적일 것이라고 착각한 것이었다.
이런 주제 선정 과정에서도 많은 교훈을 얻을 수 있었다.
사람들마다 생각이 다르다는 것을 알면서도, 내 생각이 다른 사용자의 입장일 것이라고 착각하는 사고방식을 반성할 수 있었기 때문이다.
주제와 타겟 플랫폼이 결정되었고, 구체적인 기능도 정의되었다.
시간이 흘러 데모데이가 되었고, 아이디어를 제시하신 분께서 발표를 도맡아 하셨다.
주요 질문 중 하나는 다음과 같았다.
가격 추이를 제공하기 위해 데이터를 어떻게 수집할 계획인가요?
이 질문을 받기 전부터 팀에서 해당 문제에 대한 해결책을 찾는 것이 큰 고민이었다.
무신사는 별도로 API를 제공해주지 않기 때문에, 여러 고민 끝에 크롤링을 택했었다.
처음에는 모든 상품을 크롤링하려 했지만 데이터가 너무 방대했다. 상품 카테고리만 10개가 넘었고, 그중에서도 상의 제품만으로도 20만 개가 넘어갔다.
이로 인해 특정 카테고리에서만 우선적으로 크롤링을 시작했다.
무신사는 상품 가격 변동이나 신제품 출시 시 업데이트 카테고리에서 변경 내용을 확인할 수 있도록 제공한다.
그래서 처음에는 모든 데이터를 수집한 다음, 업데이트 카테고리에서 변경된 내용만 업데이트할 계획이었다.
팀 내에서 충분히 고민한 내용을 질문으로 받았기 때문에, 적절하게 잘 답변할 수 있었다.
승(承) : 크롤링이 굴린 스노우볼..
그러나 크롤링에서 문제가 발생했다.
크롤링은 무단으로 데이터를 수집하는 방식이기 때문에
코치님들은 레벨 3 진행 중에 크롤링으로 인한 문제 발생 가능성을 최소화하려 했다.
결론적으로, 우테코 내에서 데이터 무단 수집을 금지하였다.
사실 크롤링에 대해 처음부터 걱정하지 않은 것은 아니었다.
그렇기에 1차 데모데이(2주 차) 직전까지도 거의 코치님들 한 분 한 분 찾아가며 크롤링 사용이 괜찮은지 여쭙기도 하였다.
대부분의 답변은 실제 사용자가 거의 없는 시점에서 너무 깊이 걱정하지 않아도 된다는 내용이었다.
심지어 데모데이 직후에도 별다른 이야기가 나오지 않았다.
하지만, 3주 차에 갑자기 크롤링과 관련한 공지가 발표되었다.
요약하자면, 데이터 수집을 무단으로 금지한다는 내용이었다.
솔직히 조금은 당황스러웠다.
이미 프로젝트 방향과 기능을 거의 마무리한 시점에 갑작스러운 주제 변경을 해야 했기 때문이다.
이미 우테코 레벨 3교육이 시작된 지 1/4이 지났던 때였다.
그 결과, 우리 팀은 다른 팀보다 프로젝트 시작이 2주 늦어지게 되었다.
API를 제공하는 여러 서비스를 검토했지만, 쿠팡은 이미 많은 Price Tracking 서비스가 존재하고 있었고,
그 외에 농수산물 경매, 도매가격 API와 같은 대안은 우리가 해결하고자 했던 문제 방향과는 거리가 멀었다.
(나중에 코치님에게 들어보니, 농수산물 데이터 사용에는 가격 불일치와 업데이트 지연과 같은 문제가 있었다고 한다)
결국 새로운 주제로 방향을 잡아야 했고, 팀의 분위기는 점점 바닥을 쳤다.
특히 아이디어를 제출한 팀원은 의욕을 많이 상실하여, 거의 일주일간 대화하는 모습을 거의 본 적이 없었다.
전(轉) : 새로운 주제 선정, 처음부터 다시 시작
1차 데모데이에서의 노력이 헛되게 되었으니,
코드 컨벤션, Git 컨벤션처럼 기획과는 별개의 내용을 제외하고 4주분의 작업을 2주 안에 마쳐야 했다.
팀은 이전보다 훨씬 바쁘게 움직였다.
우리는 다시 처음으로 돌아가 주제를 새롭게 선정했고, 개발자들에게 필요한 서비스를 만들어보기로 결정했다.
개발자들을 하나로 잇다.
이름하여, 커디(Connect Developer)이다.
이 서비스는 사용자에게 컨퍼런스와 대회 정보를 제공하며, 개발자들이 함께 컨퍼런스에 참여하고 싶을 때 다른 개발자와 함께 할 수 있는 기회를 제공한다.
컨퍼런스 정보를 제공하는 서비스는 많지만, 함께 가기를 원하는 요청 기능을 갖춘 서비스는 없어서, 다른 서비스와 큰 차별성을 가지고 있다고 생각한다.
커디의 기대효과는 다음과 같다.
1. 본인은 안드로이드 개발자이고, 주변에 백엔드나 iOS 개발자가 없을 때.
2. 비전공자 출신이어서 개발자들을 잘 모르며 혼자 컨퍼런스에 가기가 어려울 때.
3. 같은 조직(e.g. 우테코)에 속한 개발자와 함께 컨퍼런스에 참여하고 싶을 때.
과연 모르는 사람들과 함께 컨퍼런스에 가려고 할까?
실제로 처음에 크롤링만큼이나 걱정했던 점이었다.
하지만 기대효과를 정리하니 충분히 수요가 있는 서비스임을 확인할 수 있었다.
모르는 사람들과 커피챗도 하는 시대에서, 비슷한 관심사를 가진 개발자들과 함께 컨퍼런스에 가고 싶어 하는 사용자도 충분히 있을 것이라고 판단했기 때문이다.
기대 효과 중에서도 특히 3번 목표가 매력적으로 다가왔다.
예를 들어, 우테코 선배 중에 같은 분야 개발자와 함께 컨퍼런스 또는 대회에 참여하고 싶은 경우가 많을 것이라고 생각하며, 팀원들도 동의했다.
새로운 주제와 목표 역시 Price Tracker만큼이나 마음에 들었다.
기능 명세, 와이어 프레임, 사용자 스토리 작성, CI/CD 구축, Firebase 베타 테스트 환경 구축 등 할 일이 많았지만,
축 쳐진 처음의 분위기와 다르게, 모두 우리의 처음 동기와 설레는 마음으로 작업을 시작했다.
결(結) : 우테코와 커디는 행복하게 살았다고 한다.
시간이 어떻게 흘렀는지 모를 만큼, 다들 요구사항과 기능 개발에만 몰두했다.
마지막 데모데이가 다가온 시점에는, 눈을 떴을 때 제외하고는 안드로이드 스튜디오와 팀원들과 함께한 시간뿐이었다.
다양한 기능들을 기획했으며, 검색을 제외하고는 레벨 3에서 목표한 기능 요구사항을 모두 완성해냈다.
컨퍼런스 함께 가요, 댓글/대댓글, FCM 푸시 알림, 컨퍼런스/대회 조회 및 필터링, 프로필(이름, 관심 직무, 동아리 등) 등을 5주 안에 모두 구현한 커디(Kerdy) 팀이 자랑스러웠다.
결론적으로, 기한 내에 앱을 성공적으로 Play 스토어에 출시하였다.
그 순간만큼은 정말로 뿌듯하고 감격스러웠다.
잘한 점
1. 안드로이드에서의 Domain 모델 사용 여부에 대한 고민
프로젝트 도중 안드로이드에서 도메인 모델의 필요성에 대한 깊은 고민을 하였다.
이전 레벨의 교육에서 처음 들어본 도메인 개념을 바탕으로 미션에 도메인 모델을 추가하여 각종 비즈니스 로직을 처리하였다.
그러나 레벨 3에서는 서버에서 검증을 처리하거나 UI에서 간단한 작업으로 해결할 수 있는 것들이 많아지면서,
안드로이드에서 도메인 모델의 필요성에 대한 의문이 들었다.
예를 들어, 이전 미션들에서는 사용자 이름에 대해 UserName라는 도메인 모델을 만들었다면,
init {...} 초기화 블록에서 사용자명은 특정 길이를 넘지 못하거나, 특수 문자를 사용할 수 없다 등의 비즈니스에 종속적인 자료구조를 만들곤 하였다.
하지만 레벨 3에서는 이러한 모든 검증 과정은 서버에서 처리해 주었고,
심지어 위의 예시와 같은 상황도 도메인 모델이 아닌, EditText의 maxLength를 설정해서 해결할 수도 있었다.
또한, 특수문자와 같은 검증 로직은 Domain 모델이 아닌 ViewModel에서 작업해도 충분하다고 생각했다.
다른 팀은 어떻게 했을지 궁금해서 찾아보니,
Domain 모델은 만들어 놓았지만, 결국 DTO(클라이언트 <-> 서버)와 1 : 1 관계이거나, 아무런 기능을 하지 않았다.
또는 어떠한 검증 로직을 정의해 두어 예외가 발생하게끔 하였지만, 아래와 같이 코드가 복잡해지거나 조금은 어색해 보이는 문제가 있었다.
class xxxViewModel : ViewModel() {
...
fun changeName(name: NameUiModel) {
...
runCatching {
name.toDomain() // ..?
}.onSuccess {
...
}.onFailure {
...
}
}
}
만약 백엔드 개발자 입장이라면, 도메인 모델이 어떠한 관계를 가지는지 충분히 고민할 필요가 있어 보였다.
e.g. 컨퍼런스와 스크랩한 컨퍼런스 모델은 같은가?
e.g. 만약 같다면 컨퍼런스가 스크랩 날짜를 가지고 있는 사실이 정말로 유지보수에 유연한가? / 자연스러운가?
e.g. 또 다른 컨퍼런스가 추가된다면 또다시 속성이 추가되어야 하는 것 아닌가? OCP에 대해 고민해 보았는가?
하지만 안드로이드 개발자 입장에서 다음과 같은 상황을 제외하고,
Domain 모델이라는 개념이 과연 필요할까?라는 의문이 들기 시작했다.
1. 서버에서 검증 로직을 처리해주지 못하는 Firebase를 사용한다.
2. 서버 없이 로컬에서 모든 것을 처리하는 서비스이다.
물론 서버가 있어도 가벼운 로직은 클라이언트에서 한 번 더 검증해 주는 것이 나쁘다고 생각하지는 않는다.
만약 프로젝트 규모가 커져서(기업 규모), 개발자가 실수로 비즈니스 로직을 다르게 구현하는 상황을 방지하기 위함이라면 충분히 필요성을 느낄 것이다.
e.g. 정해진 이름의 길이는 8자인데, A 개발자는 7자 / B개발자는 8자로 설정할 수 있는 가능성 감소
하지만 프로젝트 규모가 작고, View 위젯에서 간단히 설정해주기만 하면 되는 코드까지 도메인 모델을 만드는 작업이 지금 당장은 오버 엔지니어링이라고 생각했다.
결론적으로, 왜 도메인 모델을 적용했냐고 물었을 때 당당하게 말할 수 없을 것 같았다.
서버에서는 결국 도메인 모델을 반환해 주는 것이 아닌,
도메인 모델들을 특정 UI에서 필요한 값들로 가공하고 조합하여 하나로 만들어진 DTO를 반환해 주기 때문에,
DTO만으로 서버처럼 도메인 모델을 재구성하는 것이 애당초 불가능하기도 했다.
그럼에도 도메인 모델을 적용했다면 다음과 같은 구조가 되었을 것이다.
(클라이언트) UI <-> Domain <-> Data < -HTTP-> (서버) Domain
결국 커디팀은 과연 안드로이드에서 도메인이라는 명칭을 사용하는 것이 올바를까? 라는 고민을 했고,
도메인 모델이 아닌, 데이터 모델이라는 개념을 만들어 사용하기로 결정했다.
데이터 모델은 Remote(서버)와 Local(Room 등..)에서 타입이 다른 경우 통일화 시켜주기 위한 모델이다.
실제로 대중화된 개념인지는 모르겠지만, 커디 팀에서는 데이터 모델이라는 명칭을 만들어서 사용 중이다.
이번 프로젝트를 하면서 가장 고민을 많이 하고, 안드로이드 팀원들과 수차례 토론하며 강조했던 내용이라 글이 조금 길어졌다.
(아마 따로 포스팅을 하고 싶을 정도로..?)
해당 내용을 잘한 점에 추가한 이유는,
결론이 어떻게 정해졌든, 안드로이드에서의 도메인이라는 개념이 필요한가에 대해 깊게 고민해 볼 수 있었기 때문이다.
2. 문서와 함께 기술 파헤치기
우테코를 시작하면서 어떤 기술을 적용하려고 할 때, 공식 문서와 포스팅을 함께 보는 습관을 들이게 되었다.
때문에, 이번 프로젝트에서 FCM, 깃허브 로그인을 구현하면서
원래 같았으면 빠르게 구현하기 위해 포스팅만 보고 잘 모르고 사용했던 기술들도 조금은 깊이 있게 공부할 수 있었다.
포스팅만 보고 구현하면, 지금 당장 기능 자체는 빠르게 구현할 수 있지만, 그 과정에서 어떤 일이 일어나는지 알 수 없다.
그렇게 되면 문제가 발생했을 때 무엇이 문제일지 쉽게 추측하기가 어렵다.
예를 들어, FCM에서는 백그라운드와 포그라운드 상태, 그리고 RemoteMessage 형태에 따라 동작하는 방식이 다르다.
백그라운드에서 notification이 포함된 RemoteMessage를 전달받으면 자동으로 푸시 알림을 보여주지만,
onMessageReceived() 메서드는 호출되지 않는다.
이러한 사실을 제대로 공부하지 않았다면,
백엔드 개발자분에게 아무런 형태로 데이터를 보내줘도 괜찮다고 했을 것이고, 문제 해결에 오랜 시간을 투자했을 것이다.
이처럼 대충 사용하는 방법만 아는 것이 아니라,
문서를 통해 이론적인 내용을 공부하고, 직접 코드로 구현해 보면서 여러 상황에서 어떻게 대처해야 하는지 미리 가정하는 습관을 들이는 것이 중요하다는 사실을 다시금 느낄 수 있었다.
아쉬웠던 점
하지만 감격스러운 순간과 함께 아쉬운 부분도 분명 있었다..
1. 코드리뷰
각 데모데이 초기에는 코드리뷰가 원활하게 이루어졌지만,
아무래도 다른 팀에 비해 시간이 부족했던 탓에 이런 환경을 꾸준히 유지하지는 못했다.
코드 리뷰를 하면서 내가 놓친 부분이나 개선이 필요한 부분에 대해 다른 사람의 의견을 들을 수 있던 점이 정말 좋았다.
그렇기에 더더욱 포기하고 싶지 않았지만 시간은 우리를 기다려주지 않았다.
결국 안드로이드 팀원들과 상의한 결과, 각 PR을 Q/A 해보고, 기능 상에 문제가 없다면 우선은 approve 하는 방향으로 흘러갔다.
그리고 이후에 데모데이가 끝났을 때, 다 같이 코드를 통일화하고 개선하는 작업을 하기로 하였다.
이런 상황에 대해 조금 더 깊이 있게 고민하고 싶어 여러 문서를 찾아보니,
Ship - Show - Ask라는 PR 전략이 있었다.
요약하자면,
실제 현업에서는 배포까지 시간이 부족하고 PR에 피로감을 느끼는 사람들을 위해,
상황에 따라 적절히 사용할 수 있는 Ship, Show, Ask 3가지 옵션을 제공하는 것이다.
너무 매력적이라 자세한 내용은 레벨 4 과정에서 프로젝트 유지보수 단계에 도입하고, 그 결과를 포스팅할 예정이다.
문제점을 파악하고 개선해 나간다면 아쉬움을 조금이나마 덜어낼 수 있을 것 같다는 생각이다.
2. 조금은(?) 지저분한 코드
처음에는 최대한 코드를 깔끔하게 작성하고자 노력했지만,
배포 날짜에 쫓기다 보니 시간이 지날수록, 우선 돌아가는 쓰레기라도 만들자 라는 마인드로 임하게 되었다.
그렇다고 AAC ViewModel에서 context를 참조하거나,
데이터바인딩의 BindingAdapter를 남용하거나,
변수명을 a, b, c와 같이 난독화된 것처럼 작성하는 등의 죄악은 일절 저지르지 않았다.
다만, 레벨 1, 2에서 코드 리뷰를 받던 시절처럼
모든 함수를 깔끔하게 분리하거나,
depth가 2를 초과하지 않게끔 하거나,
더 좋은 메서드명을 고민하기 위해 시간을 많이 투자하지는 못했다.
아쉬움 때문인지 코치님께도 현업에서 이런 상황이 발생하는지 여쭤보기도 했다.
코치님께서는 다음과 같이 말씀해 주셨다.
모든 기업을 다 경험한 것은 아니지만, 예전에 일했던 부서에서 처음 코드를 봤을 때 매우 충격이었다.
소위 들으면 다 알법한 기업임에도 불구하고, 당장 서비스를 배포해야 하는 상황에서 테스트 코드를 작성하거나 코드를 깔끔하게 작성하는 상황이 쉽지는 않다.
오히려, 기업 입장에서는 코드를 깔끔하게 작성하고 테스트 코드를 작성하는 것보다,
서비스로 돈을 벌어주는 것이 좋은 서비스이고 좋은 프로젝트일 수 있다.
이 말을 듣고 조금은 위로가 되었다.
시간이 부족한 우리 팀의 상황과 결과가 코치님께서 경험하신 현업과 많이 비슷했기 때문이다.
(우리만 이런게 아니구나 하는..?)
바쁜 상황에서도 목표한 기능을 모두 구현하고, 컨벤션과 코드 형식을 모두 지키기 위해 노력한 우리 팀이 더 자랑스러워졌다.
그래도 우리는 현업이 아닌, 학습을 하는 과정이기 때문에,
안도하지 않고 레벨 4 시작 무렵에 팀원들과 다 같이 코드 리팩토링을 하며 문제를 해결해 나갈 예정이다.
3. 테스트 코드는 대체 어디?
사실 가장 아쉬운 점은 테스트 코드 작성이다.
레벨 1, 2 교육과정에서 그렇게 강조한 테스트 코드를 전혀 작성하지 못했기 때문이다.
처음 1주 차에서 컨벤션을 정할 때, 본인이 작성한 커디(Kerdy)팀의 테스트 코드 문화이다.
테스트 코드는 개발한 기능에 대한 근거가 된다고 떠들어 놓고,
정작 아무 테스트 코드도 작성하지 못했기 때문이다.
시간이 부족했기 때문인 것은 사실이지만,
테스트 코드를 작성하지 못한 것 또한 사실이기에 그 어떤 것보다 아쉬움이 컸다.
그래서 안드로이드 팀은 레벨 3에서 레벨 4로 넘어가는 방학 기간 중에,
본인이 개발한 기능에 대해 테스트 코드를 작성해 오는 것을 숙제로 정했다.
MVVM 패턴을 사용하기 때문에 ViewModel에 대한 테스트 코드를 작성해 올 예정이다.
그리고 검색 기능 등을 제외하고는 이미 만들어 놓은 기능이 충분하기 때문에,
레벨 4에서는 대규모 기능 추가보다는,
버그를 고치거나 기존 기능을 확장하면서 반드시 테스트 코드를 작성한 후에 PR을 올리는 것을 목표로 할 예정이다.
프로젝트를 마무리하며..
방학 동안 코드 리팩토링과 테스트 코드 작성, 개인적인 성장을 위한 시간을 가질 계획이다.
프로젝트에서 겪은 경험과 고민을 바탕으로 더 나은 결과를 이뤄내기 위해 노력할 것이다.
'대외활동 > 우아한테크코스' 카테고리의 다른 글
[우아한테크코스] 📚 레벨로그 - 레벨1 인터뷰 (0) | 2023.03.30 |
---|---|
[학습로그] 최신 Android에서는 왜 MVC 패턴을 사용하지 않는가? (1) | 2023.03.27 |
[우아한테크코스] 함수형 프로그래밍(Functional Programming)이란? (4) | 2023.03.08 |
[우아한테크코스] 우아한테크코스 한 달 생활기 (2) | 2023.03.05 |
[우아한테크코스] 원시값 포장과 일급컬렉션은 무엇이고, 어디까지 감싸야 할까? (1) | 2023.02.27 |