이번 회고록도, 이전 회고록처럼, 영국 에든버러 대학의 툴킷을 참고하여, 다음 4f 에 기반하여 작성되었습니다.
Facts 사실 | An objective account of what happened
Feelings 느낌 | The emotional reactions to the situation
Findings 교훈 | The concrete learning that you can take away from the situation
Future 앞으로 | Structuring your learning such that you can use it in the future
새로운 네 명이 모였다. 동환님과는 지난 WAF 프로젝트에 이어 또 같은 팀이 되었다. 아이디어 스케치를 했는데, 팀장님께서 전통주를 소개하는 웹서비스를 꽤 구체적으로 생각해오셔서 이 아이디어를 발전시켜보기로 했다. 채택되지 못했지만 내가 준비했던 아이디어 두 개도 함께 적어본다.
사이드 프로젝트가 된 아이디어
하나는 일반인의 미술작품 대중경매 사이트였다. 미술, 옥션이라는 주제가 마치 와인처럼 하이엔드 문화로 여겨지는게 항상 아쉬웠다. DIY와 공예를 즐기는 나로서, 보다 가볍게 접근할 수 있는 플랫폼을 만들고 싶었다. 좋은 가격에 팔리면 좋고, 아니어도 재미는 볼 수 있는(?). 하지만 팀원 중 한 분이 이전 프로젝트에서 이와 비슷한 중고물품 대중경매 플랫폼을 만드셨기에, 이건 스킵되었다.
다른 하나는 생애주기 통합건강관리 앱이었다. 먹거리, 입을거리 하나하나 몸에 좋은 것을 찾는 요즘 시대, 헬스케어는 국민소득 3만달러 시대에 더 진화할 시장이다. 물론 이런 거창한 이유보다는 개인적인 이유로 구상한 서비스였다. 치과 등 정기 검진 다니는 곳이 많고, 한 질환으로 여러 병원을 다니기도 하고, 어쩌면 같이 먹으면 안 되는 약들이 있을 수 있는데 대개 받아온 약 봉지를 보관하지 않으면 손 안의 관리가 어렵고, 마지막으로 이런 기록들은 보험사에 청구할 때도 요긴한데 적어두지 않으면 까먹기 일쑤였다. 이런 걸 한 방에! 관리할 수 있는 앱을 만들고 싶었다. 하지만 4주차 프로젝트로 선정하기에는 규모가 작다는 의견이 있어서, 이들은 모두 나의 사이드 프로젝트 명단에 오르며 물러났다.
기술스택
이전에 백엔드를 담당했던 나는, 이번에는 동환님과 함께 프론트엔드를 맡았다. 4주차는 새로운 스택, 모두가 써보고 싶던 스택을 적극 반영하여 기술 스택을 구성했다. 프론트 엔드에서는 Typescript, React hooks, Styled-components, Redux 백엔드에서는 Node.js, Express.js, TypeORM, MySql, Kakao Oauth, Google Oauth, AWS S3, EC2, RDS 로 구성했다.
프로젝트 4주 기간 동안 첫 일주일은 기술 스택 공부 및 복습, 그 다음 일주일은 SR 미팅 및 초기 환경 세팅을 하고, 나머지 2주 동안 열나게 코드를 썼다.
팀원들 모두 디테일 작렬하는 프로젝트 기획의 중요성을 잘 느끼고 있어서, 거의 일주일을 할애하여 프로젝트 기획을 했다. git flow 는 다들 익숙했고, figma 로 와이어 프레임, UI 목업 설정, 대강의 콘텐츠를 생각해보고, drawio 로 플로우 차트를 만들고, dbdiagram 으로 데이터 스키마 구상, gitbook 으로 각 엔드포인트와 주고 받을 데이터에 대해 스케치해보았다.
작업 환경설정
새벽에 혼자 데이터 흐름을 생각해보며 위와 같은 컴포넌트 트리를 그리고, react-router-dom 문서를 훑어보며 useParams 를 통해 동적 라우팅을 구상하며 목업 프로젝트를 만들어서 테스트해보았다. 다음 날 다행히 다른 팀원 분의 동의를 얻어서 그대로 적용할 수 있었다.
파일 구조를 만들 때에는 리덕스, 아토믹 패턴을 함께 고려해야했기에, 우선 리덕스용 store, actions, reducers 디렉토리를 만들었다. 그리고 App 디렉토리 안에 아토믹 패턴을 담을 atoms, molecules, organisms, templates, pages 를 디렉토리를 만들었다.
마지막으로 로컬 서버, 배포 서버 주소를 환경 변수처럼 관리하기 위해 apis 디렉토리에 server 파일을 만들고 axios.create 의 base url 을 지정한 후, 계속 이 파일을 import 하여 axios 요청을 했다. 배포 후에는 base url을 EC2 서버 주소로 바꾸기만 했다. 최종 src 파일 구조는 아래와 같이 완성되었다.
협업
우선 NavBar, 랜딩페이지, Footer 를 페어 프로그래밍 형식으로 작업하면서, 우리 파일 구조에 어떻게 파일명을 짓고 기능 분할을 할 지 세부적인 가닥을 잡았다. 그리고 페이지를 나눠서 맡되, 프론트엔드 협업은 항상 줌을 연결하여 모르는 것은 함께 풀어가는 페어코딩 방식을 택했다.
🍉 전통주 상세페이지
svg를 리액트 컴포넌트로 바꿔서 icon 으로 사용
리액트에서 리뷰에 들어갈 별 icon 작업을 위해 이미지를 다운받으려고 보니, svg 옵션이 있었다. HTML5 에서 svg 가 생긴 것은 알고 있는데, 리액트에서는 어떻게 활용할 수 있는지 궁금했다. stackoverflow 를 통해 svg 파일을 하나의 리액트 컴포넌트로 사용할 수 있다는 것을 알고, atoms 디렉토리에 만들어서 사용했다.
import { ReactComponent as StarSvg } from '/images/star.svg';
위와 같이 svg 파일을 import 해주면 끝이다. svg 컴포넌트는 자체 fill 속성을 사용해서 쉽게 색상 변경을 할 수 있다. 리액트 컴포넌트이다 보니 때에 따라 props 로 색깔을 내려주면 되서 일반 png, jpg에 비해 훨씬 용도에 맞게 사용할 수 있었다.
useState, styled-components 로 CSS 효과주기
이전 프로젝트의 프론트엔드 분들은 돔을 조작하여 :hover selector 를 쓰거나 active 클래스를 만들고 classList.add/remove 로 마우스 이벤트에 대한 효과를 컨트롤했는데 이번에는 주어진 기술 환경부터가 달랐다.
react hooks 와 styled-components 를 조합해서 hover, active 효과를 주는 게 도전 과제였다. 여기서는 useState 를 사용했다. hover 의 경우 onMouseEnter 와 onMouseLeave 이벤트 리스너를, active 의 경우 onClick 이벤트 리스너를 사용해서 setState 하였다. 그리고 state에 따라 별도의 styled-components 를 렌더링하도록 하였다.
리뷰 별점 기능
해외 블로그를 보고 거의 그대로 가져다 적용해볼 수 있었다. 특히 useMemo 를 사용해볼 수 있어서 좋았다. memo는 내가 이전 블로그에도 정리해본 적 있는 memoization 의 준말이다. 이전에 계산한 값을 저장하여 동일한 계산은 건너뜀으로서 프로그램 실행을 빠르게 해주는 기술인데, 별 아이콘을 빠르게 hover over 할 때마다 위치에 맞춰서 별을 노랗게 바꿀지 말지를 연산하는데에 적용되었다.
리뷰 리스트
맡은 페이지에서 UX 최적화를 위해 노력했다. 예컨대 클릭을 유도하는 버튼에 진한 색깔을 주고, 리스트는 컨텐츠에 집중할 수 있도록 최대 4개까지만 보이도록 구현했다. 반응형에 따라 3개까지 줄어들기도 한다.
유효성검사
게스트가 만약 개인화된 기능을 사용하려면 로그인을 해야한다. 대표적으로 상세페이지에 있는 '북마크 등록' 과 '리뷰 등록' 이다. 로컬스토리지 내의 토큰을 확인하고, 토큰이 없다면 로그인 요청 팝업이 뜬다.
🍉 마이페이지
사이드바
마이페이지의 탑레벨 컴포넌트에 사이드바와 사이드바 클릭에 따라 렌더링될 페이지들을 담았다.
북마크 리스트
flexbox 를 사용하여 레이아웃을 잡고, 하나의 북마크는 하나의 카드 형태로 보여주었다. 카드에 많은 정보는 담지 않고, hover 하는 경우 북마크 삭제 또는 상세보기 페이지로 이동할 수 있는 버튼이 보인다. 역시 UX를 생각했을 때, 북마크에서 많은 정보를 보여줄 필요는 없다고 생각했다. 만약 북마크에 등록된 전통주가 없다면, 가볍게 디폴트 메세지를 보여주었다.
formData, 모달 생성
사용자는 로그인 후에 이미지, 닉네임, 비밀번호 수정을 할 수 있다. 이미지의 경우 formData 형태로 보내면 서버에서 multer 를 사용해 업데이트를 한다. 그리고 닉네임과 비밀번호 수정을 해야하는데, 개인적으로 팝업 모달은 광고 생각이 나서 좋아하지 않기 때문에, input 이 나타날 공간을 미리 확보해두고 클릭해따라 display 를 나타내는 식으로 구현했다.
본격적으로 팀원 네 명이 머리를 맞대고 협업을 했다. 공교롭게도 우리가 13인치, 14인치, 27인치 등 다양한 사이즈의 모니터를 사용하고 있어서, 개발자도구와 각자의 로컬 환경에서 테스트 해보는 것으로 반응형 테스트를 진행해볼 수 있었다.
글로벌 CSS 파일 세팅
udemy에서 CSS 심화 수업을 들은 적이 있는데, 그 때 요긴했던 base 세팅을 가져와서 우리 글로벌 CSS 파일을 만들어보았다. 기본적으로 px 대신, rem 을 사용하기로 하고 em, rem 의 차이 등에 대해 복습했다. em 은 부모요소 대비, rem 은 root 요소 대비로 계산한다. 크롬 기준 브라우저 디폴트 폰트 크기가 16px 이고, 나는 계산의 편의를 위해 1rem = 10px 로 설정하고자 했으므로, root font-size 는 62.5% 로 설정했다. (10px/16px * 100)
그리고 우리 메인 컬러가 있었기 때문에, 매번 hexcode 를 적는 불편함을 덜고자 custom variable 을 사용했다. 그리고 flexbox 와 미디어쿼리로 반응형을 구현했다.
이번에는 도메인 주소를 Router53 에 연결하여, 비교적 예쁜 주소로 배포까지 마무리하였다.
분명히 코딩 프로젝트인데, 하루 정도는 꼬박 콘텐츠 구상에 쏟기도 했다. 로그인에 들어갈 그림, 랜딩에 넣을 문구들 등등 이런 부분들 하나하나 의견 교환이 많이 필요했던 작업이었는데, 팀원들 모두 콘텐츠에 각종 인터넷 드립들을 섞어보며.. 😂 즐겁게 작업했다!
나의 건강은 팀의 자산
프로젝트를 만드는 것도 재밌었고, 무엇보다 너무 좋은 팀원들과 함께했기 때문에 마음은 하나도 힘들지 않았는데, 오히려 그랬기에 몸이 힘든 걸 대수롭지 않게 여겼던 것 같다. 약 두 달 가까이, 특히 파이널 프로젝트 기간 동안 점심시간 1시간, 저녁시간 2시간 외 자는 시간을 빼고 정말 항상 화상채팅을 켜 놓고 프로젝트를 하는 생활을 하다보니, 프로젝트 마지막 전 날 내내 원인불명의 두통과 속 울렁거림으로 병이 나서 울다가 자다가 했다. 끝까지 마무리를 하는 팀원들에게 어찌나 미안하던지.. 협업을 할 때에는 내 건강이 나의 자산일 뿐만 아니라, 팀의 자산이기도 하다는 것을 깨달았다.
인간 프리티어
이번에 나는 자칭 인간 프리티어였다. 나는 평소에도 깔끔하고 주석없이도 알아볼 수 있는 직관적인 코드, 일명 clean code에 열광한다. 그리고 아직 내 지식이 얕고 배우는 단계에서는 가급적 convention 이나 best practice 를 찾고 따르는 편이다. 그런데 이번 개발 환경 세팅 당시 모두 prettier 를 사용하기로 했는데, 동환님과 나의 컴퓨터에선 어쩐 일인지 적용이 되지 않았다. 프론트엔드인 우리는 화면 공유를 해서 의논할 때가 많았기 때문에, 내가 종종 동환님에게 몇 가지 부탁을 드리곤 했다. 예를 들어 isClicked 와 같은 변수명에 대한 값은 textContent 문자열이 아닌 불리언 값으로 바꿔달라든지.. 너무 컨벤션 코드를 봐온 탓에, 문자열을 값으로 가진 isClicked 로 로직 고민을 해야하는데 머리가 버벅대는 것 같았다. 결국 불리언 값으로 바꿨다. 여러 사람이 읽는 코드이기때문에 다수가 용이하다고 합의한 컨벤션을 참조하는 것도 좋지만, 여러 사람의 스타일을 받아들이고 유연하게 작업할 수 있는 능력 또한 필요하다고 느꼈다. 많이 귀찮았을 요구를 웃으며 받아준 동료에게 다시 한 번 감사함을 전한다.
디테일, 디테일, 디테일...
꼼꼼한 성격을 스스로 피곤해하는 편이지만, 이 성격이 발휘될 수 있는 일을 맡으면 물만난 물고기가 된다. 프론트엔드 개발자는 기능 구현뿐 아니라, 사용자가 직접 보는 화면을 폴리싱하는 담당자인만큼, 디테일을 섬세하게 다듬을수록 프로덕트는 빛이 난다. 우리 빚다 사이트를 예로 들면 데이터 로딩이 오래 걸리는 페이지 전환의 경우 로딩 애니메이션을 넣었다. 그 밖에도 같은 레벨의 타이틀에는 같은 폰트 사이즈를 사용해야한다든지. 이런 류의 사소한 디테일을 다 챙겨야 하는데 다행히도 성격에 위배되지 않아서(?) 즐거운 작업이었다.
🎉 첫 프로젝트의 findings 에 적었던 개선점 반영
개인적으로 이전 프로젝트에서 기술적으로 아쉬웠던 부분들 - 컴포넌트 기초 설계, 상태관리 라이브러리 사용, 반응형 CSS 구현 - 을 이번 프로젝트에서 모두 개선시켰다. 이번 프로젝트가 만족스러운 이유 중의 하나이다.
styled-components 와 아토믹 패턴의 조합
우리 규모의 프로젝트에서 꿀조합이라고는 할 수 없었다. 스타일드 컴포넌트들 자체도 하나의 리액트 컴포넌트들인데, 점점 컴포넌트들 갯수가 증폭되면서 재사용성의 장점을 넘어 오버킬하는 느낌이었다. 거의 모든 jsx 가 일회용 스타일드 컴포넌트화되었다. 그리고 아토믹 패턴으로 구성된 수많은 리액트 컴포넌트들도 필연적으로 props 를 몇 번씩 거쳐 내리는데, 스타일드 컴포넌트들에도 props 를 내리면서 props hell이 펼쳐졌다.
물론 스타일드 컴포넌트의 장점이 확실히 있었다. 기능 컴포넌트에 밀착 사용하기때문에 컴포넌트를 삭제할 경우 쉽게 스타일링과 함께 삭제 가능하고, 클래스 이름의 중복으로 인한 문제점을 피할 수 있으며, 동적 스타일링이 가능하다는 점이 특히 매력적이었다. 동적 스타일링의 경우, 나는 버튼에서 유용하게 사용했다. 버튼을 누르면 항상 어떤 이벤트가 발생하는 점을 고려하여 항상 handleClick 이라는 props 를 받도록 해두고, 버튼 스타일드 컴포넌트를 사용할 때마다 다른 클릭 이벤트 함수를 내려줬다. Sass 처럼 &:hover, &:focus 의 형태로 중첩해서 사용할 수도 있고, 기존 스타일드 컴포넌트를 extend 해서 사용하는 기능도 편리했다.
이렇게 버튼만 따로 떼서 컴포넌트를 만든 건 아토믹 디자인 패턴에 따른 것이었다. 아토믹 패턴은 하나의 기능 컴포넌트 요소들을 그 역할 또는 기능에 따라 잘게 쪼개어서 atoms
, molecules
, organisms
, templates
, pages
에 나누는 것이다. 더 이상 잘게 쪼갤 수 없는 버튼, 인풋창 등은 atoms, 인풋과 버튼이 만나면 molecules, 거기에 라벨까지 씌우면 organisms 가 된다. 여러 개의 organisms 가 template 에 정의한 레이아웃 안에 배치되고, 최상단의 pages 는 그 template 을 렌더링하는 역할을 한다. 자연스레 여러 컴포넌트의 중첩 구조를 상상해볼 수 있다. 아토믹 패턴은 프로젝트 규모가 더 커서 재사용 가능한 컴포넌트들이 많이 있을 때 훨씬 더 유용하게 사용할 수 있을 것 같다. 우리 프로젝트에서는 일회용 컴포넌트들이 많아 제대로 효용을 발휘하지 못한 것 같아 아쉽지만, 그 장단점을 체험해볼 수 있어서 좋았다.
styled-components 변수명 규칙
변수명 규칙은 camelCase 로 정했지만, 스타일드 컴포넌트 변수명 규칙도 정했어야 한다는 것을 간과했다. 나는 스타일드 컴포넌트의 경우 'Style + 기능' 의 패턴으로 'StyleReview' 라는 식으로 네이밍했다면, 다른 프론트엔드 팀원 분은 'CSS특징 + jsx' 의 패턴으로 'FlexBoxDiv' 라는 식으로 네이밍을 하셨다.
처음 우리 코드를 리뷰 하는 사람이라면, 이렇게 통일되지 않은 네이밍 방식에 분명 혼란스럽고 가독성이 떨어진다고 느낄 것이다. 혼란스러운 코드는 개발자로 하여금 불필요한 추측에 시간을 소요하게 한다. 사소해보이지만, 이런 부분들이 쌓여 향후 디버깅 등에 있어 문제 해결속도를 지연시키므로 결과적으로 기술적 부채에 해당한다고 본다. 꼭 개선해야할 부분이다.
typescript의 사용
타입스크립트의 유용성은 깃헙 회고에도 작성한 바 있다.
타입스크립트의 장점은 개발자로 하여금, type 충돌로 발생 가능한 에러를 런타임에 실행하기 전 "개발 단계" 에서 감지하고 수정하게 함으로써, 런타임 에러를 줄여준다는 것이다. 예전에는 브라우저에서 실행시킨 후 콘솔창을 보고 에러를 감지했다면, typescript 는 코드 에디터 상에서, 혹은 typescript 컴파일링 단계에서 에러를 감지할 수 있게 한다. 이를 테면, 사후약처방에서 사전예방으로 에러 핸들링을 할 수 있게된 것이다.
프로젝트 4주차에 접어들자 코드 작성을 하면서 바로 타입 지정을 할만큼, 타입스크립트를 선제적으로 사용할 수 있었다. 즉, 나 스스로도 훨씬 예측 가능한 코드를 작성 가능하게 된 것이다. 타입스크립트가 유용한 또 다른 경우도 있었다. 우리 사이트는 관리자 모드가 있는데 처음에는 관리자인지 여부를 boolean 으로 데이터를 주고 받기로 했으나, 서버에서 0, 1 의 값으로 대체해야 한다고 했다. 곧바로 타입스크립트의 enum 기능이 생각나서 써먹었다. 0 이 true인지, false인지 모를 때 enum 으로 미리 정의해두면 누구나 쉽게 알 수 있었다.
token 상태관리는 로컬스토리지에서
글로벌 상태관리를 위해 redux 를 도입하기로 하고, 가장 먼저 token과 같은 로그인 상태를 저장하였으나 새로고침 후에 리덕스가 reset 되어 로그인이 풀리는 현상이 발생했다. 이런 고질적인 문제를 어떻게 해결할까하다가 결국 로컬스토리지를 사용했다. 그러나 모든 정보를 로컬스토리지에 저장하기에는 한계가 있었고, 리덕스 사용성이 현격히 떨어진다는 생각이 들었다. 결국 토큰만 로컬스토리지에 저장하고, 항상 렌더링되는 NavBar에서 새로고침 때마다 로컬스토리지에 토큰이 있는지 확인하고, 토큰이 있으면 유저 정보를 불러와 리덕스 스토어에 저장하여 사용하는 방식을 택했다. 프로젝트 당시에는 이렇게 해결했고, 프로젝트 발표 후 리서치를 통해 이런 점을 보완해주는 라이브러리 redux-persist 에 대해 알게 됐다. 다음에는 이 라이브러리를 적용해서 해결해보고 싶다.
이번 프로젝트를 토대로 다음 프로젝트에 적용해보고 싶은 것들이 생겼다.
Storybook 사용
스토리북은 컴포넌트 UI 개발을 위한 오픈소스 툴이라고 하는데, 아토믹 패턴을 찾아볼 때 거의 모두가 사용하고 있었다. 미리 계획하고 사용하지 못해서 아쉽지만, 얼마나 좋은지 다음 번엔 꼭 사용해봐야겠다.
Custom hook
hooks 의 꽃은 커스텀 훅이라고 생각하는데, 이번 프로젝트에서 활용해보지 못해서 아쉽다. 프로젝트 끝으로 갈수록, 리덕스 스토어에 접근하기위해 아래 문법들이 반복 사용되었는데 이 부분을 커스텀 훅으로 리팩토링해볼 수 있을 것 같다.
const state = useSelector((state: RootState) => state.signintReducer);
const dispatch = useDispatch();
container presenter 패턴 적용
컨테이너 프레젠터 패턴은 리액트의 가장 기본 패턴이기도 한데, 비즈니스 로직을 핸들링하는 container 컴포넌트와 UI만을 렌더링하는 presenter 컴포넌트로 분리하여 설계하는 것이다. 컴포넌트 설계에 다양한 아키텍처를 적용해보면서, 향후에 프로젝트 특성에 맞는 패턴들을 직접 골라 사용할 수 있으리라 기대한다.
취향기반 전통주 추천 서비스, 직접 체험해보세요 - 빚다
글 작성하시는것만 봐도 고수의 향기가 폴폴 나네요. 혹시 영어 이름이 권폴이신가요 ? 권척인가 ?