[회고] 2023년 9월 프로젝트 회고

Chaeyeon Lee·2023년 10월 4일
3

회고록

목록 보기
4/6
post-thumbnail

0. 프로젝트 개괄적 소개

기간: 2023년 9월 1일(금) ~ 2023년 9월 27일(수)
주제: SNS 프로젝트
참여 인원: 프론트엔드 5명
배포 링크: https://tmi-homers.vercel.app/

1. 프로젝트 기획(1차 스프린트)

1-1. 팀 문화, 팀 규칙

프로젝트를 잘 만들기 위해선 탄탄한 팀 문화와 규칙이 필요하다고 생각하여, 팀원들끼리 활발한 소통을 하며 하나씩 만들어 나갔다. 정해진 팀 문화와 규칙은 다음과 같다.

(1) 오픈 스크럼, 클로즈 스크럼 진행하기
스크럼 진행 방식과 기록은 나의 담당이었으므로 내가 생각한 스크럼의 장단점과 개선점을 적어보았다.

방법

  • 오픈 스크럼 때에는 코어타임 이후 한 일에 대해 이야기 하고 해결하지 못한 이슈나 공유해야 할 사항들에 대해 팀원들에게 전달한다. 또한 그날 할 일을 정리하고 말한다.
  • 클로즈 스크럼 때에는 코어타임 동안 한 일에 대해 이야기 하고, 해결하지 못한 이슈나 공유해야 할 사항들에 대해 팀원들에게 전달한다. 또한 코어타임 이후 할 일들을 이야기한다.
  • 각 스크럼 때에는 개별적으로 진행한 업무를 합하여, 전체 업무와 관련된 일정을 업데이트한다.

장점

  • 스크럼을 통해 다른 팀원이 어떤 일을 하고 있는지, 개발 진행 속도가 계획된 일정과 얼마나 일치하는지 알 수 있어서 효율적인 업무 분배와 개발이 가능했다.

단점

  • 기록 형식에 통일성이 없어서 어떤 날에는 중요한 수정사항이나 변경사항 등을 제대로 업데이트 하지 못해 소통의 문제가 발생했다.

개선점

  • 스크럼 기록도 유의미한 기록물로 만들어야 한다. 나중에 확인했을 때에도 어떤 업무를 했었고 어떤 업무를 해야 했는지, 혹은 어떤 수정사항이 있었는지 알 수 있어야 한다.

(2) 팀 프로젝트에 임하는 태도
프로젝트를 시작하기 이전에 멘토님께 프로젝트 관련 질문을 드린 적이 있었는데, 멘토님의 답변을 기준으로 팀 문화가 암묵적으로 정착이 된 것 같다.

  • 맨데이와 간트차트로 일정 관리하기.
    : 팀 프로젝트에 있어서 일정 추산과 이행은 굉장히 중요하다. 개인이 하루에 개발하는 데에 얼마나 시간을 쏟을 수 있는지 맨데이를 산정해보고, 1맨데이에 개발할 수 있는 기능이 어느정도 되는지 아는 것이 현업에서 필수적으로 필요하다고 하셨다. 또한 맨데이를 기준으로 결정된 개발일정을 간트차트로 관리해서 다른 팀원들의 일정을 수시로 확인할 수 있도록 하고, 업무가 너무 많아서 길어지는 일정을 다른 팀원들에게 분배하거나 업무가 너무 없어서 놀고 있는 팀원이 없도록 하는 것이 핵심이라고 하셨다.
  • 팀원 간 역할이 무조건 평등할 수 없음을 이해하기
    : 프로젝트에 참여하는 팀원들은 각기 다른 경험과 개발역량을 가지고 있으므로 모두에게 똑같은 업무량을 분배할 수 없는 게 당연하다. 따라서 더 어려운 기능은 비교적 잘하는 사람이 가져가서 개인적 성장을 목표로 하고, 쉬운 기능은 비교적 못하는 사람(나)이 가져가서 프로젝트에 쓰이는 기술스택을 습득하는 데에 힘써야 한다. 나는 프로젝트나 개발 경험이 팀원들에 비해 절대적으로 부족했기 때문에 이 부분에 대해 멘토님께 질문 드렸는데, 멘토님은 짧은 개발 기간과 현실적인 가능성을 고려하셔서 위와 같은 답변을 해주셨다. 또한 자신이 맡은 기능에 대한 구현이 늦어지면 팀 간 논의를 통해 일정이 널널한 사람이 해당 업무를 가져가서 처리할 수 있는 유연성이 필요하다고 하셨다.
  • 열정을 강요하지 않기
    : 프로젝트를 진행하면서 어떤 팀원은 밤을 새서 고도화하는 반면, 어떤 팀원은 자신의 일정이 끝나면 홀연히 사라질 수 있다. 각자가 맡은 기능과 관련된 일정에 지장을 주지 않는다면 프로젝트에 쏟는 열정은 개개인마다 다르고, 누구에게 강요할 수 없는 것이라고 하셨다. 우리 팀은 이번 프로젝트를 하며 다들 열정을 쏟아 부었지만, 추후에 다른 팀 프로젝트를 진행할 때에 충분히 맞닥뜨릴 수 있는 상황이라고 생각했다. '나는 이렇게 열심히 하는데 왜 쟤는 저렇게밖에 안 하지?"라는 생각은 프로젝트에 있어서 도움이 안 되며, 피해만 주지 않는다면 감정소모할 바에 자기 자신을 성장시키는 데에 힘쓰라고 하셨다. 인상 깊었음.

