2022. 1. 1. 10:56ㆍDeveloper History
Overview
11월 12월 회고이자, 한해를 정리하는 느낌으로 가볍게 여러 주제들을 적어내려 보려 한다. 꼭 회고를 해야만 하는 건 아닐 테지만, 이렇게 한 해에 있었던 일들과 여러 우여곡절들을 글로 정리하고 나면 미련없이 지난 일년의 짐들을 털어버리는 듯한 느낌을 받기도 하고, 내년에는 어떤 것들에 집중하고 어떤 부분들을 개선해야 할지에 대한 방향도 잡히는 것 같다. 내가 2021년을 맞이하면서 성장하길 바랐던 수준을 100이라고 한다면 한해를 닫는 지금 이 시점에서는(아마 포스팅이 올라갈 즈음에는 이미 2022년이 되어 있겠지만) 한 30점 ~ 35점 정도는 줄 수 있지 않을까. 1등급이 90~96점 언저리에서 형성되는 수능에 익숙해서인지 낮은 점수인것 같지만?!
Frontend
거의 한 분기에 걸친 새로운 프로젝트가 굉장히 도전적이어서 툭하면 막차를 놓치고 택시를 타고 집에 가기 일쑤였지만, 한번도 택시를 타고 가면서 뿌듯하지 않았던 적은 없었던 것 같다. 프로젝트 자체가 회사에서 갖는 의미도 중요했지만, 그 이외에도 프론트엔드 개발자로서 마주해야 할 여러 고민들을 두루두루 하게 할 수 있게 만들었다는 점에서 어떻게 보면 고마운 프로젝트였다.
아키텍쳐에 대한 고민
이전까지만 해도 새로운 프로젝트를 맡아서 개발할 때, 기간안에 빠르게 제품을 완성하는데에 집중하다보니 "아키텍쳐"에 대한 고민을 깊게 하지는 않았었다. 사내에서도 이렇다 할 SOTA(State Of The Art) 아키텍쳐는 없었고, 주로 "View"를 다루는 웹이 많다보니 Atomic Design Pattern, Container-Presenter Pattern등의 잘 알려진 패턴을 사용해서 개발을 했었다. 하지만 이번 프로젝트는 표면적으로 드러나는 비즈니스 도메인의 수도 많았고, 웹뷰, 모바일, 데스크탑에서 모두 동작해야 하는 웹 프로젝트인데다가 결제, 관리, 상품권등록 등 User Action이 많은 프로젝트여서 기존의 패턴을 사용해서 개발하기에는 유지보수성이 떨어지는 코드가 나올 것 같다는 판단을 내렸다. 게다가 추후 해당 프로젝트에 많은 기능들이 추가될 가능성이 있었기 때문에(당장 다음 분기만해도 2~3개의 큼지막한 기능 추가가 예정되어 있다..!) 코드의 유지보수성과 가독성은 매우 중요해보였고, 때문에 도메인 기반의 프론트엔드 클린 아키텍쳐를 프론트엔드에 적용하는 시도를 하게 되었다.
프론트엔드 팀에 오신 시니어분께서 아키텍쳐에 대한 여러 조언들과 밑작업들을 진행해주시고, 관련된 책들도 소개해주셔서 이를 기반으로 현실적으로 적용가능한 프론트엔드 아키텍처에 대해 많이 고민하게 되었다. (프론트엔드에서 적용가능한 클린 아키텍쳐에 대해서는 추후 시리즈로 포스팅할 예정이다.) 자세한 내용들을 다 적어내려갈 수는 없지만, 핵심은 적절한 추상화 단계를 통해 핵심 도메인과, 사용자 액션을 다루는 비즈니스 로직을 구분해서 변경에 유연한 컴포넌트들을 설계하는 것이다. 구체적인 적용 사례들이 너무 많지만, 추후 자세히 소개하려 한다.
실제로 클린 아키텍쳐의 적용과 선언형 로직의 작성, 그리고 아래에서 설명할 테스트코드의 작성을 통해 변경에 유연한 코드를 설계할 수 있었다. 프로젝트가 개발되는 도중에 추가적인 개발사항들이 하나둘 들어오게 되었는데, 덕분에 새로운 개발사항들을 도메인과 사용자 액션의 관점에서 생각해보고 기존코드에 대한 큰 변경 없이 추가할 수 있었다.
http://www.yes24.com/Product/Goods/77283734
http://www.yes24.com/Product/Goods/5312881
Functional Code
중간에 함수형 프로그래밍에 혹한적이 있어서 '도대체 이게 뭔가' 하는 생각에 이것저것 공부를 했었던 기억이 난다. 실질적으로 프론트엔드에서는 Global Store에 dispatch하고 network call이 일어나고 대부분의 로직이 사용자 이벤트의 callback으로 수행되기 때문에 순수한 함수를 사용해서 함수 파이프라이닝을 구성하는 것은 쉽지 않았다. (지금의 지식으로는 사실상 불가능했던 것 같다.)
하지만 함수형 프로그래밍에 대한 공부를 하면서 "선언형으로 작성하기"와 "순수함수와 비순수함수를 분리하기"라는 관점을 갖게 되었고, 실제로 이는 유지보수하기 좋은 코드를 만드는데 확실한 도움이 되었다.
선언형으로 작성하기
함수를 선언형으로 작성하고, 선언형으로 작성한 함수들을 클로저 등의 적당한 자바스크립트의 성질을 사용해서 체이닝하도록 하여 로직을 작성했는데, 우선 로직을 보기가 너무 편했다. 실제로 if - else의 분기를 사용하는것보다야 당연히 메모리도 많이 먹는(클로저를 사용하니까..!)구조이지만 복잡한 로직들을 책의 문장을 읽듯이 읽어내려갈 수 있었고, 실제로 특정 분기의 로직에 문제가 생겼을 때, 해당 로직을 찾아서 수정하는 일이 너무 쉬웠다. 어쨌든 버그가 생겼을때 문제를 찾아 수정하기 위해 코드를 읽어내려가는 건 사람 개발자니까, 유지보수성을 높이는데 가독성이 적잖이 영향을 미치는구나 하는 사실을 알게 되었던 것 같다.
이렇게 가독성을 고려하다보니 함수의 이름도 조금 구체적으로 작성하게 되었다. 예전에는 함수 이름이 길어지는걸 약간 불편하게 생각했었는데, 여러 도메인을 복합적으로 다루는 함수들의 경우 이름을 조금 길게 짓더라도 의미를 명확하게 하고 넘어가는게 정말 중요하구나 라는 사실을 다시 알게 되었다. (어차피 배포할 땐, 번들러가 1~2글자로 바꿔주니까 성능에는 큰 영향이 없다.)
순수함수와 비순수함수를 분리하기
실제로 분리가 잘 되든 되지 않든 간에, "순수함수와 비순수함수를 분리하자" 라는 관점을 갖게 되는 것이 정말 중요한 것 같다. 실제로 대부분의 로직들이 SideEffect를 발생시키는 로직들이었기 때문에 (global dispatch 호출, network call, state변경 등등) 분리하기가 어려웠지만 유틸성함수(formatter, validation등등)를 거기서 분리해낼 수 있었고, 적절한 pipe를 통해 에러가 발생했을 시에 그것이 sideEffect에서 난 것인지, 순수함수에서 난 것인지를 구분할 수 있게 되었다. 유틸성함수는 따로 테스트코드를 작성하기도 쉽기 때문에 에러가 나면 대부분의 경우 sideEffect함수만 보면 된다.
간단하게 결론을 내려보면, 함수형 프로그래밍을 접한지 그렇게 오래 되지 않고, 학습할 시간도 그렇게 많지 않아서 이 패러다임을 100% 이해하고 코드를 작성한 것은 아니지만, 이 패러다임에서 강조하는 몇 가지 사항들이 왜 중요한지, 그리고 적용했을때 어떤 이점들이 있을 수 있는지를 조금은 이해한 것 같다. 실제로 모든 컴포넌트 비즈니스 로직을 함수형으로 작성하기에는 현실적인 어려움이 따른다. (우선 코드는 나 혼자 작성하는게 아니라서 프론트엔드 팀원들의 동의와 이해가 있어야 한다)
http://www.yes24.com/Product/Goods/22894910
TestCode
아키텍쳐에 대한 고민들을 많이 하면서 코드를 이리바꾸고 저리바꾸고 하는 소위 '리팩터링'을 정말 많이 했다. 아직 기능이 배포되기 전에는 큰 문제가 없었지만, 기능이 배포된 시점에서 결제와 같은 민감한 로직들이 들어가있으면 마음놓고 리팩터링을 하기가 어렵다. "따라서 이러한 경우에는 적절한 테스트코드를 통해 내부 구현이 바뀌어도 의도한 동작들이 항상 정상적으로 동작하는지를 확인하는게 필요하다."
...는게 잘 알려져있는 사실이다. 하지만 현실적으로 Frontend에서 테스트코드를 작성하기란, 게다가 TDD로 테스트를 먼저 작성하고 해당 테스트를 통과하도록 로직을 작성하는 방식은 와닿지 않았었다. Jest를 사용해서 테스트코드를 작성하는데는 사실 많은 제약이 따랐었다. 일단 컴포넌트 하나를 테스트하는데 필요한 비용이 너무 크다. 우선 global store(redux) 모킹해야하고, custom Hook 모킹해야하고, 네트워크 응답 모킹해야하는 등 많은 것들을 mocking해주어야 한다. 또한 리팩터링을 하면서 두 컴포넌트를 합치거나 하나의 컴포넌트를 두개로 나눠야 하는 경우 테스트코드를 처음부터(!) 다시 작성해주어야 한다.
이러한 비용 문제 때문에 사실 테스트코드를 제대로 작성하지 않았었고, 현실적으로 프론트엔드에서 테스트코드를 잘 작성하기가 어렵구나라고 생각했었다. 하지만 최근에 클린 아키텍쳐를 적용하면서 "컴포넌트 내에 있는 사소한 로직들을 전부 테스트하지 말고 "Usecase"중심으로 사용자가 할 수 있는 행동들을 기준으로 테스트를 작성한다면?" 이라는 생각을 하게 되었고, Cypress를 도입해서 integration test (E2E는 서버쪽과 인프라쪽의 추가적인 논의도 필요해서 우선 Integration Test부터 작성하기 시작했다.)를 작성하게 되었다.
Cypress는 브라우저 환경을 모킹해서 테스트를 해주는 것이기 때문에 SSR방식인 Nextjs에서 Server-Side Network Request를 모킹하는 방법을 찾는데 굉장히 많은 시간을 들였던 것 같다. 하지만 어느 정도 테스트코드를 작성할 기반이 마련되고, 테스트코드를 작성하고 나니 "아 이거구나"하는 생각이 들었다. Cypress와 Jest, 그리고 Chromatic을 사용해서 상호보완적인 테스트 환경을 마련할 수 있었고, 대략 다음과 같다.
예를 들어 유저가 결제화면에 접속해서 결제버튼을 눌러 결제하는 경우 다음과 같이 테스트할 수 있다.
- Cypress Integration Test를 통해 해당 화면을 visit하고 스크롤해서 결제 버튼을 누른 뒤에 결제를 완료하고 완료페이지로 넘어가서 해당 페이지에 결제 정보가 올바르게 찍히는지에 대한 일련의 "User Action"을 테스트한다. 해당 로직만 통과가 된다면 컴포넌트들 쪼개든 합치든, 변수이름을 바꾸든 아무 상관이 없다. (어쨌든 유저한테는 일관적인 동작을 보장하니까)
- Jest Unit Test를 통해 해당 User Action을 구성하는 비즈니스 로직들 (대개 순수함수로 작성되어 있어서 테스트하기가 쉽다)을 Unit Test한다.
- develop환경에 배포하기전에 CI 파이프라인에서 storybook을 빌드해서 chromatic에 deploy한다, 로직을 수정하고 컴포넌트들을 만지는 과정에서 예상하지 못한 UI상의 사이드이펙트 (나는 버튼 폰트사이즈만 바꿨는데 예상하지 못하게 저 멀리있는 컴포넌트의 폰트사이즈가 같이 바뀌는 등?)가 생기면 빌드가 실패하고 배포가 되지 않는다. 해당 사이드이펙트를 수정하고 다시 배포한다.
이렇게 서로 상호작용하는 테스트코드를 작성하고 나니 어쩌면 TDD가 프론트엔드에서 현실적으로 가능할 수 있겠구나 하는 생각이 들었다. 내부적으로 어떤 로직들이 동작하고 몇개의 컴포넌트가 필요할지는 중요하지 않다. 실제로는 "유저가 결제를 성공적으로 할 수 있구나"가 중요한 것이고, 이러한 케이스들을 테스트코드로 작성하고, 이를 성공시킬 수 있는 로직을 만들면 되는 것 같다. 이를 기반으로 다음번 기능추가시에는 TDD를 적용해보려고 한다.
Frontend Next Level
전사적으로 웹뷰의 비중이 매우 높아지는 분위기가 되면서 사내 프론트엔드 팀의 규모가 점점 커지고 있다. 이에 따라서 팀이 서로의 어깨위에 올라가서 성장하는 분위기를 만들고, 회사에서 중요하다고 여기는 것들에 팀원들의 목표도 align되어서 해당 목표에 초점을 맞추는 것이 중요하다는 생각이 들었다.
실제로 팀에서 제일 중요하게 생각해야 할 것은 "사용자에게 얼마나 편안하고 자연스러운가?" 인 것 같다. 모든 것이 있어야 할 곳에 있고, 눌러야 할걸 누르면 기대했던 동작이 수행되고, 빠르게 로딩되는.
이런게 가능하려면 팀원들이 매일매일 이런 고민들만 하는 환경이 조성되어야 해서, 기본적인 프로젝트 세팅, 파이프라인 구성, 테스트코드 세팅등등을 기본적으로 제공하고, 팀원들끼리 만나면 도메인이야기를 하고, 만나면 UX이야기를 하는 환경을 조성해야겠다는 생각이 들었다. 같은 프론트엔드 개발을 하더라도 서로가 잘하는 것이 다르기 때문에 이를 통해 서로가 최대한 이익을 얻을 수 있는 구조로 팀을 구성하는 것이 필요하다는 생각이 들었다. (물론 말은 쉽다!)
Frontend Antifragility
올해 가장 감명깊게 읽은 책인 "Antifragile"이 내 의사결정에 참 많은 영향을 끼치는 것 같다. 드디어 전역의 해(2022년!)가 밝아오면서 프론트엔드 개발을 계속 할 날이 얼마 남지 않았을 수 있겠다 라는 생각이 들고, 지난 2년동안 나는 어떻게 프론트엔드 개발을 했고, 이것은 웹에 불어오는 변화의 바람에 안티프래질한가? 라는 관점으로 참 많은 고민들을 했다. 고민이 깊어갈수록 후회도 많이되고, 아쉬움도 많이 남는다.
올해는 "본질"에 집중할 생각이다. 리액트가 Svelte와 같은 다른 라이브러리로 대체되고, 내가 사용하는 Third-party 라이브러리가 모두 deprecated되고 새로운 API가 나왔을 때, 나에게는 무엇이 남을까?의 관점에서 본질을 생각해보았다. 물론 나중에는 자바스크립트도 없어지고 HTML도 없어지고 CSS도 없어질 수도 있겠지만? 결국 웹의 본질은 HTML, CSS, JS와 런타임 (브라우저, nodejs)의 상호작용으로 일어나는 것이 아닌가? 수많은 라이브러리나 Framework들이 이들간의 상호작용을 더 쉽게 하기 위한 해결책으로서 나온 것이기 때문에 이들이 "어떻게" 이 상호작용을 쉽게 하는지에 대해서 조금더 고민을 해보려 한다. 기본적으로 메모리, 퍼포먼스, 네트워크에 대한 공부를 조금 더 해야될 것 같고, CSS와 HTML의 기본에 대해 공부를 조금 깊게 해보려 한다.
추가적으로 Ops의 시대다. 웹도 Nextjs와 같은 서버사이드 렌더링이 히트를 치면서 인스턴스를 띄우고 클러스터 구조 안에서 관리되는 경우가 대부분이고, 그렇지 않더라도 배포하면서 CI / CD 딴에서 테스트코드를 돌리고, 여러 작업들을 수행한다. 지금 Kubernetes 스터디가 한 절반쯤 왔는데, 어서 마무리하고 2분기 쯤에는 Docker로 넘어가보려 한다.
'Developer History' 카테고리의 다른 글
개발일지 (2월 회고) (0) | 2022.02.28 |
---|---|
개발일지 (1월 회고) (1) | 2022.02.12 |
개발일지 (10월 회고) (2) | 2021.11.07 |
개발일지 (9월 회고) (2) | 2021.09.26 |
개발일지 (4 & 5월 회고) (0) | 2021.05.31 |