- Published on
후원자 관리 회고록
- Authors
- Name
- Na Hyunwoo
목차
서론
프로젝트 명
후원자 관리
기간
2023.06 - 2023.08 (3개월)
목표
- 기존 Ruby로 되어있던 후원자 관리의 완전한 개편
- 앞으로 텀블벅 핵심 기능이 들어갈 new repo 생성
내 역할
- 후원자 관리 FE 전담
목표와 결과
설정한 목표와 이를 달성한 정도
설정한 목표
- 목표된 3개월에 쉽고 확장성있는 프로젝트의 성공적인 배포
달성한 정도
- 100%
사용한 기술 스택
- Next.js, Typescript, React Query, Tailwind CSS, Zustand, Shadcn, Radix UI(partial), React Hook Form, Sentry, Segment
성취한 점
성공적으로 마친 작업 혹은 해결한 문제
- 기존 후원자 관리는 Ruby라는 언어로 되어있었다. 그리고 10년정도 된 legacy였기 때문에 코드의 확장성이 굉장히 떨어졌다. 이는 텀블벅에서 새로운 feature를 개발하는데 큰 장애물이 되었다.
- 따라서, 확장 가능하고 쉬우며 fresh한 후원자 관리 웹 애플리케이션의 완전한 리모델링이 필요했다.
- 결과적으로는 계획된 시간 안에 쉽고 확장성있는 프로젝트를 만들 수 있었다.
이를 가능하게 만든 기술적/팀워크적인 요인
- 모든 프로젝트의 성공에서 가장 중요한 것은 이루고자 하는 마음이다. 그리고 그 이외에 것은 다 부차적인 것 일 뿐이다.
- 어려운 문제가 발생하면 종이에 문제를 옮겨 적었다. 문제를 시각화 하면 해결될 확률이 올라간다.
- 생각나는 해결 방안을 전부 적었고, 좋은 해결방안이 떠오르지 않을때면 인터넷에서 좋은 레퍼런스를 찾기위해 노력했다.
- 스스로 정리되지 않거나 의문이 생기는 것들은 팀원들과 상의하였고, 상의하는 과정에서 스스로 깨닫거나 해결의 실마리를 얻은 경우가 많았다.
어려웠던 점 및 개선할 부분
프로젝트 중 겪은 어려움과 해결 방법
- 테이블 조회
- 프론트에서 테이블을 구현할 때 데이터 조회를 구현하는 방법에는 두 가지가 있다. 첫 번째 방법은 테이블 렌더링시에, 모든 데이터를 가져오는 것이다. 두 번째 방법은 조회가 필요한 페이지의 데이터만 가져오는 것이다.
- 후원자 관리의 경우 인기가 많은 프로젝트는 후원자 수가 수 만명씩 될 수 있다. 만약 한번에 모든 데이터를 가져오는 방식을 사용하게 된다면, 최악의 경우 데이터를 가져오는 데에만 수 초에서 수십 초 까지 걸릴 수 있다. 따라서 의심의 여지 없이 조회하려는 페이지의 데이터만 가져오는 방식을 사용해야 했다.
- 거기에 더해 테이블 조회에 속하는 필터는 총 10가지였다. 필터의 종류에는 리워드, 결제 상태 라던가 sort하는 방식 그리고 검색과 같은 것들이 있었다. 즉, 엄청나게 복잡하게 상태를 관리해야 한다는 의미였다.
- 이를 해결하기 위한 방식은 두가지가 있었다. 첫 번째는 전역 상태 관리 라이브러리(zustand)로 관리하는 것이고 두 번째는 url의 query로 상태를 관리하는 것이였다.
- 나는 두 번째를 선택했다. url로 상태를 관리하게 될 경우 복잡한 상태의 선택값들을 눈으로 볼 수 있기 때문에 복잡성을 낮춰준다고 생각했다. 그리고 store를 추가하지 않아도 된다는 점과 새로고침을 했을 때에 필터 값이 유지된다는 장점이 있다고 생각했다. 결과는 아래와 같다.
- 테이블 선택
- 사용자는 테이블에 있는 데이터 즉, 유저들을 선택해서 자신이 원하는 벌크 액션을 행할 수 있다. 예를 들어서, 613명의 유저 중 50명을 선택한 뒤에 메시지 발송을 할 수 있는 것이다.
- 이 기능을 구현하려 했을 때, 적잖이 당황했다. 왜냐하면 테이블 데이터를 페이지 단위로 가져오는데, 만약 전체 선택을 통해서 아직 가지고 있지 않은 데이터를 사용자가 접근하려 하면 어떻게 하지? 라는 생각 때문이었다.
- 이 문제는 골치가 아팠지만 한순간의 번뜩이는 아이디어로 해결할 수 있었다. 바로 flag를 사용하는 것이었다.
- 예를 들어서 유저가 전체 선택을 눌렀다면 isSelectAll이라는 flag를 사용하는 것이다. 그러면 사용자가 선택한 데이터가 담긴 store를 selectedData라고 가정했을 때, selectedData의 모든 data값을 넣을 필요 없이 isSelectAll일 경우에는 모든 데이터가 선택되었다고 생각할 수 있는 것이다. 여기서 한가지 더 중요한 key point는 데이터를 store에 저장할 때, 모든 value를 넣는 것이 아니라 id와 같은 unique한 key을 사용하는 것이다. 이는 react에서 선택된 리스트를 상태에 저장할 때 중요한 지점이다. (참고: Avoid duplication in state)
- 테이블 선택 기획이 계속 바뀌었던 문제
- 원래는 테이블 전체 선택이 위와 같은 형태가 아니였다. 기존에는 table header에 있는 checkbox를 클릭하면 gmail과 같이 table 최상단에 전체 선택할 것이라는 toggle이 떴었다. 나는 이 UX가 직관적이지 않다고 생각했다. 왜냐하면 전체 선택의 depth가 두개였기 때문이다. 나는 기본적으로 컴포넌트는 하나의 기능만 하는것이 가장 좋다고 생각한다.
- 그래서 이 UX/UI에 대한 논쟁이 일주일 정도 논의되었고, 다른 개발자들과 운영팀의 의견을 수렴하는 과정이 있었다.
- 결과적으로는 가장 기본적이면서도 general한 형태인 전체 선택과 전체 해제 버튼을 추가하는 것으로 종결되었다.
- 기획이 여러번 뒤집히는 과정 속에서 약간의 스트레스가 쌓였지만 작업을 잘 쪼개서 진행하였기 때문에 반영된 사항을 rollback하고 새로운 feature를 만드는 것은 크게 어렵지 않았다.
- 오히려 개발이 간단해져서 좋아진 점도 있었다.
- 종속성 문제
- 로컬 환경에서는 build가 잘 되는데, production에서 build가 계속 실패하여 배포가 안되는 문제가 있었다. 처음에는 node version 문제인가 의심했었기 때문에 서버와 모든 버전을 동기화 하는 과정을 거쳤다. 그럼에도 불구하고 문제는 해결되지 않았다.
- 그래서 직전 배포때와 차이점에 대해서 고민해보다 새로운 library를 추가한 것이 생각나서 package-lock.json을 지웠다 새로 깔았더니 서버에서 나는 에러와 똑같은 에러가 나더라 (유레카 🙌)
- 그래서 문제를 잘 해결할 수 있었다.
- 이참에 yarn-berry를 도입해서 종속성 지옥에서 벗어날 수 있었으면 좋겠다. (bun도 써보고 싶다..)
- 401 unauthorization
- 후원자 관리 페이지 진입시에는 최초에 유저를 검증한다. 그래서 검증되지 않은 유저라면 로그인 페이지로 redirect 시킨다. 그리고 로그인을 성공하면 redirect 시킨 url로 다시 redirect가 된다.
- 그리고 로그인 페이지에서는 쿠키를 가지고 있으면 자동으로 로그인 처리를 한다.
- 여기서 문제가 발생했다. 어떤 유저가 자신의 페이지가 계속해서 무한 로프를 돈다고 버그 리포트를 한 것이다.
- api 오류를 까보니 accessibility를 검증하는 api에서 cookie가 안담겨오고 있었다.
- 아니 cookie가 있어서 로그인이 처리가 됐는데 cookie가 어떻게 없지(?)
- 그래서 관련되어 있는 BE 파트 분들과 얘기를 나눴는데, 그순간에 url에 찍혀있는 www가 눈에 들어왔다.
- 즉, 문제의 원인은 tumblbug.com에서 로그인을 하여 cookie가 있지만, www.tumblbug.com으로 진입하면 다른 domain으로 인식되서 cookie가 안담기는 것이다 !
- 그래서 BE에서는 www.tumblbug.com으로 진입 시에 tumblbug.com으로 redirection 하게끔 처리를 했다.(alb를 통해 처리)
- 그리고 FE에서는 redirection시에 www를 제거하는 방어 로직을 추가하였다.
- 여기서 왜 load balancer를 통해 처리했냐는 의문이 들 수도 있다. (좋은 지적이다!) 그 이유는 로그인 페이지와 후원자 관리 페이지가 서로 다른 프로젝트에 속해 있었기 때문이다. 이렇게 서로 다른 프로젝트에 있지만 url을 기준으로 각각에 맞는 페이지를 보여주는 것을 Micro Frontend에서 Universal Unified SPA이라고 하더라.
- 운영 단계에서 발생하는 이슈 트래킹에 어려움을 느낌
- 위에서 발생한 401 unauthorization 이슈를 트래킹하는 과정에서 FE단에서 이슈를 트래킹 할 수 있는 수단이 없었다. 이슈 트래킹 툴이 BE에만 있었기 때문이다. 기존에 비용 문제로 인해 Sentry를 달았다 제거했다고 하셨다.
- 비용 문제가 중요한 이슈이긴 하지만, 적어도 새로운 production이 나가는 시점에는 반드시 issue tracking tool이 필요하다고 생각했다. 그래서 Sentry 도입 허락을 구했고 수락이 떨어진 직후 바로 Sentry를 도입했다.
- Sentry 도입 이후 에러 트래킹이 훨씬 쉬워졌다. 그리고 유저의 행동까지 영상으로 보여져서 너무 신기했다.
- 다행히도 이후에는 운영에서 이슈가 없었고 안정기에 들어갔다고 판단했다.
- Sentry가 차량의 블랙박스와 굉장히 유사하다고 느꼈고 당분간은 계속해서 트래킹 해야겠다고 생각했다.
기술적 인사이트
새로 배운 기술이나 패턴
- Suspense
- React Error Boundary
- Headless UI (Radix UI, Shadcn)
- React Query의 고도화된 사용
- MSW
- Sentry
- Universal Unified SPA
기술적인 도전과 이를 어떻게 극복했는지
- SSR 적용 실패
- 후원자 관리 페이지 진입시에 접속을 시도하는 유저가 검증된 유저인지, 해당 프로젝트에 접근할 수 있는 유저인지와 같은 Authorization을 SSR로 구현 시도하였다 .
- local에서 잘 빌드되었으나 stage(production 직전 branch) 배포시 SSR page의 asset prefix가 안붙어 페이지가 안보이는 문제가 있었다.
- 해결이 빠르게 될 문제가 아닐 것으로 보였고 page를
<Authorizaion>
컴포넌트로 감싸는 방식으로 CSR로 충분히 해결할 수 있는 문제였기 때문에 CSR로 처리하였다. - FE만의 문제는 아닌 것으로 판단되었고 CDN과 관련된 문제로 보였기 때문에 도입에 실패하였다. 그러나, 기회가 된다면 꼭 도입할 예정이다.
- Infra를 잘 모르니 디버깅에 어려움을 느꼈고 Infra 공부의 계기가 되었다.
팀워크와 커뮤니케이션
팀 내에서의 커뮤니케이션 상황
- 매주 프로젝트 위클리를 진행하였다.
- 이슈 발생시 원인과 예상 해결 시간에 대해 바로 바로 공유하였다. 관련해서 PO분의 긍정적인 피드백도 있었다.
협업 도구나 방법론
- Slack과 Jira를 통해 테스크들이 투명하게 공유되었다.
마무리
프로젝트를 통해 얻은 교훈이나 느낀 점
- 한가지 자랑하고 가고 싶은 것이 이번 광고 플랫폼과 후원자 관리 프로젝트의 성공적인 결과로 인해 엔지니어 성과 1등을 하였다. 👍
- 이번 프로젝트 진행에 앞서 앞선 프로젝트(광고 플랫폼)에서도 새로운 프로젝트 개발에 참여했었다. 그래서, 다음 작업은 새로운 프로젝트 개발에 참여하는 것이 아니라 텀블벅 운영 이슈를 해결해야 하겠다고 생각하고 있었다. 우리 FE 팀에서 연차가 가장 높은 분이 후원자 관리를 담당하길 희망하셨고 나 또한 그게 좋겠다고 생각했다. 그러나 다음날 CTO인 록님께서 내가 후원자 관리를 맡아주었으면 좋겠다고 말씀하셨다. PO, PM분들의 요청이 있었던 것 같기도 했고, 내가 초기 스타트업 출신답게 제품 개발에 강점을 가지고 있다는 것을 알고 내가 잘하는 것을 하게 해주신 것이 아닐까 싶다. 결론적으로 나를 인정해주셔서 감사했고 그 의미에 보답하기 위해 더 열심히 했다.
- 3개월짜리 긴 프로젝트를 혼자 전담해서 했음에도 무리 없이, 그리고 운영 배포 이후에도 큰 이슈 없이 진행되었다는 점에서 스스로 많이 성장했다고 느꼈다.
- SSR 도입 실패 이후에 디버깅 하는 과정에서 인프라 관련 지식이 부족해 디버깅에 어려움을 느꼈다. 그래서 웹 환경 전반에 대한 공부와 텀블벅의 인프라를 공부하는 계기가 되었다. (현재 진행중이다)
- 앞서 진행한 프로젝트와 비교했을 때, 훨씬 좋은 퀄리티의 제품이 나온 것 같아 기쁘다. 이번 프로젝트가 텀블벅의 레거시들을 옮기는 repo로 사용될 예정인데, 시작을 잘 한 것 같다.
- 이번에는 굉장히 많은 모달과 해당 모달에서의 Use Case를 다루면서 이를 Radix UI로 구현했다. 정확히 말하면 Radix UI를 한번 더 custom한 Shadcn을 사용했다고 할 수 있다. Radix UI의 간단함과 확장성의 감탄을 금치 못하였다. 앞으로 하는 프로젝트에 전부 도입하고 싶었으나 컴포넌트에 종속성이 생기게 되므로 팀원들과 얘기를 해봐야겠다.
- SSR도입 시도를 하면서 Next.js와 많이 가까워졌다고 느꼈다. 그와 동시에 SSR이 굉장히 까다롭다고 느꼈고 FE로서 advance 레벨로 가려면 devops 영역까지 알아야겠다 싶었다.
- 이번에 프론트 리드분이 새로 오셨는데 원래 BE를 하시던 분이라 전문 지식이 폭넓게 있으셔서 배울점이 많아 기쁘다. 팀 차원에서 핵심적으로 수정이 필요한 부분들을 손봐주시고(CI/CD) 나의 개발 시야를 넓혀주셔서 감사하다.
다음 프로젝트에서 적용하고 싶은 것들 + 그대로 가져가면 좋을 것들
- 파이프라인 구성
- 인프라 파이프라인에 비효율적인 것들이 많다고 직감적으로 느끼는데 이 부분에 관련해서 학습한 뒤에 production에서 적용해서 CI/CD 비용을 크게 줄일 수 있었으면 좋겠다.
- SSR
- SSR이 항상 옳은 것은 아니지만, 분명히 강점이 드러나는 순간이 있을 것이다. 특히 로그인 로직 처리와 같은 부분이 그러한데, 관련 개념을 잘 파악해서 다음 프로젝트에 필요한 순간이 온다면 꼭 도입하고 싶다.
- MSW
- 처음 MSW를 도입했을 때, MSW를 사용하면 실제 API를 연동할 때 latency가 0이겠지 ? 라는 암묵적인 기대감을 가지고 도입을 했었다. 그러나, MSW로 적용된 API를 실 API로 연동하는 과정에서도 꽤나 이슈가 많았다. 예를 들어 스펠링과 같은 휴먼 에러와 같은 것들이 그러하다. 그럼에도 불구하고 MSW를 사용하면서 느꼈던 가장 큰 강점은 에러 처리를 할 때였다.
- FE에서 critical한 에러는 실제 데이터를 주입하는 과정에서 발생할 확률이 가장 크다고 생각한다. 그래서 에러 처리를 잘 하는 것이 중요한데 그 에러 처리를 잘하기 위해 이번에 React Error Boundary를 도입한 것이였다. 그 과정에서 MSW를 통해 API 호출에 따른 여러 에러들을 발생시킬 수 있었고, 그에 맞는 분기 처리를 할 수 있었기 때문에 MSW의 강점을 찾은 것 같았다. 그래서 MSW는 계속 가져가는 것이 좋을 것 같았다.
- Sentry
- 운영 단계에서 발생하는 예상치 못한 에러를 트래킹 하는 과정에서, 사용자의 정황을 기준으로 어느정도 원인을 파악하고 이슈를 해결할 수 있다. 그러나, 모든 문제가 그러하지는 못하고 때로는 굉장히 원인을 추적하기 어려운 경우가 있는데 (이번 401 unauthorizaion 문제) 이러한 경우에 문제를 정확히 파악하고 근본적으로 문제를 해결하기 위해서 sentry와 같은 에러 트래킹 도구의 도입이 반드시 필요하다고 생각했다.
- 비용적인 문제가 있지만, 적어도 production 배포 최초 이후부터 안정화 전까지는 반드시 필요하다고 생각한다. 그리고 sentry는 에러가 발생한 부분의 코드에 대해서도 알려주지만, 에러가 발생한 경과즈음에 영상도 같이 제공한다.(자동차의 블랙박스 같다고 느꼈다) 나는 sentry의 이러한 기능에 감탄했고 안정적인 운영을 위해 반드시 필요하다고 느꼈다.
- Suspense, React Error Boundary
- 이번 프로젝트에는 page 진입시에 호출하는 api가 여러개가 있었다. 유저 경험을 위해서 api 호출시에 loader(혹은 skeleton)를 필수적으로 제공해야 한다고 생각하는데 api를 여러번 호출하면서 loader를 여러 번 호출하게 되면 loader가 연속적으로 끊기면서 등장하게 된다. 이런 경험이 좋지 않다고 느꼈고 해결 방법을 알아보던 찰나에 react18에서 안정화된 Suspense를 접하게 되었다.
- 현재 FE에서 SSR이 굉장히 중요해지고 있는 만큼 Suspense의 역할도 확대될 것으로 보았다. (SSR로 rendering되는 page를 Suspense로 감싸면 loader가 동작한다. 이는 Suspense를 사용하지 않으면 구현 불가능한 부분이다.)
- 그 이외에도 Suspense를 통해 기존에
와 같이 처리되던 부분을if (isLoading) return <Loader /> return <Something />
과 같이 선언전인 코드를 작성할 수 있게 해준다. 여기에 더해 React Error Boundary까지 사용한다면<Suspense fallback={<Loader />}> <Something /> <Suspense/>
과 같이 사용할 수 있고, 여기서 더 나아가서 FallbackComponent를 추상화해서 여러가지 에러 코드에 대해 추상화를 진행할 수 있다. 이는 코드를 선언적이게 해주고 코드를 읽는 사람의 부담을 줄여준다.<ErrorBoundary fallback={<FallbackComponent />}> <Suspense fallback={<Loader />}> <Something /> </Suspense> </ErrorBoundary>
- Shadcn, Radix UI
- 사이드 프로젝트로 자린고비라는 애플리케이션을 만들면서 처음으로 Shadcn을 접했다. 그 사용 경험이 너무 좋았기 때문에 실무에 적용해봐도 좋을 것이라 생각했다. 팀원들을 설득했고 부분적으로 사용해봐도 좋을 것 같다는 의견이 있었다. 그래서 Modal에 한해 도입을 해봤다. (이렇게 부분적으로 도입할 수 있다는 것 또한 Shadcn의 큰 장점이다.)
- Shadcn의 경우 큰 공수를 들이지 않고, 수준 높은 컴포넌트를 사용할 수 있다. 확장성이 뛰어나기 때문에 텀블벅 디자인 시스템에 맞게 스타일을 바꾸었고, 동작들도 이에 맞게 바꾸었다.
- 회사에서 디자인 시스템을 꾸릴 인력이 부족하다면 해당 라이브러리를 도입해서 많은 자원을 아끼는 것도 굉장히 좋은 선택지라고 생각한다.
return ( <CenterModal open={isEnterWaybillModalOpen}> <CenterModalContent> <CenterModalHeader>운송장 입력</CenterModalHeader> <EnterWaybillModalBody /> <EnterWaybillModalFooter /> </CenterModalContent> </CenterModal> )
- 사용 예시는 위와 같고, 내가 좋아하는 Compound Component Pattern을 사용하기 때문에 사용하는데 있어서도 엄청난 유연성을 자랑한다.
- React Query(Tanstack Query)
- 개인적으로 React Query는 혁신이라고 생각한다. 내가 사용한 라이브러리 중에서 딱 하나만 사용할 수 있다고 한다면 나는 고민의 여지없이 React Query를 고를 정도로 이 라이브러리를 좋아한다. React Query를 통해 서버 상태를 다른 상태들과 분리할 수 있다. 그리고 커스텀 훅의 형태로 서버 상태를 관리할 수 있고, 전역에서 접근할 수 있으며 캐싱이 이뤄진다.
export default function useProjectBackers() { const router = useRouter(); const size = DEFAULT_PAGE_SIZE; const { backerName = null, id = null, notificationStatus = null, page = 1, paymentStatus = null, rewardDeliveryStatus = null, rewardUuid = null, sortField = null, sortOrder = null, uuid = null, } = router.query as TUrlQuery; const { data, isFetching, refetch } = useQuery({ queryKey: [ 'projectBackers', page, rewardUuid, paymentStatus, rewardDeliveryStatus, notificationStatus, id, uuid, backerName, sortField, sortOrder, size, ], queryFn: () => getProjectBackers({ page, size, rewardUuid, paymentStatus, rewardDeliveryStatus, notificationStatus, id, uuid, backerName, sortField, sortOrder, }), keepPreviousData: true, refetchOnWindowFocus: false, suspense: true, useErrorBoundary: true, refetchOnMount: false, }); return { data, isFetching, refetch } as { data: TProjectBackers; isFetching: boolean; refetch: () => Promise<QueryObserverResult<TProjectBackers, unknown>>; }; }
- 실제로 내가 사용했던 Custom Hook중 하나이다. queryKey에 parameter를 넣어주면 해당 params가 바뀔 때마다 data가 자동으로 fetching된다.
- 위에서 얘기했던 테이블 조회에 사용되는 10가지 filter들을 key로 넘겨준다. 그러면 useRouter hook을 통해 가져온 url의 query가 변경될 때마다 해당 값을 가져와 자동으로 fetching이 일어난다. 너무 대단하지 않은가 ?!??
- 그리고 Suspense와 React Error Boundary와의 호환성도 좋아 저렇게 옵션값을 넣어주면 해당 기능들을 사용할 수 있다. 그리고 error 발생시 동작을 커스텀 훅 내부에서 지정할 수 있기 때문에 해당 로직도 분리할 수 있다는 큰 장점이 있다.
const { isLoading, mutate } = useMutation({ mutationFn: confirmRewardSendComplete, useErrorBoundary: true, onSuccess: () => { refetch() setIsOpenModal(false) addToast( <div className="font-label-regular-md flex flex-col items-center justify-center py-5 px-6 "> <IconCheck className="h-5 w-5" /> <p>선물 전달 완료 처리 되었습니다.</p> </div> ) }, })
- 그 이외에도 위의 사용 예시처럼 onSuccess option을 통해 요청 성공시에 동작을 해당 훅안에서 처리할 수 있다는 큰 장점이 있다.
- 데일리 스크럼
- 이번에는 프로젝트 단위의 데일리 스크럼을 진행하지 않았는데, BE에서 작업이 투명하지 못하다고 느껴질 때에면 답답함이 조금 있었다. 이럴 때에 직접 물어보긴 했지만, 5분정도의 데일리 스크럼을 통해 이런 부분을 완전히 해소할 수 있다고 생각했기 때문에 다음 프로젝트에는 데일리 스크럼 도입을 추진 해야겠다.
- Impact 측정
- 텀블벅에서 여러 굵직한 프로젝트를 진행하면서, 내가 한 일이 얼마나 임펙트 있는 일이지? 하는 의구심이 들었다. 물론 회사 차원에서 내가 진행했던 프로젝트들이 너무나도 중요한 일이라고 인지하고 진행하였지만, 이것들이 정량적으로 측정 가능해야 한다고 생각했기 때문이다.
- 물론 PO쪽에서는 나름의 우선순위와 중요도를 가지고 판단하겠지만, 나는 이에 대한 공유가 부족하다고 느꼈다. 그와 동시에 임펙트 측정에 대한 부분이 개발하는 입장에서도 중요하다고 생각했기 때문에 공유를 요청드렸다. 그리고 실제로 임펙트 측정을 위한 정량적 지표들이 부족하다고 느꼈고, 이에 대해 요청을 드렸더니 다음부터는 이와 관련된 문서를 투명하게 공개하겠다고 하셨다.
부록
- Github Insights
코드량이 절대적인 지표는 아니지만 그래도 해당 프로젝트에 대부분을 기여한 것 같아 기쁘고 대견하다 .