1-2. 협업방식 정하기

(1) 기술스택 정하기
타입스크립트를 사용해야 하는 건 필수 사항이었고, 어떤 기술스택을 사용할지 정하는 데에 멘토님의 도움이 컸다. 아래와 같은 기술스택을 사용하자고 정해졌고, 내가 각 기술을 쓰면서 어려웠던 부분을 써보고자 한다.

  • TypeScript
    : 처음에는 왜 필요한 것인지 몰랐는데, api에 데이터를 넣어 전송할 때에 정확한 값을 넣어줄 수 있어서 편리했음...은 느낌적 느낌에 불과했다. 실제로는 비동기적으로 데이터를 받아오는 변수에 값이 로드 되었는지 불명확할 때마다 Null이 아닌 어선셜 연산자(이하 느낌표)를 너무 남용하여서 내가 타입에 대한 이해가 부족하단 걸 느꼈다. 또한 타입별칭과 값을 명확하게 알고 있지 않았던 것 같다. 아직 자바스크립트도 부족한데, 타입스크립트를 쓰면서 필요성을 느끼려고만 하니 발생한 문제인 것 같다.
  • React
    : 리액트가 뭔지도 모르고 프로젝트를 시작했지만, 프로젝트 시작 이전에 팀원들과 함께 진행한 스터디 덕분에 어떤 식으로 '상태'라는 걸 다루는지 감을 잡았고, 정확한 동작원리에 대한 이해 없이 막연한 상태로 코드를 작성했다. 이제 프로젝트도 끝났으니 진짜로 리액트를 공부할 때!
  • Tailwind CSS
    : Tailwind를 쓰기도 전에 러닝커브가 높다는 소리를 하도 들어서 두려움이 앞섰다. 공통 컴포넌트 만들면서 계속 공식 사이트를 참고해야 해서 초반에는 개발 속도도 안 나고 힘들었지만, 자주 쓰이는 속성들이 외워지다보니 페이지 레이아웃도 금방 만들고, 수정하는 것도 쉬웠다. 특히 태그에 이름을 붙이지 않고 스타일을 바로 적용할 수 있어서 굉장히 직관적이라고 느꼈다. 앞으로도 CSS를 공부하면 더 어려워지겠지만, Tailwind는 잘 배웠다고 생각했다.
  • React Hook Form
    : Tanstack Query나 React Router는 다른 팀원이 미리 사용하기 쉽도록 세팅해주어서 직접적으로 다룰 일이 없었지만, React Hook Form은 내가 맡은 기능에서 쓸 일이 있었기 때문에 빠르게 배웠어야 했다.
    아쉬웠던 점은, 내가 라이브러리 없이 리액트로 input을 관리하는 방법을 알았더라면 React Hook Form 사용 이유나 사용함으로써 얻게 되는 이점에 대해 깨달았겠지만, 처음부터 라이브러리를 사용해서 어떤 점을 편리하게 만들어주는지 몰랐다는 것이다. 물론 form이 많아질수록 useState로 일일이 관리해주기 어렵기 때문이라는 건 읽어서 알고 있으나, 그런 경험을 해보는 것도 나에겐 필요하다고 생각했다.

