깃허브 : https://github.com/prgrms-fe-devcourse/FEDC5_STYLED_sehee
배포 사이트 : https://styled-rho.vercel.app/
이번 한달은 그저 하염없이 팀 프로젝트를 달리기만 했다. 살면서 내가 노는거 빼고(ㅋㅋ) 이정도로 열중한 적이 있나? 싶을 정도로... 지금 다시 돌이켜보면 어떻게 내 몸에서 그런 집중력이 나왔을까 싶을 정도다. 개발을 시작하며 항상 걱정했던 게 내가 과연 개발에 의욕이 있는 사람일까.. 였는데, 이번 프로젝트를 하면서 확실하게 알게 되었다. 나는 일단 실전에 투입되고 보면 눈에 불을 켜고 하는 스타일이었다... 😈
그동안 어떤 일들이 있었고, 그 과정 속에서 무엇을 느꼈는지 차근 차근 얘기해보려고 한다!
프로젝트 기획
학교 다니면서 팀플은 수없이 많이 해봤지만, 단 한번도 기획을 이정도로 상세하게 해본 적이 없다. 하물며 돈 받고 하는 기업 프로젝트를 할 때에도 컨벤션이고 뭐고 대충~ 인수인계 받고 얼렁뚱땅 개발했다. 그렇기 때문에 사실 기획이라 하면은 대체 어떤걸 해야하는 건지 몰랐다. 그동안은 그냥 주제 정하고.. 대충 어떤 거 맡아서 할지 정하고... 끝...?
다행히 우리 팀에는 팀 프로젝트 경험이 꽤 있으셨던 인혁님 덕분에, 기획을 할 때에는 어떤 것들을 회의하고 정해야하는지 방향을 잘 잡아주셔서 그에 맞게 준비를 할 수 있었다. 주제 선정, 기술 스택, 개발 task 단위 작성, PR 및 커밋 메세지 컨벤션, 피그마 UI 설계... 이외에 정말 많은 것들에 대해 미리 자료 조사를 해오고, 계속 회의하고 결정하며... ✨우리 팀만의 규칙✨이 세워졌다!
사소하더라도 어떤 경우엔 파스칼 케이스를 쓰고, 카멜 케이스를 쓸 지 미리 약속해두니 혼란을 겪을 일이 없었다😎
개인적으로 또 좋았던 건 기술 스택을 채택하며 왜 해당 기술을 선택했는지 이유를 적어본 것이다. 우리 팀은 react-query와 zustand를 선택하게 되었는데, 특히 전역 상태 관리 라이브러리는 정말 다양한 선택지가 존재했기 때문에 많은 고민을 했다. Redux, Recoil이 그 후보에 있었는데, 처음엔 그냥 Redux가 회사에서 많이 사용되는 것 같으니 사용하면 좋겠다는 생각을 했다. 그러나 우리가 진행할 프로젝트는 큰 규모가 아니었고, 팀원들 대부분이 사용해 본 경험이 없기 때문에 기술을 익히는데 걸리는 시간도 고려해야했다. zustand는 후보 중 가장 쉽고 빠르게 적용시킬 수 있는 라이브러리 였고, 그 외 여러 장점들을 고려하여 최종적으로 zustand로 결정하게 되었다. 이 선택을 정말 잘했다고 느꼈던게, 실제로 개발하며 생각보다 전역 상태 관리를 할 것들이 많지 않았고, 사용 방법이 너무 쉬워서 거의 공부할 것도 없이 바로 적용시킬 수 있어 빠르게 개발할 수 있었다. (오히려 react-query가 생각보다 어려웠던....😭)
기획을 거의 3~5일 동안 진행하면서 음.. 이정도로 오래 걸려도 괜찮은건가? 싶었는데, 개발 후반으로 갈수록 이런 기획 단계에서 확실하게 정하고 넘어가니 이후에 서로 conflict가 일어나거나, 프로젝트 방향성에 있어서 혼란이 일어날 일이 없어 너~무 편안하게 개발할 수 있었다. 이후 커피챗을 진행하면서 세희 멘토님도 원래 기획은 일주일, 길게는 한 달 까지도 걸릴 수 있고, 당연히 기획에서 시간을 오래 투자해야 이후에 혼란을 겪지 않는다고 말씀해주셨다.
이번에 기획의 중요성을 정말x10000 많이 깨닫게 되었다....
아!! 그리고 개인적으로 또 많이 배웠던 점...
프로젝트 극초기에 어떻게 틀을 잡고 시작해야 하는지 하나도 몰랐는데, 이것도 인혁님이 방향성을 잘 잡아주셔서 어떤 것들을 먼저 수행한 다음 각자 맡은 테스크 개발을 시작하면 되는지 알 수 있었다. 특히 전역 스타일 설정, Theme 데이터 설정은 처음 알게 된 개념인데, 이거 덕분에 다들 css에서 큰 혼란 없이 통일된 UI로 개발을 할 수 있어서 좋았다...🥹 감사합니다 인혁님!!
내가 맡은 핵심 테스크
내가 맡은 핵심 테스크는 자잘한 것들 제외하고 크게 총 3가지였다.
1. 공용 API 함수 작성
2. 다이렉트 메세지 페이지 구현
3. 팔로워/팔로잉 모달 구현
1. 공용 API 함수 작성
일단 공용 API 함수를 빨리 작성해야 다른 팀원 분들이 실제 데이터를 사용할 수 있기 때문에 해당 테스크를 최우선으로 수행했다. 생각보다 해당 테스크를 진행하는데 시간이 오래 걸렸는데, 작성해야할 함수의 양도 생각보다 많았고, 사실 axios는 많이 사용해 본적이 없어서 처음부터 어떻게 설계를 해야할 지 좀 감이 안잡혔다... 그래서 초반에는 4기 분들의 레포지토리를 참고하며 어떻게 설계를 해야하는지, axios를 어떻게 활용해야하는지 감을 익혀갔다.
👀 새롭게 알게 된 것
1) axios instance
import axios from 'axios';
import { NETWORK } from '../Constants/Api';
const axiosRequestConfig = {
baseURL: import.meta.env.VITE_API_END_POINT,
timeout: NETWORK.TIMEOUT,
};
export const axiosCommonInstance = axios.create(axiosRequestConfig);
axios.create()를 통해 인스턴스를 만들면, 매번 baseURL을 작성해야하는 번거로움을 해결할 수 있었다. baseURL은 .env 파일에 위치시켰고, VITE에서 환경 변수를 사용할 때는 변수명 앞에 VITE_를 붙여야 했다. (<-이것도 새롭게 알게 되었다..)
그리고 axios instance는 baseURL 뿐만 아니라, interceptors를 설정할 수 있다.
2) interceptors
사실 초반에 API 함수를 어떻게 작성해야할 지 끙끙 앓았던 건... 바로 토큰을 헤더를 어떻게 넣어서 보내지?!?!? 라는 고민 때문이었다. 왜냐하면 사용자는 크게 일반 사용자(전체)와 인증된 사용자(부분)로 나뉘었고, 인증된 사용자만 사용할 수 있는 기능은 로그인 후 발급받은 토큰을 헤더에 넣어서 요청해야 한다고 API 명세서에 적혀있었기 때문이다.
그렇게 방법을 계속 찾아보다가... interceptor 개념을 알게 되었다!
interceptor : 가로채는 사람. 요청과 응답을 가로챌 수 있다.
그 말인 즉슨, interceptor을 사용하면 request를 중간에 가로채서 header에 토큰을 넣어줄 수 있다는 말이다. 우와 완전 신세계...😯
import { InternalAxiosRequestConfig } from 'axios';
import { AUTH_TOKEN_KEY } from '@/Constants/Api';
/**
* 로그인 하면 토큰을 발급 받고, 인증이 필요한 경우 통신 직전에 header에 발급 받은 토큰을 넣어줍니다.
* 임시로 세션스토리지 사용 중, 추후에 변경 가능성 있습니다.
*/
const setAuthorization = (config: InternalAxiosRequestConfig) => {
const accessToken = JSON.parse(sessionStorage.getItem(AUTH_TOKEN_KEY) || '');
if (accessToken) {
config.headers.Authorization = `Bearer ${accessToken}`;
}
return config;
};
...
axiosAuthInstance.interceptors.request.use(setAuthorization, (error) => {
console.error(error);
return Promise.reject(error);
});
그래서 나는 전체 사용자 / 인증된 사용자를 나누어 axios instance를 2개 만들고, 인증된 사용자의 경우 요청할 때 마다 interceptor을 이용해 로그인할 때 세션 스토리지에 저장해둔 토큰을 헤더에 넣어주도록 설정했다!
개인적으로 아쉬웠던 건, axios instance를 1개만 만들어서도 설계가 가능할 것 같은데(4기 분들 레포지토리 보니까 1개로 하신 분들도 계셨음..)그 방법까지 이해하려면 시간이 너무 오래 걸릴 것 같아서 일단 2개로 설정하게 되었다. 추후에 이 부분에 대해서도 더 공부해서 1개로 처리할 수 있도록 리팩토링 해보고 싶다.
💦 신경 썼던 점들
1) 상수화를 통해 휴먼 에러 방지
상수화는 바로 수현님께 전수 받은 스킬...🫶 이번에 한 번 적용해보았는데 초반에 시간은 엄청 걸렸지만 확실히 이번처럼 작성해야할 API 함수의 규모가 클 수록 상수화를 해둔 것이 빛을 발하게 되었다. 생각보다 작성할 API 함수도 너무 많았고, 작성 후에도 여러 번 검수를 하면서 get, post, put 등을 잘못 적었는지 체크하는데에도 시간이 꽤 걸렸는데 ENDPOINT에 대해서는 전부 상수 처리해두니 오타 걱정할 일이 없어서 훨씬 관리하기가 쉬웠다! 만약 문제 생겨도 Constants 파일에서 한번만 수정하면 되니까 깔끔....✨
2) JSDoc 활용하여 다른 팀원들이 혼란 없이 사용할 수 있도록 돕기
API 함수 명세서를 보면 생각보다 헷갈리는 것들이 많았다. 예를 들면 명세서에서 메세지 관련 request시 필요로 하는 파라미터로 어떨 땐 receiver, 어떨 땐 sender인데 둘 다 실질적으로 상대방의 id를 의미한다던가.... 이런 건 사실 백엔드에서 조금 더 명확하게 해준다면 좋겠지만, 이번 프로젝트는 정해진 API 내에서 프론트엔드 개발자끼리 협업해야 했기 때문에, 내가 사용한 방법은 JSDoc을 활용하여 해당 함수를 사용할 팀원들의 혼란을 최대한 줄여주는 것이었다.
JSDoc은 예전에 문동욱 멘토님께서 해당 함수를 사용하면서 주의해야할 점을 적어준다던가 그런 용도로 활용해보면 좋다는 얘기를 해주신 적 있는데, 이 커피챗 내용을 머릿 속에 묵혀놓다가(ㅋㅋ) 이번에 처음 제대로 사용하게 되었다. 그러면서 param, brief, todo 등 여러 태그의 용도에 대해서도 새롭게 알게 되었다.
내가 사용한 태그와 목적
@brief 간단한 함수 설명
@detail 주의해야할 점이나 조금 더 정보가 필요한 부분에 대해서 보충 설명
@param 파라미터에 대한 설명(타입스크립트를 사용하고 있으므로 타입은 생략)
@return 리턴 값에 대한 설명
@exception 예외 사항에 대해 설명
@todo 추후에 해당 함수를 어떻게 리팩토링 할 것인지 기록
사용 예시
JSDoc으로 미리 정리해둔 덕분에 나중에 함수를 사용할 때에도 큰 혼란없이 사용할 수 있어서 매우 도움이 되었다!
그리고 비동기 통신 스킬에 대해 더 많이 배울 수 있는 좋은 경험이었다👍
2. 다이렉트 메세지 페이지 구현
처음 테스크를 가져갈 때 뭘 할지 고민하다가 다이렉트 메세지 페이지를 가져왔다. 3학년 때 소켓 프로그래밍을 배우면서 Java와 Swing을 이용해 카카오톡 모방 채팅 프로그램을 만들어본 적이 있는데, 그 때 내가 무언가의 이슈로 개발에 크게 번아웃이 왔고(근데 사실 번아웃이고 자시고 일단 실력도 안좋았음...ㅋ) 그래서 다른 팀원 친구가 클라이언트를 거의 다 만들어주고.. 난 서버만 만들고 결국 B를 받은 기억이 있었기 때문이다... 그 때의 아쉬움이 남아서인지 이번에 한 번 다시 제대로 채팅 관련 페이지를 만들어보고 싶었다!
😵💫 어려웠던 점?
1) API에서 내려주는 데이터 이해하기...
일단 페이지의 기본적인 레이아웃을 만들고, 그 다음에 데이터를 바인딩했다. 근데 이 데이터를 가져오는 과정에서 생각보다 긴 시간이 소요되었다... 바로 API 명세서에 자세히 나와있지 않은 내용에 막힌 것이다.
Conversation의 저 id 배열은 뭘 의미하는지 명세서에 나와 있지 않아서, 일단 하드 코딩으로 메세지를 보낸 다음 Conversation을 조회해봤는데 그냥 메세지를 나눈 두 사람의 아이디가 들어있는 배열이었다... 그리고 sender와 receiver는 당연히 sender가 본인(로그인 한 사용자), receiver가 해당 유저와 대화 나눈 사람일 줄 알았다. 근데 그게 아니라 마지막으로 보낸 메세지의 sender와 receiver를 의미했다. 그니까 만약 마지막 메세지를 상대방이 보낸거라면 sender가 상대방이고 receiver가 본인이라는거다. ㅇ..예...???? 🤯 (머리 터짐)
이거 말고도 아까 위에서 말한 message 관련 API 함수의 파라미터명이 명확하지 않다던가... 하는 문제에 대해 일단은 JSDoc에 보충 설명을 적어두긴 했지만, 이런 것들이 명세서에 조금 더 자세히 설명이 되어있다면 시간 소모가 훨씬 덜 되었을텐데... 하는 아쉬움이 남았다. 다음 3차 프로젝트는 백엔드 분들과 소통하며 개발할 수 있으니, 이런 상황이 생기지 않도록 꼭! 적극적으로 소통을 해야겠다는 생각이 들었다.
2) react-query 첫 사용
react-query를 사용하게 된 이유는 다음과 같다. 서버에서 주고 받는 데이터를 좀 더 쉽게 관리를 할 수 있는데, react-query에서 해당 데이터에 대한 loading, success 관련 상태도 제공해줄 뿐만 아니라, refetch 주기를 설정하는 등 다양한 기능을 제공해주기 때문이다. 그리고 데이터 캐싱 기능, 오래된 데이터는 자동으로 get해주기도 한다. 이번에 다이렉트 메세지 페이지를 만들며 처음으로 사용하게 되었는데, 생각보다 개념이 어려워서 공부하는데 3일 정도 시간이 소요되었다. 그래도 실제로 사용해보니 왜 해당 라이브러리를 사용하는지 장점을 알게 되었다.
react-query는 크게 useQuery, useMutation 2가지를 사용할 수 있고, get이 전자, 나머지는 후자를 이용하면 된다. useQuery에서는 다양한 옵션을 제공해주는데, 내가 주로 사용한 옵션은 enabled, refetchInterval이다.
enabled: 쿼리가 자동으로 실행될 지 여부를 정한다.
ex) enabled: !!id // id가 존재할 때만 쿼리 요청을 한다.
refetchInterval: 데이터를 자동으로 몇 초마다 다시 refetch해올지 설정한다.
ex) refetchInterval: 2000 // 2초마다 자동 refetch
특히 이번에는 소켓 통신이 아니라 일반 API 호출로 메세지를 주고 받아야 했기 때문에, 저 refetchInterval 옵션을 통해 완벽하진 않더라도 얼추 실시간 대화가 가능하도록 만들 수 있었다.
그리고 isLoading 상태를 활용해 데이터를 불러오는 동안에는 스켈레톤 UI가 동작하도록 만들었다.
여기서 문제가 발생했다. 느린 3G 환경에서 이전 사용자와의 대화 화면에서 다음 사용자와의 대화을 불러올 때 순간적으로 이전 사용자와의 대화가 남아서 보이는 현상이 생겼다. 처음엔 원인을 몰랐는데 계속 공식 문서와 자료를 찾아보며 이게 react-query에서 제공해주는 데이터 캐싱때문에 일어난 일이라는 것을 깨달았다. 데이터 캐싱이 오히려 이런 상황에서는 에러처럼 동작하게 된 것이다... 그래서 아무리 receiver가 바뀔 때마다 refetch를 요청해도 클릭 직후에 아직 해당 요청이 대기 중일 때는 이전 데이터가 보여지게 되었다. 이를 해결하기 위해서는 캐시를 삭제해줘야했기 때문에, removeQueries 함수를 이용하여 해당 쿼리의 캐시를 제거해준 뒤 refetch를 요청했다.
// 선택한 채팅방이 달라질 때마다 messages를 다시 가지고 온다.
useEffect(() => {
if (receiver?._id) {
queryClient.removeQueries({ queryKey: [QUERY_KEYS.MESSAGES] });
messagesRefetch();
}
}, [receiver, messagesRefetch]);
이렇게 하니 캐싱된 데이터가 삭제되면서 바로 isLoading 조건이 충족되어 스켈레톤 UI가 즉각적으로 나타나게 할 수 있었다!
확실히 기술에 대한 이해도가 많이 부족하면 이런 문제 상황을 해결하는 과정이 오래 걸리는 것 같다. 이제 처음 react-query를 사용해보았으니, 앞으로도 리팩토링 하면서 더 공부하고 상황에 필요한 옵션들을 적절히 사용해보려고 한다.
3) 시간 및 날짜 표기, UTC 변환
시간 정보는 Message객체의 createAt를 활용했는데, 문제는 UTC 기준이라 한국 시간으로 변환을 해줘야했다. 해당 기능은 세희 멘토님이 적극 추천해주신 day.js 라이브러리를 통해서 쉽게 구현할 수 있었다. 시간 변환 관련 로직은 해당 페이지 이 외에도 모든 페이지에서 사용될 로직이므로 Utils/UTCtoKST.ts 에 파일을 만들어 분리했다.
여기서 어려웠던 건 날짜 표기였다. 메세지는 메세지마다 옆에 시간을 붙여주면 되므로 그냥 변환만 해주면 끝이지만, 날짜는 날짜가 변할 때만 한 번 표기해줘야 했기 때문이다.
const Date = ({ index, messages }: DateProps) => {
// 채팅 날짜가 바뀔 때마다 위에 날짜를 표기해준다.
const isNewDay = (prevDate: string, nextDate: string) => {
return convertUtcToKstDate(prevDate) !== convertUtcToKstDate(nextDate);
};
return (
<>
{index === 0 && (
<StyledDate>
{convertUtcToKstDate(messages[index].createdAt)}
</StyledDate>
)}
{index - 1 >= 0 &&
isNewDay(messages[index - 1].createdAt, messages[index].createdAt) && (
<StyledDate>
{convertUtcToKstDate(messages[index].createdAt)}
</StyledDate>
)}
</>
);
};
많은 고민 끝에... messages.map을 하며 제공해주는 index를 활용하기로 했다. 왜냐하면 이전 데이터와 비교해서 날짜가 달라졌는지를 체크해야했는데, 그럴려면 index 값을 가져와서 messages[index-1]로 이전 데이터의 정보를 가져와야 한다고 판단했기 때문이다. index가 0인 경우는 대화의 첫번째 메세지므로 무조건 날짜를 표기해주었다. 이후엔 날짜가 달라졌는지 체크하며 이전 메세지 데이터와 날짜가 다른 경우에만 날짜를 표기하도록 만들었다.
💦 신경 썼던 점
1) 파일 분리
다이렉트 메세지 페이지는 크게 ConversationList, MessageList가 존재한다. 그리고 각 list에는 item이 존재한다. 이를 고려하여 적절히 컴포넌트를 분리하여 해당 컴포넌트 파일에는 관련된 로직만 존재하도록 만들었다. 덕분에 계속 리팩토링을 하면서도 관련된 로직만 한 눈에 보이니 유지보수가 훨씬 편했다.
2) 디바운싱 최적화, 재사용 로직 분리
메세지 작성 모달을 만들면서, input change 이벤트가 발생 할 때마다 유저 검색 API가 호출되므로 디바운싱을 적용하여 성능을 개선했다. 추후에 팔로잉 모달에 적용된 디바운싱도 로직이 매우 유사했기 때문에 재사용할 수 있도록 useDebouncedSearch 커스텀 훅을 만들었다. 덕분에 겹치는 코드를 없앨 수 있었고 필요한 로직만 파일에 남아 코드를 읽기 쉬워졌다.
🤔 고민 중인 것
로딩 시간에 따라 스켈레톤UI 적용 유무를 결정하고 싶다. 느린3G 환경과 실제 환경에서 여러번 테스트 해보면서 느낀 점인데, 확실히 느린 3G 환경에서는 스켈레톤 UI가 유의미하게 동작한다. 그러나 실제 환경에서는 빠른 로딩 덕분에 스켈레톤 UI가 거의 0.1초 급으로 스치듯이 지나가게 되는데, 그럴 때마다 오히려 스켈레톤UI가 불편하게 느껴졌다.
이 정도면 차라리 아예 안 나오는게 거슬리지도 않고 더 보기 좋아보이는데...?
그래서 로딩 시간에 따라 스켈레톤 UI를 적용할지 말지를 결정하고 싶다는 생각이 들었다. 관련 내용을 찾아보니 방법이 존재하는 것 같아, 추후에 리팩토링을 하게 된다면 해당 내용을 꼭 반영해보고 싶다.
참고 URL : https://tech.kakaopay.com/post/skeleton-ui-idea/
3. 팔로워/팔로잉 모달 구현
아이고 힘들다 이제야 여기까지 왔네..
개인적으로 모달 관련된 UI는 익준님이 잘 만들어주신 덕분에 레이아웃 자체는 손쉽게 구현할 수 있었다. 문제는 로직이었는데... 뭐 다이렉트 메세지 쪽에서 겪은 문제랑도 비슷하고 로직도 비슷하다. 디바운싱도 유틸에 잘 분리 시켜놓으니 여기서도 유용하게 사용할 수 있었다. 여기서 어려웠던 건, 다이렉트 메세지 페이지에서는 로그인 한 유저(본인)의 id값만 처음에 알고 있으면 되는데, 여기서는 유저 데이터 내부의 followings, followers 데이터를 계속 알아야했기 때문에 데이터가 변동될 때마다 로그인 한 유저의 데이터를 refetch 해와야 했다. 그리고 팔로잉 중인지 체크 하는 로직도 좀 복잡했던 걸로 기억하는데 아무래도 프로젝트 막바지에 개발한 기능이다보니 어려워도 그냥 뇌빼고 뚝딱뚝딱 만들었던 것 같다... 😇
아 그리고 팔로우 데이터는 아예 자체적인 id로 관리 되어서 팔로우 버튼을 연타하면 팔로우 데이터가 중복으로 존재할 수 있었다... 이건 추후에 QA를 진행하면서 발견했고, 급한대로 디바운싱을 적용해 해결했다. 근데 세희 멘토님한테 의견을 여쭤보니 이런 상황에서는 디바운싱 보다는 쓰로틀링이 더 적절하다고 하셨다. 디바운싱은 연속으로 호출되는 함수 중 가장 마지막 함수를 호출하는 것이고, 쓰로틀링은 마지막 함수가 호출된 후 일정한 시간동안 다시 호출되지 않도록 하기 때문이다. 둘의 개념의 차이를 잘 몰랐는데, 어떤 상황에서 사용하는 것이 더 적절한지 생각해보게 되는 계기가 되었다.
아무튼 그렇게 이번에도 PR을 올렸는데...
다들 갑자기 칭찬해주셔서 기분은 너무 좋은데... 근데 내가 무슨 최적화를 잘한건지 잘 모르겠어서 계속 의문을 가졌다... 그냥 평소대로 코드 작성한 것 같은데 왜지..??
나중에 수현님 만나서 같이 코드를 살펴보는데, 아마 디바운싱도 그렇고 useEffect를 잘 활용해 필요할 때만 데이터를 fetch 해오도록 만들어서 그런 것 같다고 하셨다. 음 근데 그건 그냥 기본으로..? 당연하게? 생각해서 잘 몰랐던 것 같다. 아니 이렇게 적으니까 잘난 척 하는 것 같네 ㅁㅊ... 그런건 아닌데 아무튼... 다들 감사합니다 ... ㅠㅠ 제가 늘 최적화를 고려하며 코드를 작성할 수 있는 사람이 되었다고 생각해도 되는거겠죠..? 🥺
커뮤니케이션
개발 이외에도 팀 프로젝트를 진행하며 정말 중요한 요소가 커뮤니케이션이다. 우리 팀은 코어 타임이 끝나도 디스코드에 자주 상주(?)했기 때문에 실시간 소통도 굉장히 잘 이루어졌고, 슬랙도 굉장히 잘 사용했기 때문에 큰 문제 없이 빠르게 소통이 이루어졌다. 매일 아침마다 스크럼을 진행하면서 논의해야할 안건, 문제 상황 공유를 진행했는데, 아침 9시마다 입이 안 벌어져서 너무 힘들었지만... 익준님이 매일 적극적으로 스크럼을 진행해주신 덕분에 잘 굴러갔던 것 같다. 정말 감사합니다 흑흑... 확실히 매일 진행 상황을 공유하니 개발의 방향성을 잃지 않았고 덕분에 마감 기한 3일 전 모든 테스크 완료라는 뿌듯한 상황을 맞이할 수 있었다!
조금 아쉬웠던 건 분명 우리는 매일 스크럼을 진행했는데 이걸 증명할 기록이 남아있지 않다. 아침에는 너무 피곤해서 글을 쓰기가 힘들었다... 라는 핑계를 대고 싶지만, 확실히 우리 팀이 프로젝트 진행하며 고민했던 것들과 해결했던 방법들이 글로 남아있지 않아 아쉽다는 생각이 들었다. 우리 진짜 잘 해결했는데... 이걸 알릴 방법이 없네 💦💦 그래도 슬랙엔 많이 흔적이 남아있어서 다행이다! 다음 팀 프로젝트 때는 대충이라도 러프하게 기록해보는 습관을 들이면 좋을 것 같다.
사실 나는 질문을 막 던지는 스타일은 아니다. 최대한 내가 알아볼 수 있는 만큼 알아보고 신중히 질문하는 편이기도 하고.. 괜히 물어보지 않고 내가 판단한대로 바로 행동할 때도 있었다. 그래도 팀 프로젝트에서는 그런 단독 행동은 최대한 지양해야겠다고 생각해서, 최대한 의식 해서 단독적으로 행동하지 않고 팀원들에게 의견을 물어볼려고 노력을 많이 했다. 우리 팀은 다들 질문에 친절하게 답변해주고, 친밀한 분위기가 형성 되어서 그런지 질문하는 것이 그렇게 어렵지 않았다. 역시 이건 바퀴벌레 포커의 힘...?ㅋㅋㅋㅋㅋㅋ
코드 리뷰에서도 활발하게 의견을 나누고, 수용하고... 가끔은 디스코드로 바로 의견 조율도 하고, 다방면에서 잘 소통이 된 것 같다. 일단 모든 팀원들이 프로젝트 개발에 열정을 갖고 계셨고(이런 적 처음임;), 다들 책임감도 뛰어나셔서 가능했던 것 같다. 아까도 말했듯이 모든 팀원들이 정말 책임감있게 임해준 덕분에 우리는 마감 3일 전에 모든 공식적인 테스크를 끝마칠 수 있었고, 남은 시간동안 여유롭게 배포 및 QA를 진행할 수 있었다. 세희팀 최고!
갑자기 너무 감사해져서 적는 팀원들 칭찬...🙇♀️
익준님: 넘나 조용했던 저희였지만 끝까지 잘 이끌어주셔서 너무 감사합니다. 익준님 덕분에 저희 팀이 훨씬 즐겁고 편안한 분위기가 형성되어서 다들 의견도 편하게 나눌 수 있었던 것 같아요. 저희가 말 안해도(ㅜㅜ) 의견 물어봐주셔서 감사합니다. 테스크도 많이 가져가셨는데 기한안에 전부 다 만드시는 거 보고 감탄을 금치 못했습니다....
승민님: 제 2년전의 삶을 되돌아보게 되었습니다... 어떻게 이렇게 금방 컴포넌트를 뚝딱뚝딱 만들어낼 수 있는지 너무 대단하다는 생각 밖에 안들었어요. 전 그나이 때 스스로 코드를 작성하기 힘든 수준이었는데... 암튼, 지금 충분히 잘하고 계셔서 앞으로의 미래가 너무 기대되네요... 그리고 바퀴벌레 포커 때 너무 웃기고 귀여웠어요. ㅎㅎ
재웅님: 막판에 css 다 바꿔주셔서 너무 감사합니다... 사실 저도 디자인 은은하게(많이) 신경쓰였는데 css 능력자신 재웅님이 바꿔주신다고 해서 속으로 기쁨의 소리를 질렀습니다.. 미적감각 너무 배우고 싶어요. 그리고 코드리뷰 할 때마다 느꼈는데 늘 완성도도 높고 코드가 깔끔하셔서 굉장히 만족하며 리뷰를 했었네요...
인혁님: 초반에 기획단계에서 인혁님에게 정말 많은 것들을 배웠습니다. 덕분에 저도 지금 1차 팀원 분들과 팀 프로젝트 기획하는데 큰 도움이 되고 있습니다. 그리고 인혁님도 테스크 정말 많이 가져가셨던 걸로 아는데 기한 안에 이 모든걸 스뭇스하게 만드시는 걸 보고 감탄 22.. 정말 많이 배웠습니다 감사합니다 ㅜㅜ
느낀점 및 다음에 개선하고 싶은 점
전반적으로 만족스러운 팀 프로젝트였다!
확실히 커뮤니케이션이 잘 이루어지니 개발 과정이 너무 수월했고 결과물도 좋게 나와서 만족할 수 밖에 없었던 것 같다.
다만 아쉬웠던 점은 초반 기획서 단계에서 멘토님이 지적해주신, 우리 프로젝트 만의 특색이 없다.. 라는 점인데, 확실히 다른 팀들의 결과물을 보니 정해진 API 내에서도 특색있는 결과물을 많이 만들어 낸 것 같아서 놀랐다. 우리 팀은 아무래도 API 제한을 좀 크게 걱정해서 기획 단계에서 많이 사렸는데, 좀 더 도전해봐도 좋았을 것 같다는 아쉬움이....ㅜㅜ 다음 3차 프로젝트는 조금 더 자유롭게 기획할 수 있으니, 그 때는 좀 더 특색있는 서비스를 만들어보고 싶다!
그리고 eslint 무작정 에어비앤비 설정하지 말고 필요한 것만 커스텀해서 적용하기. API 상 _id 라는 변수가 늘 존재했는데, 변수명 앞에 언더바 사용하지 말라는 eslint 경고가 뜨며 컨벤션이 부딪히는 현상이 벌어지기도 했다... 아 이거 말고도 은근 스트레스 받는 규칙들 있어서 (빨간줄 ㅡㅡ) 왜 기술 스택 채용할 때 근거를 확실하게 해야하는 지 깨달아버렸다... 다음엔 필요한 것만 골라서 사용하자!
마지막으로 에러 핸들링에 대한 아쉬움이 남아있다. 이전에 문동욱 멘토님과의 커피챗에서 에러 핸들링에 관한 이야기를 인상 깊게 들었는데, 아무래도 console.error를 사용하면 사용자에게도 노출 되므로 sentry 같은 것을 도입하면 좋다는 내용이었다. 그리고 에러가 발생하면 단순히 어떤 에러가 발생했는지.. 개발자에게만 필요한 정보를 알려주는 것 보다는, 사용자에게 어떤 해결 방식을 안내 해줘야 하는지도 더욱 고민해보고 싶다.
이번 프로젝트에서는 메세지 데이터 불러오는 데 실패하면 Alert으로
메세지 전송 중 네트워크 오류가 발생했습니다. 새로고침 후 다시 시도해주세요.
같은 메세지를 출력하도록 만들었는데, 이게 최선인지? 그리고 해당 컴포넌트에만 국한 시키는 게 아니라 전역적으로 에러 핸들링을 강화하면 좋을 것 같기도? ErrorBoundary 개념도 있던데 이것도 알아보고 싶다.
마무리
내용이 많이 길어졌는데... 그래도 적고 싶었던 내용은 많이 담은 것 같다. 나중에 생각나면 더 추가해야지.
이번 팀 프로젝트 기간 동안 2차 팀원 분들께 너무 감사했고, 이번에 새로 만나게 된 3차 팀원분들 다들 너무 반갑습니다. 마지막까지 모두가 만족할 수 있는 결과를 이룰 수 있으면 좋겠습니다! ㅎㅎ