중복 문제 해결하기
중복된 기술이 산재하는 문제 상황 해결하기 
- 프로덕트를 빠르게 작업하다보면 중복된 기술을 사용하는 경우가 생긴다. 
중복 기술 범위
- 중복된 Library의 활용 확인하기 - duplicate-package-checker plugin 활용 
 
- 코드상으로 아키텍처에 중복된 영역이 있는지 확인 - 아키텍처가 없다면 아키텍처를 수립하면서 중복 코드를 일원화할 수 있다. 
 
- 코드의 관심사가 분리되어있는지 확인 - 비즈니스, 서비스, 로드 등 다방면의 코드가 한번에 녹여져있는지 확인 
- 프론트의 경우 횡단 관심사 (Cross-cutting concern)가 잘 분리되지 않는 경우가 많음 
 
- 단순한 JSON과 같은 데이터 파일을 나눌 수 있는지 확인 - 예를들어 Route 같은 것들을 하드코딩하지 않고 별도 파일을 통해 관심사 분리 가능 
 
중복된 기술 범위
- 비슷한 라이브러리의 사용 혹은 대체할수 있는 기능/기술이 있는 경우 -> 비슷한 기술들 중 어떤 기술이 더 좋은지 비교하여 제거 - ex) underscore.js & lodash.js가 중복 다운로드 
- ex) lodash.js의 element selector 기능을 코드로 이미 구현한 경우 
- ex) node.js의 버전이 높아 fetch를 충분히 사용할 수 있고, axios등의 라이브러리를 사용안해도 되는 경우 
 
- 중복된 아키텍처의 영역이 있는 경우 -> 중복된 아키텍처에서 중복 코드를 제거하고 일원화 - e.g) app router 기반으로 사용되는데, 각 도메인(페이지) 단위로 중복 코드가 존재하는 경우 
- e.g) 컴포넌트에 비즈니스, 로그 그리고 서비스(API) 로직 등 다양한 로직이 짬뽕되어있는 경우 
 
코드를 분리하는 기준
컴포넌트 내 코드를 분리하는 기준
- 컴포넌트 -> 레이아웃(JSX) + 상태 
- 잘 만들어진 컴포넌트는 "레이아웃이 상태에 따라 예외상황 없이 노출이 변경되는 것" - 순수함수와 동일하다고 보자. 
 
- 즉 하나의 컴포넌트는 예측 가능한 범위에서 동작이 되어야 한다. 
컴포넌트에는 비즈니스 로직이 있으면 안된다.
- 어떤 비즈니스에 따라 동작하는게 아닌 "레이아웃의 조건에 따라 움직여야 한다." 
- 레이아웃의 조건은 props로 추상화되어 상위 영역으로부터 데이터를 주입받고 동작된다. 
- Atomic Design 혹은 커다란 서비스의 경우 "비즈니스를 포함하는 컴포넌트"와 "순수 컴포넌트"로 구분할 수 있다. - atomics, molecules 는 순수 컴포넌트로 분리하고 organisms는 비즈니스 로직을 포함하는 컴포넌트로 생각 
 
- React에서는 Custom Hooks로 비즈니스 로직을 격리할 수 있다. 
분리 Step
- 해당 컴포넌트가 몇 군데서 쓰이는지 확인 - 한 군데 밖에 쓰이지 않음 -> 좀 더 공용으로 쓰일수 있는 컴포넌트로 변경 
- e.g) CheckoutForm -> Form으로 변경, Form에 해당하는 모든 영역에 쓰일 수 있도록 제공 
- 레이아웃은 페이지에서 제공하고 기능을 Form에서 props로 받도록 한다. 
 
- 비즈니스 로직은 커스텀 훅으로 분리 - e.g) useCheckoutForm을 만들어서 격리, CheckoutForm에서 useCheckoutForm을 호출 
- 호출하고 페이지 내에서 Form 컴포넌트에 주입한다. 
 