(2) 깃헙 이슈? 브랜치 전략?

  • 코딩 컨벤션, 코드 네이밍, 폴드 구조, 브랜치 네이밍, 파일 네이밍 일치 시키기.
    : 팀원 중 팀프로젝트 경험이 많은 대단한 분이 계셔서 협업할 때 정해야 하는 것들에 대해 정리해주셨다. 데브코스 시작하기 전에는 변수 명이나 함수 명의 통일성이 갖는 중요성에 대해 생각해본 적 없는데, 확실히 여러 명이서 작업하는 프로젝트 특성상 사소하게 보이는 부분이라도 규칙을 통해 맞추고 시작하는 것이 효율적임을 깨달았다.
  • git issue 이용하기.
    : 내가 이번 프로젝트를 재미있게 참여할 수 있었던 이유 중 하나는 git issue 때문이었다. issue를 통해 프로젝트에 추가할 기능이나, 버그, 리팩토링 사항을 생성하고 작업이 완료된 후에 닫아주면 된다는 팀원의 설명을 듣고 "왜 필요한 거지?" 싶었다. 그러나 직접 해보니, 다른 팀원이 어떤 일을 하고 있는지 알기도 쉬웠고, issue나 pr을 잘 적으면 자동으로 문서화도 되었으며, 무엇보다 개발하면서 퀘스트를 깨는 게임을 하고 있다는 생각이 들어서 재밌었다. 막바지에 이르러서야 issue에 status라는 것도 존재하고, git 프로젝트 칸반보드에서 작업중인 일정으로 설정할 수 있다는 걸 알아서 아쉬웠지만, 다음 프로젝트에서는 이를 더 잘 활용할 수 있을 거란 생각이 들었다. 개인 프로젝트를 할 때에도 적극적으로 활용해 볼 예정이다.
  • 깃플로우? 깃헙플로우?
    : 데브코스에서 과제를 제출하는 방식은 다음과 같다.
  1. main브랜치에서 각자 개인 working 브랜치와 개인 제출 브랜치 생성.
  2. working 브랜치에서 작업이 완료 되면 개인 제출 브랜치로 push.
  3. 머지 시 개인 제출 브랜치 업데이트
    나는 이 과정이 깃플로우와 동일하다고 생각해서 익숙한 깃플로우 전략으로 했으면 했는데, 멘토님이 볼륨이 작은 프로젝트의 경우에는 과할 수도 있다고 하셨다. 때문에 바로 메인에 푸시해야 한다는 부담감이 있었지만, 능력자 팀원분이 husky와 lint-staged를 이용해 eslint를 통과한 코드만 commit이 가능하도록 했고, push 전에 build test를 진행하도록 하여 main브랜치를 보호해줘서 큰 문제가 발생하지 않았다! eslint, husky 등이 왜 중요한지 깨달았던 경험이다.

1-3. 아이디어 및 디자인

아이디어는 빠르게 정해졌다. 자신의 tmi를 "속보"와 같은 말머리를 달아서 쓸 수 있는 SNS가 있으면 재밌을 것 같다라는 생각에 컨셉이 정해지고, 이걸 주제로 만들기로 했다.

다른 기능이나 기술을 쓸 수 있는 아이디어가 많았지만 내 입장에서는 기본기가 부족했기 때문에 최대한 SNS 프로젝트라는 주제와 요구사항에 충실한 서비스를 만들며 공부하고 싶었고, 다행히 팀원들도 흥미 위주로 주제를 선택해주었다.
개인적으로 디자인은 프론트엔드 개발자의 역할이 아니라고 생각했지만, 디자인과 개발 올라운더인 팀원이 있어서 아름답고 센스있는 디자인이 이틀만에 완성 되었다. 색상이나 폰트, 글자 위치 같은 사소한 변화도 서비스의 완성도를 결정하는구나 느꼈다. 아쉬웠던 점은, 와이어프레임을 먼저 만들고 디자인을 만들었다면 디자인을 지속적으로 수정할 필요가 없었을 텐데, 디자인 자체가 와이어프레임이 되다 보니 필요한 요소가 생길 때마다 디자인을 손봐야 했어서 시간이 많이 들었던 것 같다.

1-4. 기능 명세서로 역할 나누기

(1) 공통 컴포넌트
디자인을 보고 자주 사용되는 UI 요소를 공통 컴포넌트로 지정하고, 공통 컴포넌트들의 기능과 종류를 정리하여 역할을 배분하였다. 자신이 맡은 페이지에서 자주 쓰이는 공통컴포넌트를 개발하기로 하였고, 한 명에게 너무 몰릴 경우 비교적 널널한 사람이 가져가기도 하였다.

