들어가며
작년 10월 말부터 신규 프로젝트에 참여하게 되었다. 야놀자, 인터파크, 트리플 3개 회사를 통합하면서 회원 체계, 그리고 로그인 시스템도 통합한다는 장대한 꿈의 프로젝트였다. 요즘 추세에 맞춰 간단한 UI와 과정으로 회원의 상태를 전환할 수 있게 하기로 하였기에 혼자서 진행하기로 했다. 하지만 내용이 점점 더 많아지고, 많은 경우의 수를 대응하자는 요청이 있었다. 결국 팀원 한명이 더 투입되어 FE는 2명이 개발하게 되었다.
업무 방식에서도 차이가 있었다. 기획에서 모든 플로우를 정하는 것이 아니라, 디자이너가 유저 플로우를 담당하는 방식으로 진행되었다. 유저 친화적이라는 장점도 있지만, 개발 측면에서 고려되지 않은 내용들이 다수 추가됨에 따라 FE에 업무적 압박감이 더욱 커졌다. 가장 문제가 되었던 것은 보존하지 않기로 하지 않았던 상태 데이터를 이용해 UI를 구성했던 것이다. 기획서를 보고 데이터를 구조화하고 준비하던 API와 달리, FE는 디자인 가이드를 보고 준비하여 서로 어긋나기 시작한 것이다. 결국 쿼리 파라미터를 이용해 지속적으로 유저의 상태를 이어가게 하였다. 그러다보니 코드 리뷰나 구조화 등은 오히려 더 족쇄가 되었고, 얼기설기 이어진 API와 FE의 로직들은 변화에 취약해졌다.
굉장히 불만족스러운 결과물이 만들어졌지만, 어떻게든 일정 내에 런칭하는데는 성공했다. 후회보다는 어떤 점을 잘 했는지, 어떤 점이 부족했었는지, 그리고 어떤 점을 배웠는지 고민하여 적어둔다. 앞으로 진행할 프로젝트에서는 훨씬 더 나은 결과물을 내리라 다짐한다.
잘한 점과 못한 점
1. 업무
잘한점
협력
야/인/트 소속이었던 개발자들과 함께 하나의 목표를 가지고 합을 맞춰 개발했다. 이렇게 완전히 다른 소속의 개발자들과 협업한 경험은 처음인데, 생각보다 문제도 충돌도 없이 진행되었다.
전혀 다른 개발자들과 개발하기 위해서 먼저 서로의 서비스 상태와 용어 파악이 필요했다. 예를 들어, 로그인을 위해 웹뷰가 아닌 실제 브라우저 어플리케이션을 실행하는 방식을 활용하였는데, 이렇게 실행된 브라우저를 외부 브라우저나 크롬 브라우저, 인 앱 브라우저 등 제각각의 이름으로 부르고 있었다. 개발에 앞서 이 브라우저를 인 앱 브라우저라고 부르기로 정했다. 이렇게 한번 정하니 의사소통하는데 어려움이 없고, 서로의 의도를 단번에 파악할 수 있었다. 용어를 맞춰가며 구현된 현황을 파악하니 자연스럽게 이 프로젝트에서 필요한 웹뷰 브릿지 함수에 대한 의견이 모아졌고, 액션 아이템으로 도출하여 중요도에 따라 순차적으로 개발을 진행할 수 있었다.
PoC를 통한 선행개발
본 개발에 앞서 어려움이 예상되는 부분을 개발해봄으로써 전체적인 구조를 잡고 난제를 해결했다. 이 부분이 가장 이질적이며 어려웠던 부분이다. 문제가 발생한 원인은 단순하다. 소셜 로그인 때문이다.
이런 상황은 회원 통합 서비스에서 오히려 더 큰 어려움으로 다가온다. 각각의 앱에 등록된 서비스와 클라이언트 아이디가 다르기 때문에 단순한 방법으로는 로그인을 시도한 유저가 같은 회원임을 알 수 없다. 특히 이 프로젝트는 합병 예정이지만 법적으로는 아직 구분된 회사라는 특이한 상황에서 개발을 시작하게 되었다. 그렇기 때문에 소셜 서비스에서 지원하는 서비스 그루핑을 할 수 없다는 문제가 생겼다(구글/애플은 그런 기능을 지원하는지조차 알 수 없었다). 또, 기존 로그인 기능을 제거하는 것이 아니라 유지하면서 신규 서비스의 로그인을 지원해야 한다는 점도 중요한 점이었다. 이미 기기에 등록된 소셜 서비스의 클라이언트 아이디가 있다면 이것을 상황에 따라 동적으로 교체할 수 없기 때문이다.
이런 문제들을 해결하기 위해 네이티브 SDK(Android/IOS)가 아니라 웹뷰(JS SDK)로 로그인하고자 했다. 별 문제 없어 보였지만 테스트 도중 또 다른 문제를 발견하게 되었다. 보안 이슈였다. 웹뷰는 조작이 가능한 브라우저이기 때문에 소셜 로그인을 허용하지 않는 경우가 있던 것이다.
결국 인 앱 브라우저라는 특이한 환경을 활용하게 되었다. 일종의 샌드박스 브라우저인 웹뷰가 아닌 실제 웹 브라우저인 크롬이나 사파리 브라우저를 여는 방식인데, 앱에서 생명주기와 값을 컨트롤 할 수 있는 웹뷰와 달리 완전한 외부 브라우저라는 것이 큰 차이점이다. 외부 브라우저이므로 당연하게 웹뷰 브릿지 함수를 사용할 수 없게 되었고 이 문제를 해결하기 위해 다시 앱으로 돌아올 수 있는 딥링크를 만들어야 했다.
만약 PoC를 진행하지 않았다면 이런 복잡하고 잘 사용되지 않는 기술에 대해 미리 검토하지 못했을 것이다. 특히 미리 개발하여 TF 인원들에게 예시 화면을 공유함으로써 필요한 기능에 대해 다시 한번 확인할 수 있었고, 실행 결과를 영상으로 남겨둠으로써 당시 상황을 재현할 수 있었다. 특히 PoC를 진행하면서 어떻게 문서 작업을 해야 하며 어떤 것들에 우선순위를 두어야 하는지 알게 되었다. 이 프로젝트에 있어 아주 중요한 과정이었다고 생각한다. 이 경험은 앞으로도 큰 도움이 될 것이다.
못한점
일정 관리
사실 리더가 아닌 실무자라는 입장상 다른 팀에 일정을 강요할 수 없다. 하지만 그럼에도 강요했어야 했다는 생각이 든다. 개발 기간에 들어갔음에도 최상위 결정권자의 의사와 엣지 케이스의 발견 때문에 기획과 디자인이 계속해서 바뀌게 되었다. FE에서 사용할 수 있을법한 API가 나오기까지는 너무나 오랜 시간이 걸렸다.
느지막히 나온 API는 BFF라고 보기 어려운 수준이었다. 하나의 액션을 수행하기 위해 여러 API를 연속으로 불러야 하는 문제가 가장 두드러졌는데, 트랜잭션을 관리할 수 없는 FE에서 이런 상황이 발생하면 말 그대로 아무 것도 할 수 없는 상황에 처하게 되었다. 또, 화면 구성에도 큰 문제가 생겼다. 늦어지는 API를 기다릴 수 없으니 먼저 화면을 개발해두었는데, 개발이 끝난 화면이 불필요해지기도 했고 중간중간 필요한 화면이 추가되기도 했다. 정상적인 작업이 불가능한 상황이 이어졌다.
늘 그렇듯 일정이 끝나갈수록 이슈에 대한 모든 초점은 FE로 집중 되었다. 일정에 대한 압박과 불완전한 API를 기반으로 만들어낸 결과물은 '단단하지' 않았고, QA 중 발견한 엣지 케이스에 무너져 내리기 시작했다. 몰아치는 이슈를 해결하고자 야근도 계속 하게 되었다. 완전한 일정 관리 실패였다.
개발 리딩
믿고 맡기는 것은 오만함일 수 있다는 점을 배웠다. '당연히' 잘 하리라 생각한 부분에서도 많은 결점이 있었고 이것은 전체적인 완성도를 떨어뜨려 '단단하지 않은' 프로젝트가 되었다. 예를 들어, 앞서 설명했던 PoC에서 진행한 인 앱 브라우저를 이용한 로그인도 코드 자체의 난이도에 어려움은 없었기에 간단히 설명만 해주었지만(당연히 관련된 문서는 공유했다), 웹뷰와 인 앱 브라우저의 쿠키가 공유되리라 생각한다거나, 세션에 무엇인가를 저장해두려는 등 정상적인 동작이 불가능한 코드가 작성되어 있었다.
흔히 이런 문제를 막기 위해 PR을 제시한다. 동감하지만 현실적으로 가능한지에 대해 의문이 생긴다. 너무나 많은 회의(정말 하루 종일 회의를 했다)로 시간이 없고 지쳐있는 상황에서 코드를 보니 너무 많은 부분을 놓쳤다. 큰 문제 중 하나는 타입 선언이었는데, 인터페이스를 이중 상속받는 등 개발 초기에 절대로 해서는 안되는 개발을 했다는 사실을 개발 마지막 주에 발견했다. 이미 엎질러진 물이기에 더이상 수정은 불가능했기에 어쩔 수 없이 그대로 두고 개발하였지만 다양한 엣지 케이스를 대응하지 못해 엉망 진창의 타입 아닌 타입 선언으로 변하고 말았다.
결국 내가 더 강하게 API 우선순위에 대해 의견을 제시하고 리딩했다면 이런 문제들이 없지 않았을까 하는 고민을 하게 되었다. PR을 조금 더 신경 썼더라면 어땠을까. 미리 개발 방향에 대해 제시하고 큰 그림을 그려줬다면 어땠을까.
이어서 진행되고 있는 다음 작업은 아주 철저하게 준비를 하고 있다. 가장 먼저 한 일은 각 페이지가 어떤 동작을 해야 하는지 서버와 클라이언트 레벨에서 철저하게 작성하기 시작했다.
이번 프로젝트를 진행하며 알게 되었는데, PHP/JSP처럼 서버와 클라이언트에서의 동작을 명확하게 구분하여 개발하는 것을 '신세대' FE 개발자는 잘 하지 못하는 것처럼 보였다. XHR을 이용한 클라이언트 사이드에서의 요청을 선호하고 자주 사용해왔기에 회원 가입과 같은 페이지 단위의 플로우 개발에 약한 모습을 보이는 것 같다. 다음에 비슷한 일을 하거나 학습에 대해 조언을 구한다면 이렇게 작성한 페이지 단위에서 어떤 로직을 어떻게 수행해야 하는지 작성하여 가이드할 것이다.
2. 기술
못한점
신규 기술 도입
기술을 도입한 것이 문제가 아니라, 제대로 파악하지 않고 도입한 것이 문제였다. 더군다나 다른 프로젝트에서 사용하지 않던 기술을 도입함으로써 이슈 대응에 너무 긴 시간이 소요되었다.
올드함의 끝을 달리는 Beanstalk를 사용하던 다른 프로젝트와 달리, 사내 EKS 시스템을 사용함에 따라 전체적인 시스템 구성이 변하게 되었다. 생각치도 못한 로깅 시스템도 걸림돌이었다. Datadog을 사용하기 위해서는 사내에서 제공하는 라이브러리를 이용해야 했는데 어처구니없게도 Express.js 용으로만 만들어져 있어 Trace가 유실되는 문제를 발견했다. 나는 이를 해결하고자 꽤나 긴 시간을 들여 직접 Datadog과 연결을 설정하였는데, 커스터마이징해서 쓰지 말라는 경고를 받아 그냥 되던 말던 모르겠다는 식으로 마무리할 수 밖에 없었다.
배포 시간에도 문제가 있었다. 사내의 FE 프로젝트는 모노레포로 구성됨에 따라 빌드에 큰 리소스를 사용하고 있다. 모노레포가 문제가 아니라 정책상 빌드 캐시를 적용하지 않는 점이 문제다. 캐시를 사용하지 않으니 매번 모든 패키지를 다시 빌드하는데, 이 때문에 너무 긴 시간을 소모한다. 게다가 다른 패키지에서 사용하는 라이브러리도 도커 이미지에 포함되어 엄청나게 큰 용량을 가지게 된다. 배포 이후 dockerfile을 수정하고 next의 standalone 모드를 사용하는 등 아주 간단한 튜닝으로 빌드 시간을 크게 줄여냈지만 일정상 개발 도중에는 이런 문제를 자세하게 파악하지 못했고, 전체적인 개발 생산성을 크게 떨어뜨렸다.
Next v15 버전으로 버전을 올린 것도 문제였다. 14버전으로 시작했지만 개발 도중 15버전으로 버전을 올렸는데, 기존에 사용하던 기능들이 정상 작동되지 않는 것을 확인하지 못해 시간 낭비로 이어졌다. 하나 예를 들자면 instrumentation 파일 이슈가 있었다. Next config의 pageExtension을 설정하여 사용하고 있는데, 15버전부터 instrumentation이 정식 도입되면서 페이지 파일로 구분되었다. 14버전에서는 pageExtension을 사용하더라도 instrumentation.ts 파일로 사용하는 것과 다른 부분이다. 개발하던 도중 버전을 올려 instrumentation의 코드가 제대로 작동되지 않고 있다는 사실을 파악하지 못했고, 전혀 다른 부분에 수정을 가해 시간을 낭비했을뿐 아니라 코드의 질도 떨어뜨리게 되었다.
개발 리딩 실패
기술적으로도 개발 리딩에 실패했다고 생각한다. 자신 있다고, 할 수 있다고 말한다고 해서 정말 믿고 맡기는 것이 정말 옳은 것일지 의문이 든다. 내가 직접 하면 1시간으로 끝날 작업이 2~3일 소모될 수 있다는 것이 문제다. 대답을 믿지 않았어야 했던 것일까.
예를 들자면, IOS에서 발생했던 '간헐적으로 로컬 스토리지에 접근 되지 않는' 문제가 있었다. 새 창 혹은 새 웹뷰를 열고, 새로 열린 웹뷰에서 결과를 로컬 스토리지에 저장한 후 창을 닫아 원본 페이지로 돌아가는 로직이 있다. 돌아온 원본 페이지에서는 저장된 값을 꺼내기 위해 setInterval로 로컬 스토리지를 polling하는 로직이 구현되어 있었다. 그런데 간헐적으로 로컬 스토리지의 함수들이 모두 먹통이 되는 문제가 있다는 것이다.
팀원은 이 문제를 해결하기 위해 며칠간 앓고 있었다. 이야기를 듣고 잠시 후 나는 IOS가 보안 이슈로 기능을 차단한다고 판단했다. 로컬 스토리지에 접근하는 것은 엄연히 Disk IO에 해당하는 기능인데 이걸 지속적으로 부르고 있다면 이건 아주 이상한 일이다. 팀원은 이 문제를 해결하기 위해 로컬 스토리지를 사용하던 로직을 쿠키로 변경했고, 쿠키 특성상 실시간 업데이트가 잘 되지 않으니 방어 로직이나 설정 시점을 변경하는 등 엉망이 되고 있었다.
눈치챘겠지만 심지어 이 구현 자체가 잘못 되기도 했다. 새 창에서 데이터를 변경했을 때 처리하기 위해 polling을 시도한 것인데, visibility change 이벤트와 postmessage를 사용하면 간단하게 해결된다. 내가 신경쓰지 않는 동안 다른 길로 두발자국 걸어가 고민하고 있었던 것이다.
내가 더 빠르다고, 잘 안다고 일을 빼앗아 하는 것은 분명 잘못되었다. 동료의 성장에도 도움이 되지 않는다. 하지만 그렇다고 해서 믿고 맡기는 것만이 옳은 것일까? 일정 내에 산출물을 내야 하는 상황이라면 성장할 수 있도록 맡기고 기다리는 것이 성공할 수 있는 길일까? 나는 아직 답을 내지 못하고 있다.
잘한점
웹뷰 브릿지 신규 개발
같은 웹 페이지를 3사 서비스 앱에서 사용하게 됨으로써 공통적으로 사용할 수 있는 신규 웹뷰 브릿지 라이브러리를 개발해야 했다. 기존에 사용하던 라이브러리와 크게 다르지 않지만 몇가지 신경써서 개발한 부분이 있다. 첫번째는 커맨드 형식의 웹뷰 브릿지다. 웹뷰가 시작될 때 앱은 웹뷰의 window 객체에 하나의 함수를 등록하고, 함수 내에서 문자열 작성된 커맨드에 따라 다른 기능을 실행하는 것이다. 별거 아닌 것처럼 보이지만 버전이 다를 때에도 오류가 발생하지 않는다는 큰 장점이 있다. 두번째는 응답 처리다. 웹뷰 브릿지의 경우 실제로는 함수 실행에 대한 응답을 받을 수 없다. 네이티브 시스템에서 호출을 받는 것 뿐이지 실제로 연동된 함수가 아니기 때문이다. 이를 위해 네이티브에서 함수를 실행한 후, 실행할 웹의 콜백 함수의 이름도 함께 전송하도록 했다. 기존에도 있던 기능이지만 나는 여기에 비동기 대기 로직을 추가했다.
웹뷰 브릿지 함수 자체를 async로 만들고, 네이티브에서 콜백 함수를 실행했을 때 웹뷰 브릿지 함수를 resolve 하도록 한 것이다. 이런 간단한 방법으로 웹뷰 브릿지 함수가 항상 응답을 보장하는 함수가 되었다.
state를 활용한 소셜 로그인
소셜 로그인은 CSRF 토큰 확인을 위해 state라는 값을 전송할 수 있다. 이것이 표준 스펙화 되어있다는 점을 발견했는데, PoC 도중 이 state를 조금 다르게 사용하는 아이디어를 얻었다.
현재 상태, 혹은 이동해야 하는 URL 등을 포함하여 직렬화-암호화하고 state로 전송하면 항상 동일한 URL로 이동해야 하는 소셜 로그인에서(로그인 완료 URL을 등록하므로) 유저 상태에 따라 액션을 분기할 수 있겠다고 생각한 것이다. 이 아이디어는 위에서 설명했던 인앱 브라우저 로그인에서 활용할 수 있었다. 유저가 단순 로그인하려고 했던 것인지, 계정 통합을 위해 무엇인가 인증받던 상황인지 등을 state에 담는 것이다. 세션을 유지할 수 없는 인앱 브라우저에서도 쿼리로 전달받은 state로 다양한 정보를 이어받을 수 있게 되었다. 이것과 관련된 내용은 이 글에서 조금 더 풀어 설명한다.
레거시 시스템과의 융합
신규 회원 서비스에 맞춰 모든 서비스의 회원 로그인 방식을 바꾸기는 불가능에 가깝다. 그래서 기존 시스템과 신규 시스템을 함께 사용할 수 있도록 개발하는 것도 중요한 포인트였다. 나는 이런 레거시 시스템과 함께 사용할 수 있도록 기존 로그인 정보를 확인하는 세션과 신규 로그인을 위한 토큰 시스템을 동시에 업데이트 하도록 조정했다. 특히 기존 방식 그대로 회원 정보를 사용하면서, 신규 토큰 정보를 갱신할 수 있도록 gateway 어플리케이션에 토큰 갱신 로직을 포함한 것이 유효했다고 생각한다. 다른 서비스는 회원 시스템 변경에 영향을 받지 않고 자연스럽게 신규 회원 서비스를 이용하게 되었다.
3. 배운점
태스크 관리
개발 작업을 어떻게 관리할 것인가에 대해 조금 더 배울 수 있었다. 특히 진행해야 하는 작업을 어떻게 공유해야 하는지에 대해 많은 고민을 하게 되었다. 이런저런 이야기를 나누다 보니 FE에서 필요한 API 명세를 작성해두는 것이 낫겠다는 생각을 하게 되었다. 어떤 API가 필요하고 응답이 어떤지 적엄으로써 각 페이지와 컴포넌트가 어떻게 구성될 것인지를 미리 적어두는 것인데, API 개발자가 이 내용을 보면 그대로 만들어주지 않더라도 필요한 값에 대해서 인지할 수 있다.
아직 이것이 더 나은 방법인지 장담할 수 없다. 하지만 최소한 끌려다니는 개발에서 벗어날 수 있을 것이라고 기대하고 있다.
역할 분담과 코드리뷰
오랫동안 합을 맞춰왔거나 경험이 많은 개발자가 아닌 경우 명확한 지시가 필요함을 알게 되었다. 사람마다, 역량에 따라, 그리고 상황에 따라 다르겠지만, 알아서 잘 하는 것을 기대하는 것은 과한 기대일지도 모르겠다. 또, 어떻게 코드 리뷰를 해야 할지에 대해 더 고민하는 기회를 가지게 되었다. 지금까지 나는 개인의 '개성'이 될 수 있는 코드에 대해서는 별달리 코멘트를 하지 않는 편이었다. 하지만 그것이 그 사람의 '개성'이 아니라 잘못된 방향으로 가고 있는 전조일 수 있다는 것을 알게 되었다.
이해하고 의도한 것이 아니라, 단순히 이렇게 하면 된다고 추천받은 코드일 수 있기 때문이다. 이렇게 작성된 코드는 직면한 문제만을 해결하게 된다. 나는 이제 의도한 작성한 것인지, 아니면 다른 방법을 찾지 못해 차선책을 선택한 것인지 물어보고 더 나은 방향을 제시할 것이다.
후임자를 어떻게 가이드할 것인가
앞서 적었던 것처럼, 오히려 올드한 개발 방식에 대해 가이드 해주어야 하는 상황이 있었다. 정말 간단해서 당연히 알고 있을 것이라 생각하고 있거나, 혹은 알고 있다고 말하더라도 자주 사용하지 않아 익숙하지 않은 개발 방식인 경우가 있을 수 있다. 이런 경우도 앞서 계속 말해온 문서를 작성하며 조금 더 자세하게 개요를 적는 식으로 가이드하면 어떨까 한다. 물론 가이드해도 이해하지 못하거나 읽지 않을 수 있다. 하지만 그것은 개인의 역량이니 어쩔 수 없겠다.
리다이렉션
로직의 분리를 위해 '무의미한' 리다이렉션 전용 페이지를 만드는 것이 더 나은 상황을 발견했다. 예를 들자면, 소셜 로그인으로 얻은 토큰으로 엑세스 토큰을 얻어내는 것과 엑세스 토큰으로 회원 정보를 조회하는 것은 다른 로직이니 다른 페이지로 분리하는 것이 더 나을수도 있다.
PHP나 JSP같은 오래된 개발 방식처럼 '페이지' 단위로 로직을 수행하도록 하는 것이다. 책임 분리 원칙을 페이지 단위에 녹여냈다고 볼 수 있다. 각 URL에서 기대하는 로직이 명확하고, API를 연속으로 호출하지 않도록 구성할 수 있다. 수정이 필요할 때 페이지를 '끼워넣는' 식으로 처리할 수 있다는 것도 장점이다. 하지만 관리해야 하는 URL이 많아지고 사용자 경험에 악영향을 줄 수 있다는점, 그리고 이런 방식으로 개발하는 것에 익숙하지 않은 경우가 많다는 점이 단점이다.
프록시와 헤더
어처구니없게도 클라이언트 요청을 API에 프록시 처리하다가 이슈를 발견했다. 이유는 굉장히 단순한데, Content Length나 Content Type 등 헤더가 요청 온 그대로 전송되었기 때문이다. 실제로 전송하는 body의 length와 헤더로 전송된 Content Length가 달라 API에서 오류가 발생하는데, 이 오류는 로그에도 남지 않는다. 프레임워크 레벨에서 잘못된 요청으로 감지하고 예외 처리하기 때문이다. 간단하지만 경험이 없으면 찾기 힘든 이슈다. 이전에 크롤링하고 프록시하는 토이 프로젝트들을 해보지 않았다면 굉장히 고생했을 것 같았다. 유사한 실수를 막기 위해 헤더에서 불필요한 값을 제거하도록 상위 레벨에서 제어하는 것이 나아보인다.
상위 레벨 리더의 중요성
이번 프로젝트로 크게 느낀 것 중 하나다. 단순히 팀을 이끄는 리더가 아니라, 프로젝트 단위를 넘어서는 더 상위 레벨의 리더에게 필요한 역량과 그 자리를 위한 것이 무엇일지에 대해 많은 고민을 하게 되었다. 내가 과연 높은 자리에 있을 때 어려움 없이 프로젝트가 진행되도록 할 수 있을까, 혹은 인정받아 그 자리에 갈 수는 있을까 하는 것들이다. 최소한 지금처럼 일하기만 한다면 인생에 아무런 변화를 줄 수 없으리라는 것은 확실하다. 좋든 싫든 학벌이나 출신과 인맥이 필요한게 사회이기 때문이다.
마치며
이 프로젝트는 정말 힘들었다. 매일매일 퇴사를 고민할 정도였다(사실 지금도 여파가 이어지고 있다). 특히 내가 절대 그리지 않는 상황에 강요당했기에 더욱 스트레스를 받았다. 하지만 어떻게든 마무리지어 런칭했고, 사용자들에게 서비스를 선보였다. 좋든 싫든 내 작품이 된 것이다. 오히려 지금부터 시작이라고 생각한다. 이어질 작업으로 이 불만족스러운 산출물을 개선하면 된다.
그리고 이번 프로젝트를 진행하며 많은 고민을 하고, 또 고통받은만큼 성장하는 계기가 되었다고 믿는다. 앞으로 진행하는 프로젝트는 훨씬 더 나은 결과물이 될 것이다.
'ECMAScript | TypeScript' 카테고리의 다른 글
소셜 로그인의 구조화 (0) | 2025.05.05 |
---|---|
배려하는 코드란 무엇일까 (1) | 2025.04.16 |
atob와 encodeURIComponent. 짝이 되는 변환 함수들 (0) | 2024.07.31 |
React의 hook deps와 Object.is (0) | 2023.09.17 |
더블 클릭이란 무엇일까 (0) | 2023.07.14 |