- useEffect 등의 동작으로 예측이 어려운 컴포넌트에서 -> 페이지 단위로 상태를 올림 - 단순한 레이아웃만 가진 컴포넌트가 될 수 있음 
 
점진적으로 분리하기
더 좋은 형태로 순차적 장기적으로 분리를 진행한다
- 점진적 분리는 장기적으로 이루어지기 때문에 목표를 잘게 쪼갠다. (phase) - phase를 나눠 현재 상황에 맞게 어느정도 분리를 진행할 것인지 기간과 목표를 잡는다. 
- 비즈니스와 레이아웃을 분리하는것도 phase 단위로 영역을 분리하여 잡을 수 있다. 
- 현재 팀의 가용 가능한 리소스와 기간을 생각해보며 잡는다. 
 
- 최종 목표로할 아키텍처를 구축한다. - 최종적으로 변경될 아키텍처를 생각하고, phase 단위로 변화될 모습을 그린다. 
- 단순 텍스트, 혹은 개선사항만 제공하면 개발자는 한번에 이해가 안될 수 있다. - 자주 잡아줘야하는 문제점 발생 
 
- Folder Structure, System Arhitecture, Infra Arhitectur 
 
- Phase 1. 테스트 코드 작성 - 컴포넌트 테스트 커버리지 80% 목표 
- 핵심 컴포넌트 100%, 기타 컴포넌트 60%... 
- 총 컴포넌트 갯수 n개 
- 기간 3주, 담당자 .. 
 
- Phase 2. 컴포넌트 코드 분리 - 컴포넌트에서 비즈니스 로직과 레이아웃 로직 분리 
- 총 컴포넌트 갯수 n개 
- 기간 2주, 담당자 ... 
 
- Phase 3. ... 
하나의 기술로 정리하기
남게되는 기술과 그렇지 않은 기술 구분하기
좋은 기술을 판단하는 기준
- 사용자와 커뮤니티 수에 따른 근거: 많이, 널리 사용되는 기술은 검증이 많이 된 기술일 확률이 높음 
- 용량에 따른 근거: 트리 셰이킹이 되지만, 그럼에도 사용되는 용량이 적을수록 더 빠른 서비스를 제공할 수 있음 
- 트랜드에 의거한 근거: 가파르게 사용률이 올라가는 경우(NPM Trends), 사용하는 근거가 있을 확률 높음 
- 편의성에 의거한 근거: 코드상에서 개발자 관점으로 얼만큼 친절한 인터페이스, 주석을 제공하는지, 공식문서 등 
기술을 통합하기 위한 방안
- 명확하게 사용하는 곳이 드러나고, 교체해도 큰 이슈가 없는 경우 - 라이브러리를 언인스톨하거나, 모듈을 제거 
- Type checker, Runtime error를 확인하면서 작업 
 
- 당분간 필수적으로 사용되어야 하며, 교체했을 시 큰 이슈가 있는 경우 - 사용되는 코드를 컴포넌트 혹은 페이지 단위로 격리 
- 격리 시킨후, - adapter pattern혹은- strategy pattern등으로 로직을 여러개 허용되도록 구축
- 완전한 전환 및 확인 후 전환한다. 
 
- 인프라적인 측면 - 완전히 잘 동작하는 빌드 파일과, 변경 교체한 빌드 파일로 각각 인프라 구축 
- 페이지 단위로 완전히 이관이 끝난 페이지만 route53 혹은 cloudfront에 랜딩되도록 경로 제공 
 
서버 상태와 클라이언트 상태 분리하기 
가장 많이 하는 상태관리 실수 -> 서버 상태와 클라이언트 상태 공존
- 상태(state): 사물 현상이 처해 있는 형편이나 모양 - 물리계가 어떤 시각에 대하여, 그 미래 시간 변화를 예측할 수 있게 하는 데이터들을 일컫는다. 
 