(2) 기능 명세서로 역할 나누기
역할분담을 페이지별로 할지, 기능별로 할지 고민하다가 일의 크기가 더 작은 기능별로 역할을 나누기로 했다. 이를 위해 페이지별로 구현해야 하는 기능들을 상세히 정리하고, 역할을 분담한 뒤 우선순위에 따라 개발을 시작하기로 했다. 각 기능에 예상되는 맨데이를 정하고 개인별 간트차트를 만들어 일정을 관리했다.

  • 아쉬운 점
    - 역할분담에 대한 기준은 정해져 있지 않았기 때문에 그냥 하고 싶은 페이지를 눈치껏 가져갔다. 이 부분은 프로젝트 경험이 아무리 많아도 어떻게 나눠야할지 모를 것 같다.
    - 기능 명세서를 충분히 세세하게 작성했다고 생각했는데, 예상외로 기능이 더 추가되기도 하고, 생각보다 개발일정이 촉박하거나 널널한 경우가 있었다. 간트차트 하나로 팀원 전체의 일정을 관리했다면 늘어지는 일정을 효율적으로 쓸 수 있었지 않았을까?
    - 분명 기능별로 나누어 놨는데, 페이지 기준으로 역할이 분배되어서 역할 쏠림 현상이 있었다.

1-5. 팀 스터디

  • 역할 분담을 한 뒤, 본격적인 개발에 앞서서 팀장님의 제안으로 다같이 주요 기능에 대해 스터디를 하기로 했다. 회원가입, 글 검색, 글 작성에 대해 1차 스프린트 기간(약 6일) 동안 함께 스터디 했다. 덕분에 프로젝트를 진행하면서 다른 페이지를 어떻게 개발하는지 알 수 있었고, 다른 팀원의 코드를 보면서 빠르게 라이브러리 사용법을 배울 수 있었다. 또한 코드 스타일을 보면서 통일성 있게 코드를 작성하게 되었다.
  • 처음에는 스터디에 너무 많이 투자하는 것 아닐까 걱정했지만, 다른 팀원들이 맡은 기능 구현에 드는 시간이 예상보다 짧기도 했고 스터디를 하면서 공통 컴포넌트를 구현했기 때문에 실질적으로 개발일정에 큰 영향을 주진 않았다.
  • 스터디는 나에게 있어서 첫 프로젝트를 할 수 있게 해준 입장 티켓이었다. 회원가입 기능에 대해 공부하고 다른 팀원의 코드를 보며 react hook form을 쓰는 방법을 대략적으로 배웠고, 글 검색 기능을 공부하며 query 사용과 디바운스에 대해 배웠다. 또한 글 작성 기능을 공부하며 api를 사용하여 서버에 데이터를 저장하는 방법을 알게 되었다.

2. 프로젝트 개발(2차 스프린트)

2-1. 내가 맡은 역할

개발 단계에서 내가 맡은 역할은 다음과 같다.

  • 글 작성 페이지
    - 제목 작성 시 말머리를 선택할 수 있다.
    • react hook form을 이용해 제목과 글 작성 input을 관리한다.
    • 사진을 첨부하고, 첨부한 사진을 삭제할 수 있다.
    • 글이 작성이 완료되기 전까지 작성하기 버튼이 비활성화된다.
  • 글 상세 페이지
    - 작성된 글의 상세 정보를 불러온다.
    • 작성된 글에 달린 댓글을 불러온다.
    • 좋아요 누르기 및 좋아요 취소를 할 수 있다.
    • 댓글 달기 및 댓글 삭제를 할 수 있다.
    • 자신이 작성한 글은 삭제를 할 수 있다.
    • 글 상세 페이지에서 사용자를 클릭하면 각 사용자 프로필로 이동한다.
  • 다크모드
    - 홈 화면에서 다크모드 전환 버튼을 누르면 모드가 전환된다.
    • 계속 오류가 일어나서 계획된 일정에서 초과가 되었고, 다른 팀원이 맡았지만 다시 한번 오류가 나서 결국 우리 팀의 테크리더가 다크모드를 구현하였다.

2-2. 개발 시 겪었던 이슈

(1) 글 작성 기능
글 작성 기능에 대해 팀원들과 스터디 할 때에도 이미지 첨부 기능을 구현하지 못해서 애먹었었다.

  • 글 작성 시 사진 첨부가 가능해야 하므로 FormData 객체를 이용해야 한다.
