프로젝트 소개
JOYING: 세상의 모든 OTT 컨텐츠를 한 곳에 보여조잉!
개발 기간: 2023.06.28 ~ 2023.07.24
코드스테이츠 프론트엔드 부트캠프 메인 프로젝트를 마무리하며 KPT 방법론에 따라 프로젝트를 회고해보았습니다.
이 프로젝트에서 담당했던 부분은 다음과 같습니다.
- 헤더
- 메인 페이지 슬라이더
- 멤버 마이페이지
- 로그인/회원가입
- 후기
- 404, 500 오류 페이지
- 검색어 자동완성
- 모바일 GNB
- 액세스, 리프레시 토큰 저장과 후속 처리
- top 버튼
keep
스프린트 기반 플래닝
애자일 조직 운영 방법론 중 스프린트에 기반해 프로젝트를 운영했다. 약 4주의 프로젝트 기간을 네 개의 스프린트로 나누고 다음과 같이 각 스프린트의 목표를 정했다.
- 스프린트 1: 팀 빌딩 회의, 디자인과 기획 확정, 초기 세팅
- 스프린트 2: 기본 CRUD 완성
- 스프린트 3: advanced 과제 달성, 로딩과 에러 처리, 성능 최적화
- 스프린트 4: 산출물 정리와 데모데이 준비
스프린트 1은 일주일이 채 되지 않았기 때문에 그렇게 여유 있는 일정은 아니었고, 사실상 2주 만에 개발을 마치는 플랜이었기 때문에 상당히 빡빡한 목표였다. 그렇지만 초기에 이렇게 스프린트의 목표를 분명하게 정하고 시작했기 때문에 팀원들이 각 스프린트의 목표에 집중해서 스프린트의 기본 취지처럼 단거리 달리기를 하듯 작업을 해 나갈 수 있었다고 생각한다. 실제로도 스프린트 2를 마무리하며 진행한 개발자 테스트에서 advanced 과제 포함 64%의 패스율을 기록했으니 아주 터무니 없는 목표는 아니었다고 볼 수도 있겠다.
스프린트 운영을 하면서 데일리 스크럼을 하고 스프린트 첫날에 스프린트 플래닝과 스프린트 마지막 날에 스프린트 회고를 한 것도 스프린트 목표를 달성하는 데 도움이 됐다. 데일리 스크럼을 통해서 매일 팀 전체의 진행상황을 파악할 수 있었고, 진행하면서 어려웠던 점을 하나씩 말하도록 정해두어서 어려운 부분에 대해 서로 조언해주거나 함께 코딩하는 식으로 도움을 줄 수 있었다.
스프린트 회고는 KPT 회고법으로 진행했는데, 스프린트 3에서 내가 조급한 마음 때문에 스프린트 목표에 정해져 있지 않던 작업을 팀원에게 맡기는 일이 있었고 이 점을 problem으로 돌아보며 다음 스프린트에서 문제점을 수정하는 경험을 할 수 있었던 것이 좋았다.
팀 내 활발한 소통과 원만한 갈등 조정 과정
우리 팀은 비교적 소통이 잘 되고 갈등이 있더라도 잘 해결된 팀이었다고 생각한다. 스프린트 운영을 하면서 데일리 스크럼으로 상시적으로 진행상황을 서로 파악했던 것, 백엔드 팀이 줌에서 상주하면서 작업했던 것, 백엔드 상주 줌에 내가 팀장으로서 자주 함께 상주하면서 소통했던 것 등이 소통을 원활하게 하는 데 도움이 됐다.
갈등이 있었다고 한다면, 초기에 api가 빠르게 완성되지 않아서 프론트-백 간의 약간의 마찰이 있었던 적이 있었다. ott 데이터를 크롤링 과정에서 너무 많은 데이터를 수집하려 하니 케이스별 분기가 너무 많이 생기고, 그것 때문에 크롤링에 에러가 자주 생기면서 데이터베이스를 완성하는 데 어려움이 있었다. 크롤링이 완료되지 않은 상태에서 api를 완성할 수가 없었기 때문에 api 작업이 늦어졌고, 그러다 보니 프론트 팀원들은 맡은 기능을 완성하기가 어려워서 점차 불만을 가지게 됐다.
이런 상황에서 나는 정해진 작업 일정에 맞춰 프로젝트를 완성하기 위해 제공하는 데이터의 양을 줄이더라도 크롤링이 먼저 완료돼야 한다고 생각했다. 그래서 먼저 팀원들의 의견을 수렴해 수집하는 데이터의 개수를 줄였다. 영화 감독과 시청 가능 연령 같은 데이터를 수집하지 않기로 결정해서 크롤링 작업에 분기를 줄이고 좀 더 빠르게 완료할 수 있었다. 이런 식으로 백엔드 팀에게 가해지는 부하를 줄이니 백엔드 팀원들의 불만을 해결할 수 있었다.
프론트 팀원들에게는 api 완성이 늦어지는 이유에 대해서 충분히 납득할 때까지 설명하고, api가 나오기 전까지 작업을 할 수 있도록 더미 데이터를 작성하고 json server를 이용해 작업을 먼저 해둘 수 있게 조치를 취해 두었다. 이렇게 양측이 서로 만족하는 해결책을 찾는 과정이 나에게는 팀장으로서 해볼 수 있었던 소중한 경험이었다.
성능 최적화에 대한 고민
프로젝트를 진행하는 내내 성능 최적화에 대한 고민을 놓지 않았다. 특히 우리 프로젝트 특성상 이미지를 아주 많이 사용해야 했기 때문에 성능에 대한 고민이 필수적이었다. 성능 최적화를 위해서는 다음과 같은 조치들을 했다.
- 이미지는 되도록이면 webp로 사용, png는 쓰지 않는다.
- 이미지를 클라이언트 asset으로 보유하지 않고 s3 데이터베이스에 넣어 두고 링크로 활용.
- 무한스크롤을 활용한 레이지 로딩
- 리액트 쿼리의 캐싱 기능 활용
이중 리액트 쿼리의 캐싱 기능을 활용을 하기 위해서 cacheTime과 staleTime을 디폴트로 Infinity로 설정하고 필요한 부분에서 쿼리를 invalidate 시켜주는 식으로 해두었다. 정보 제공의 목적이 강하고 유저들의 데이터 업데이트 상호작용이 많지 않다는 프로젝트 특성상 이런 조치들이 적절했다고 생각한다.
ux 고민
이전까지 진행했던 솔로 프로젝트나 프리 프로젝트에서는 ux에 관한 고민이 크지 않았다. 누굴 보여준다는 생각을 거의 하지 않고 그냥 어떤 기능을 구현하는 것 자체에 의미를 두고 작업했기 때문이다. 그런데 꽤나 많은 사람들이 우리 프로젝트를 보게 될 것을 생각하니 ux에 관한 고민을 하지 않을 수 없었다. ux 향상을 위해 취한 조치들은 다음과 같은 것들이 있다.
- 회원가입 직후 로그인되도록 처리
팀원 중 한명은 이런 장치를 안 해두면 마치 첫 인사에서 반말을 하는 것과 같은 기분이라는 비유를 했는데 참 재밌고 적절한 비유인 것 같다.
- 유저의 상호작용이 있는 요소는 호버 효과
호버 효과는 코드 짜는 입장에서는 꽤나 성가시고 하나 안 하나 차이가 있나 싶지만 유저의 입장에서는 은근하지만 꽤나 강력한 ux향상을 준다고 생각한다.
- 라우트 보호
리액트 라우터의 loader를 사용해 로그인 상태에서는 접근되어서는 안되는 로그인/회원가입 페이지, 로그아웃 상태에서는 접근이 허용되지 않는 마이페이지는 접근이 안되도록 처리했다. 이 주제에 관해서는 기술 발표에서도 소개했다.
- 토스트창을 활용한 상호작용 피드백 띄우기
유저가 데이터 업데이트 상호작용 이후에 처리 결과가 눈에 보이지 않는 경우 토스트창을 활용해 피드백을 띄웠다(회원가입, 회원 탈퇴, 선호도 수정 등)
- 검색창 자동완성에서 키보드와 마우스 모두 사용해 자동완성 결과로 이동할 수 있도록 함
백엔드 멘토께서 이 기능을 보고서 "디테일이 명품이다"라는 평을 해주셨다 해서 기분이 아주 좋았다.
- 프로필 이미지 업데이트 시 파일 선택과 드래그앤드롭 모두 지원
- 클릭할 수 있는 요소는 cursor: pointer
- 로그인 상태에서 이용할 수 없지만 화면에 보이게 되는 요소들은 diabled 하거나 클릭했을 때 토스트 안내창 띄우기
그밖에도 ux를 생각해서 한 조치들이 많고 사실 성능 최적화도 ux 향상을 위한 조치 중 일부이기도 하다. ux 향상을 위한 조치들을 하면서 느낀 것이 코드를 짜는 입장에서는 귀찮게 여겨지는 일에 공을 들일수록 ux는 향상된다는 것이다. 유저가 언제나 우리 생각대로 행동하는 것은 아니고 사실 그렇게 하지 않는 경우가 생각보다 아주 많을 것이기 때문에 ux 향상을 위해서 여러 엣지케이스에 대해서도 항상 고민해야겠다.
problem
스프린트 목표를 추상적으로, 양적 근거 없이 정한 것
스프린트를 짜서 프로젝트를 운영한 것은 좋았지만 스프린트의 목표는 추상적이었고, 객관적이고 양적인 근거가 없었다. 예를 들면 스프린트 2에 기본 crud를 완성하자고 했었는데 '기본' crud가 어디까지이고 좀 더 나아간(?) crud는 무엇인지에 대한 기준이 분명하지 않았다. 2주차 멘토링에서 스프린트 운영에 관해서 멘토님이 조언을 해주셨는데, 실제로 스프린트를 운영할 때는 각 이슈마다 estimate 점수를 매기고 한 스프린트마다 목표 점수를 둬서 달성률을 따진다고 한다. 이런 식으로 하면 좀 더 목표가 분명해지고 나중에 스프린트 평가를 할 때도 모두가 인정할 수 있는 객관적인 달성률 통계를 낼 수 있을 것 같다.
코드 품질 문제
스프린트의 목표가 추상적이었던 것과 별개로 목표를 일부러 약간 높게 잡았는데, 그것이 스프린트의 기본 취지에 맞는 것이어서이기도 했지만 일단 주어진 시간 자체가 목표했던 기능들을 구현하기에는 좀 빡빡하기도 했다. 사실상 2주 안에 개발을 마쳐야 하는 상황이어서 기능을 전부 돌아가게만 만들어도 잘한 거라 생각이 들었는데, 그러다 보니 코드 품질을 신경쓸 겨를이 없었다. 나나 다른 팀원들 모두 중복된 코드가 많았고, 컨벤션이 지켜지지 않는 부분도 있었으며, 네이밍이 직관적이지 않고, 파일이나 폴더 구조도 엉망이었다. 멘토님께서는 코드 품질만큼이나 제때 납기하는 것이 프로그래머의 중요한 자질이라 하셨지만, 어쨌든 문제는 문제이니까...
최적화에 대한 고민이 충분히 나아가지 못했다
성능 최적화에 대한 고민을 계속 했었지만, 부트캠프 내 릴리즈 이후에 성능에 관한 개선 요청이 가장 많았다. 아이템 카드의 로딩 속도가 느리다는 것이었는데, 아이템 카드로 사용하기에는 너무 화질이 좋고 용량이 큰 이미지를 사용했던 것이 가장 큰 원인이었다. 사실 조금만 고민했다면 알 수 있었던 지점이었는데 릴리즈 이후에야 문제 파악을 하게 되어서 아쉽다. 이걸 고치려면 크롤링을 다시 하고 db를 다 날려야 했기에 릴리즈 이후에는 선뜻 고치기가 어려웠다.
리프레시 토큰 보안 문제
이건 개인적으로 아쉬운 문제인데, 나 혼자서 해결하기는 어려워서 조금 조심스럽기도 하다. 이번 프로젝트에서는 리프레시 토큰을 이용해서 액세스토큰의 기한을 연장해주는 기능을 처음으로 구현해 봤다. 그런데 이 토큰들을 가장 안전하게 처리하려면 액세스토큰은 메모리에, 리프레시 토큰은 http only 쿠키에 저장하도록 하는 것이 좋다고 알려져 있다. 이렇게 하려면 서버에서도 쿠키로 리프레시토큰을 받아서 연장 처리를 해줘야 하며, 액세스토큰 만료 시점이 지나지 않아도 언제든 클라이언트에서 액세스토큰이 유실될 경우(새로고침 등의 작동으로) 리프레시토큰 연장을 해줘야 한다. 이렇게 구현하는 데는 연구가 더 필요했기 때문에 아쉽게도 이번 프로젝트에서는 구현을 하지 못했다. 지금은 액세스토큰과 리프레시토큰 모두 로컬 스토리지에 저장하는 식으로 구현되어 있는데, 기회가 된다면 다른 방식도 적용해 보고 싶다.
try
관심사에 따른 폴더구조 정리
지금의 코드 품질 문제에서 가장 심각한 것은 폴더 구조가 엉망이라는 점이라고 생각한다. component 폴더가 가장 엉망인데, 폴더 구조를 나눈 데 기준이 다 달라서 다른 사람의 코드를 고칠 때 어떤 파일에 원하는 코드가 들어있는지 찾기가 어렵다. 도메인별, 관심사별로 폴더 구조를 나누고 기존에 한데 모아뒀던 api 인스턴스도 각각의 폴더에 맞게 나눠 두면 훨씬 더 깔끔한 폴더 구조가 될 것 같다.
코드의 재사용성 향상과 커스텀훅화
중복된 코드가 많다는 것을 알면서도 일단 구현이 급해서 충분히 고민하지 않고 같은 코드를 복붙하곤 했는데 이것을 꼭 고치고 싶다. api 요청 관련한 부분을 커스텀훅화 해볼 수 있겠다는 멘토님의 조언이 있었는데 이것을 포함해서 여러 중복되는 로직들을 커스텀훅화하고 코드를 제대로 다이어트시켜 보려고 한다.
테스트코드 적용
테스트코드 주도 개발(TDD)는 여러 장점이 있지만 유닛테스트를 도입하면 리팩토링의 두려움을 덜어준다는 것과 컴포넌트 분리의 기준을 세워준다는 장점이 있다고 한다. 프로젝트가 완성에 가까워지고 마감이 다가올수록 리팩토링을 하기가 두려워졌는데 테스트코드를 도입하면 리팩토링의 두려움을 조금 덜고 코드 재사용성 향상에도 도움이 될 것이라고 기대가 된다.
리팩토링 기록
[Refactor/ESlint] ESlint-plugin-import로 import문 순서 린팅하기
[Refactor/Architecture] 좋은 폴더구조를 찾기 위한 여정
[Refactor] 재사용 가능한 버튼 컴포넌트 만들기(styled components)
[Refactor] 디바운싱으로 불필요한 쿼리와 리렌더링 방지하기
[Refactor] 리액트 쿼리 커스텀훅 만들기
'프로젝트 > SEB 44 main-project' 카테고리의 다른 글
[Refactor] 재사용 가능한 버튼 컴포넌트 만들기(styled components) (0) | 2023.08.16 |
---|---|
[Refactor/Architecture] 좋은 폴더구조를 찾기 위한 여정 (0) | 2023.08.07 |
[Refactor/ESlint] ESlint-plugin-import로 import문 순서 린팅하기 (0) | 2023.08.01 |
[React-Query] enabled: false로 남은 쿼리는 isLoading 상태다 (0) | 2023.07.24 |
flex.. 너는 까도 까도 계속 나오는 양파 같다 (0) | 2023.07.19 |