- 클라이언트 상태: 웹에서 레이아웃, 캐시된 데이터, 유저의 데이터 등, 현재 시각적으로 나타내기 위한 정보를 가지고 있는 경우 - 컴포넌트 레벨: useState, useRef와 같이 React(혹은 js)에서 저장하는 상태 값 
- 브라우저 레벨: cache, url, query, cookie 등 브라우저 자체에서 내장하는 저장된 값 
 
- 서버 상태: DB 혹은 Radis 등 저장된 유저의 현재 상황에 대한 값 - 제일 공신력이 높고 100% 따라야 하는 값 
- 개인화된 유저데이터는 클라이언트가 아닌 서버에서 무조건 가져와야 한다. 
 
권장되는 방법
- 서버 상태: React Query 혹은 Relay등을 사용하는 전략 - React Query는 서버 상태를 Hook에 한정하여 모듈화를 진행함으로써 클라이언트 상태와 명확하게 분리시킬 수 있음 
 
- 클라이언트 상태: Recoil 혹은 Jotai 등의 atom 단위 상태를 사용하는 전략 - 단순한 인증 데이터라면 Context를 활용한 Provider로도 충분 
- 다만 상태가 여러 페이지에 전반적으로 나타난다면 Context API의 성능 문제를 야기시킬 수 있음 
 
로그가 산재된 경우 중앙화를 통해 로직 개선하기
const ProductDetailPage = () => {
  ...
  useEffect(() => {
    Logger.view('ProjectDetail')
    
    pageRef.addEventListener('scroll', (e) => {
      Logger.event('scroll', 'imp', e.scrollY)
    }, [])
  })
  
  return (
    <div ref={pageRef}>
    ...
    </div>
  )
}- 각 페이지 별 로그의 요구사항은 매우 다채롭고 복잡함 
- 페이지뿐만 아니라 컴포넌트 단위로도 존재할 수 있음 
- 이러한 코드는 매우 산재되어 있을 가능성이 높기에 로그를 페이지 단위가 아닌 중앙화해서 관리할 수 있는 방안을 찾아보자. 
로그의 패턴 찾기
로그의 요구사항은 서비스마다 다르지만 유저와 관련되 로깅은 아래와 같이 나뉠 수 있음
- View Log: 화면에 진입했는가를 확인하는 로그 
- CTR Log (or CTA): 전환률을 확인하는 로그 (보통은 Submit Button등에 추가) 
- Event Log: 스크롤, 사용자의 커서 액션 등 사용자의 인터렉션이 있는 로그 
AOP (Aspect-Oriented Programming)
관점지향 프로그래밍 객체지향 프로그래밍에서 어떤 문제에 대해 횡단 관심사로 해결하기 위한 프로그래밍 기법
- 독립적으로 분리하기 어려운 부가 기능을 모듈화하는 방식 
- 로그 등 횡단 관심사 (여러 컴포넌트, 객체가 공통적으로 갖고 있는 관심사)를 분리하는 데 용이 

로그 모듈의 설계
사용 및 동작 플로우 설계
- 로그를 추적할 페이지에 "useLog(pageRef)"를 선언 
- parameter로 받는 ref를 이용해 page 내부 custom attribute를 찾는다. 
- parameter를 추가적으로 등록하여 view 로그를 남길 수 있도록 한다. 
- custom attribute의 string을 읽는다. - event, ctr 등 어떤 이벤트인지 string에 기입할 수 있도록 한다. 또한 unique한 이름을 갖도록 한다. 
- event는 다양한 동작이 있을텐데 이또한 알 수 있는 이름 패턴을 만든다. 
 
- 읽은 string을 토대로 ctr은 클릭 이벤트가 발생했을 때 마지막으로 실행, event는 이벤트 발생 시 자연스럽게 발생하도록 한다. 
- 커스텀으로 여러 동작이 필요한 경우 커스텀을 제공할 수 있도록 페이지 별 extension file을 만들어 동작에 대한 커스터마이징을 할 수 있도록 한다. 

Last updated