const saveArticle = async ({ title, body, image }: FormValue) => {
  const formData = new FormData();
  formData.append('title', JSON.stringify({ title: title, body: body }));
  formData.append('channelId', CHANNEL_ID);
  formData.append('image', image);

  await axiosClient.post('/posts/create', formData, {
    headers: {
      'Content-Type': 'multipart/form-data',
      Authorization: `bearer ${TOKEN}`,
    },
  });
};

const useArticle = () => {
  const queryClient = useQueryClient();
  return useMutation(saveArticle, {
    onSuccess: () => {
      queryClient.invalidateQueries(['search']);
    },
  });
};

const NewArticlePage = () => {
  const navigate = useNavigate();
  const [titleCount, setTitleCount] = useState(0);
  const isTitleOverLimit = titleCount > 20;

  const {
    register,
    handleSubmit,
    trigger,
    formState: { errors },
  } = useForm<FormValue>();
  const addArticle = useArticle();
  const [image, setImage] = useState<File | null>(null);

  const onSubmit: SubmitHandler<FormValue> = (data) => {
    try {
      addArticle.mutate(data);
      alert(JSON.stringify(data));
    } catch (error) {
      alert('error남');
    }
  };
  • 사용자가 폼을 작성하고 제출 버튼을 클릭하면 onSubmit 함수가 호출되고, onSubmit 함수에서는 addArticle.mutate(data)를 호출하여 비동기적으로 saveArticle 함수를 실행한다. 이 함수는 서버로 데이터를 전송하고, 성공하면 onSuccess 콜백을 실행함.
  • addArticle.mutate(data)를 호출하면 비동기 요청이 서버로 전송되고 헤더에 Authorization 토큰을 추가한 다음, /posts/create 엔드포인트로 POST 요청을 보낸다.
  • 서버는 요청을 받고, FormData 객체에 있는 데이터를 파싱하여 새로운 게시물을 생성한다.

고 알고 있었는데, 자꾸 이미지 파일은 전송되지 않았다. data가 어떻게 생겼는지 보는데도 이미지 파일을 찾아 볼 수는 없었다. 그래서 테크리더에게 물어봤더니, submit 시 addArticle.mutate(...data, image)로 작성해보라고 했더니 이미지가 제대로 제출 되었다. 내가 생각한 이유는 다음과 같다.

  • addArticle.mutate(data)에서는 data가 객체 형태로 전달되는데, 이미지 파일인 image는 객체 속성에 포함되어 있지 않으므로, 서버로 전송되지 않았다.
  • addArticle.mutate(...data, image)의 경우, ...data는 객체의 속성과 값들을 분해하여 개별 인수로 전달하고, image 변수를 추가로 전달하기 때문에 이미지파일도 서버로 전송되었던 것이다.

위와 같이 생각은 해 보았지만, 아직 확실하게 정답이라는 느낌은 없어서 react query와 객체에 대해 더 공부해봐야겠다는 생각을 했다.

(2) storybook의 router 에러

react-router를 사용하는 컴포넌트인데, storybook에서 route를 찾지 못해서 발생하는 오류로, storybook에서 react-router-dom 라이브러리의 MemoryRouter를 import하면 해결되는 간단한 문제이다.
그런데 이와 관련해서 멘토님이 storybook addon react router v6과 관련해서 조사해보라고 하셨고 프로젝트 중이라 자세히 공부하진 못했지만 추후에 참고할 만한 정리글이 있어서 남겨 놓겠다.
storybook react router v6

(3) 글 상세 페이지 레이아웃

글 상세 페이지에는 크게 고려해야 할 정보값이 많았는데, 컴포넌트를 어떻게 분리해야 적당한 크기로 효율적일지 고민했었다.
결론적으로 크게는 게시글 작성자/글 상세 정보/댓글/댓글 입력창으로 구분하기로 하였다.
문제는 api 상에서 댓글은 글 상세 정보에 해당하는 데이터들이었고, 댓글 한 개를 담당하는 컴포넌트와 복수의 댓글을 관리하는 컴포넌트로 구분할 수밖에 없어서 계속 props이 하위 컴포넌트로 전달해야 하는 상황을 관리해주었어야 했다. 다른 사람들에게는 복잡할지언정 어렵지는 않은 일이었을 테지만, 리액트를 처음 접하는 나로썬 꽤나 골머리를 많이 앓았다.

(4) 글 상세 페이지 좋아요 누르기, 취소 기능
나에게 제일 어려웠던 기능이었다. 좋아요를 누르기, 취소하기 정보를 서버에 저장하는 것은 쉬웠지만 고려해야 할 점이 많았다.
1. 사용자가 특정 글에 대한 상세페이지로 이동할 때, 해당 글에 좋아요를 눌렀었는지 판단해야 한다.
: 이를 위해 사용자의 likes 배열에서 like객체의 postId가 해당 글의 id와 일치하는지 boolean 값으로 판단해야 된다고 생각했고 isPostLiked라는 변수로 판단했다. 그리고 버튼의 초기값을 isPostLiked 값에 따라 설정해 주었다.
2. 좋아요 취소 로직을 수행할 때 해당 like객체의 like id가 필요하다.
: 이 로직이 필수적이라고 생각되어 코드가 조금 지저분해졌고 다음과 같은 요상한 코드가 완성되었다.

if (user && likePushed) {
      try {
        const likeByUser = likes.find((like) => like.user === user._id);
        if (likeByUser) {
          await deleteLikePost(likeByUser._id);
        }
        setLikePushed(false);
        setLikesCount((prevCount) => (prevCount ? prevCount - 1 : likes.length - 1));
      } catch (error) {
        alert(error);
      }
    } else if (user && !likePushed) {
      try {
        await likePost(_id);
        setLikePushed(true);
        setLikesCount((prevCount) => (prevCount ? prevCount + 1 : likes.length + 1));
      } catch (error) {
        alert(error);
      }
    } else {
      alert('로그인 후에 누를 수 있습니다!');
    }
  };

좋아요 버튼의 상태에 따라 deleteLikePost함수 혹은 likePost함수가 실행되도록 if문을 사용해야 했고, 비동기 통신을 하는 두 함수 때문에 try catch문을 사용해야 한다고 생각했는데, 조급한 마음에 남용한 부분도 있었다. 게다가 관련 없어 보이는 로직들이 한 군데 묶여 있어서 코드를 이해하는 것이 어렵다.
3. 좋아요 기능 낙관적 업데이트
낙관적 업데이트는 버튼의 눌림 상태와 좋아요 수의 상태를 useState로 관리해주면 된다고 생각했는데, 좋아요 버튼을 연속적으로 누를 때 좋아요/좋아요 취소 서버 통신이 반복적으로 발생하게 되어 오류가 발생했다.
내가 생각한 해결 방법은 두 가지였다.

  • 모달
    : 에브리타임 어플에서는 좋아요를 누르면 '좋아요를 누르시겠습니까?'라는 모달이 뜨고, 확인을 누르면 '좋아요가 완료되었습니다'와 같은 메시지와 함께 페이지가 새로 렌더링된다. 모달을 적용하면 좋아요 버튼을 연속적으로 누르는 행위는 막을 수 있으나, 내가 원하는 것은 좋아요 버튼이 토글처럼 눌려서 한 게시글에는 하나 이상의 좋아요를 하지 못하고, 좋아요 취소도 간단하게 가능한 것을 원했다. 따라서 정 구현하지 못할 시 최후의 보루로 쓰겠다고 마음먹은 방법이다.
  • 클릭 디바운싱과 쓰로틀링
    : 쓰로틀링(throttling)은 마지막 함수가 호출된 후 일정 시간이 지나기 전에 다시 호출되지 않도록 하는 것이고, 디바운싱(debouncing)은 연이어 호출되는 함수들 중 마지막 함수만 호출되도록 하는 것이다. 좋아요 버튼을 누르고 특정 시간 후에 누르기 동작이 끝났다고 판단하여 그제야 서버통신을 완료 시키는 방법을 적용해보았지만 오히려 낙관적 업데이트와 서버통신 완료 후의 좋아요 상태가 일치하지 않아서 동작이 어색하다는 느낌이 들었다.

위의 두 방법으로도 안 되자 팀원분이 useMutation을 써보라고 하셨고 isLoading을 이용해 서버통신이 완료되기 전에는 서버통신 함수가 실행되지 않도록 return 시켰다.
그런데 낙관적 업데이트를 따로 해주지 않았는데도 좋아요 버튼 연속 클릭시 좋아요 수와 버튼 색상이 반영되길래 왜 그런걸까 찾아 보았더니, 브라우저에서 자체적으로 이벤트를 비동기적으로 처리하고, 브라우저는 UI렌더링을 수행한다고 한다.
그런데 이것도 확실하지 않아서 react query를 공부한 뒤 이해해야할 것 같다!

3. QA(3차 스프린트)

QA
Quality Assurance 품질보증

2차 스프린트가 끝난 뒤 배포된 서비스를 직접 테스트해보며 수정하거나 기능적으로 향상시킬 부분을 찾아보았다. 다들 기능을 개발하면서 수시로 개선점을 찾으며 리팩토링을 했으나, 모두 종합해보니 예상치 못한 문제가 많이 나왔다.

3-1. UI, UX적 측면

(1) toast메시지
: 토스트 메시지는 유저 행동의 결과를 안내 메시지로 출력해주는 기능인데, 이게 있고 없고가 사용경험에 크게 영향을 미쳤다. 예를 들어, 게시글을 삭제하거나 댓글을 달기, 로그아웃 하기 등 사용자의 확인을 필요로 하지 않는 활동들에 토스트 메시지가 없으면 '이게 제대로 수행이 된 건지..?'하는 생각이 든다. 단순히 '게시글이 삭제되었습니다'와 같은 안내 메시지가 나타나는 것만으로도 안심(?)이 되고 완성도 있는 서비스라는 느낌이 들었다.
(2) 스켈레톤
: 비동기 통신으로 인해 서버의 응답이 필요한 경우 로딩 중임을 알려줘야 했다. 로딩중일 때 빈 화면으로 두면 오류인지, 로딩 중인 건지 알 수가 없기 때문에 이를 표현하는 여러 방법이 있었다. 나는 처음에 동그란 원이 돌아가는 것(스피너)만으로도 괜찮다고 생각했지만 로딩되는 페이지와 원 크기의 차이가 너무 커서 화면이 깜빡이는 느낌이 심했다.
이를 위해 로딩이 오래 걸리는 화면에는 스켈레톤을 적용하기로 했다. 개인적으로 스켈레톤의 편리함을 잘 느끼지 못했지만, 다른 서비스를 사용하면서 스켈레톤이 어디에 나오는지, 어떤 형태로 나오는지 눈여겨보게 되었다.
(3) 낙관적 업데이트
: 서버 응답을 기다리기까지 시간이 느리므로 결과를 UI로 미리 반영하기로 했다. 내가 맡은 기능에서는 적용하지 못했지만, 프로필 페이지에서 이미지 변경 기능과 팔로우 기능에 낙관적 업데이트를 적용했다. 사용자 행동의 결과를 서버 응답 이전에 빠르게 보여주게 되니 답답하지도 않고 만족스러웠다. 생각보다 대부분의 행동은 서버의 응답을 필요로 한다는 것을 알게 되었고, 이 부분을 구현한 코드를 다시 한번 봐야할 필요성을 느꼈다.

3-2. 기능적 측면

(1) 공통 컴포넌트 분기
: 개발 단계 초기에 만들어 둔 공통 컴포넌트를 쓰다보니 계획된 행동에서 벗어나는 일에 대응을 잘 못했다. 예를 들어, 게시물 목록을 보여주는 컴포넌트를 만들어 놨고, 게시물이 없을 땐 "게시물이 없습니다"라는 문구를 보여주는 공통 컴포넌트가 있다. 근데 이것을 인기있는 게시물 목록과 구독한 사람의 게시물을 모아 볼 수 있는 페이지에서 쓰게 되면 문구를 다르게 보여줘야 했다. 따라서 다시 여러 상황들에 대한 분기 처리가 필요하게 되었다.
꾸준히 리팩토링을 했음에도 QA때 알게 된 문제였으므로 QA가 개발에 있어서 필수적이라고 생각했다.
(2) 사소한 수정
: 게시물을 무한 스크롤로 불러오고 있었지만, 게시물이 많아질수록 게시물들을 불러오는 시간이 늘어나 로딩 시간이 길어지고 있었다. 과제 마감까지 이를 해결하기 위해 팀원들이 지속적으로 고민했지만 근본적인 해결은 하지 못했다. 나도 다시 커밋기록을 보며 과정을 보고 고민해봐야겠다.

4. 프로젝트가 끝나고

4-1. 좋았던 점

개인

  • 페이지별 기능을 세분화 하여 전체 업무량을 산출하고 팀원들간의 역할분담을 빠르게 할 수 있도록 했다.
  • 스크럼을 통해 팀원들이 하고 있는 일이나 문제 상황을 공유하도록 했다.
  • 일정을 관리하여 남은 일이 무엇이 있는지 문서화하였다.
  • 모르는 기술스택이 많았으나 다른 사람의 코드를 적극적으로 참고하고 빠른 시간 내에 습득하기 위해 노력하였다.
  • 모르는 것이 있어도 일정이 늘어지지 않는 시간 내에선 끊임없이 고민했다.
  • 팀과 소통하는 데 있어서 적극적인 자세였던 것 같다.

  • 각자 역할분담이 잘 되어서 개발일정을 허비하는 일이 없었다.
  • 소통이 원할하게 되어 문제가 발생해도 빠르게 해결되었다.
  • 팀원들끼리 적극적으로 도와주어서 개발일정에 큰 변동이 없었다.
  • 일정을 문서화하는 일에 익숙해지면서 다른 팀원들이 무엇을 하고 있는지 알기 쉬워졌다.
  • 바쁜 일정 속에서도 서비스 퀄리티를 올리기 위해 새로운 것(에러 처리 등등)을 도입하였다. (내가 아직 모르는 영역이라 설명 불가능..)

4-2. 아쉬웠던 점

개인

  • 프로젝트 기획 단계에 어떤 기술스택을 쓸지 수동적이었다. 물론 뭐가 좋은지 아는 게 없기 때문에 어쩔 수 없었지만 왜 이 기술스택을 사용할 건지 세세하게 질문 했다면 팀원들에게도 재고해볼 기회가 있지 않았을까?
  • 절대적인 기술역량이 부족함을 뼈저리게 느꼈다. 프로젝트 기간에는 내가 할 수 있는 최선을 다하긴 했지만 '내가 필수적으로 필요한 인원이었나?'라는 질문에 '네니오'라고 답할 수밖에 없었다.
  • 팀원들의 pr에 코드리뷰를 상세하게 남기지 않았다. 사실 올라오는 pr은 모두 보고 코드도 눈여겨 보면서 참고도 많이 했지만 아는 게 없어서 문제점이나 개선점을 찾아주진 못했다. approve기계였다는 한계가 있다.
  • 후반으로 갈수록 코드 퀄리티에 신경쓰지 못했다.
  • 항상 도움을 받는 팀원 포지션에 있었다. 도움을 주는 팀원이 되고 싶었으나 자잘한 일에 신경쓰지 않도록 미리 처리하는 수준이었다.

  • 공통된 레이아웃을 정해놓지 않아서 각자가 구현한 페이지 사이즈 및 형태가 가지각색이었다.

4-3. 노력해볼 점

개인

  • 기술실력을 키울 것. 프로젝트 개발에 필요한 필수적인 개념을 빨리 배워야 한다. react, react query, css, js 등등. 이것이 내 문제들의 원인이라 팀원들 코드에 리뷰 상세히 하기, 도움 되는 팀원 되기 등등의 해결책이 될 것 같다.
  • 프로젝트의 환경 세팅을 어떻게 하는지 알아볼 것. 내가 전혀 관여하지 않은 부분이라 어떤 식으로 진행되는 건지 알 길이 없었다.
  • 쉽게 만족하지 않기. 프로젝트 기간이 끝나 갈 때쯤 나는 뭘 더 고쳐야 하는지 몰랐다. 완벽해보였음. 그러나 팀원들은 꾸준히 에러처리나 성능 향상을 위해 내가 모르는 무언가를 가지고 열심히 하고 있었음. 현재가 최선이 아님을 알기 위해선 역시 기술역량을 늘려야 한다...

  • 문서화에 시간 더 투자하기. 급할수록 돌아가라
  • 리팩토링에 신경쓰기.
  • 페이지 레이아웃 정해놓기

4-4. Todo

  • react 공부하기
  • react query 공부하기
  • TMI HOMERS에서 다른 팀원들이 구현한 부분 코드 살펴보기
  • 에러 바운더리에 대해 알아보기
  • react hook form 공부하기
  • storybook addon router v6 조사하기
profile
프론트엔드 개발자

1개의 댓글

comment-user-thumbnail
2023년 10월 4일

우왕 회고 킹왕짱..;;; 덕분에 저도 팀프로젝트 했던게 상세하게 떠오르네요ㅋㅋㅋㅋ굳굳 다음프로젝트도 화이팅입니당!!

답글 달기