중복 문제 해결하기

중복된 기술이 산재하는 문제 상황 해결하기

  • 프로덕트를 빠르게 작업하다보면 중복된 기술을 사용하는 경우가 생긴다.

중복 기술 범위

  • 중복된 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 단위로 나누기 예시

  • Phase 1. 테스트 코드 작성

    • 컴포넌트 테스트 커버리지 80% 목표

    • 핵심 컴포넌트 100%, 기타 컴포넌트 60%...

    • 총 컴포넌트 갯수 n개

    • 기간 3주, 담당자 ..

  • Phase 2. 컴포넌트 코드 분리

    • 컴포넌트에서 비즈니스 로직과 레이아웃 로직 분리

    • 총 컴포넌트 갯수 n개

    • 기간 2주, 담당자 ...

  • Phase 3. ...

하나의 기술로 정리하기

남게되는 기술과 그렇지 않은 기술 구분하기

좋은 기술을 판단하는 기준

  1. 사용자와 커뮤니티 수에 따른 근거: 많이, 널리 사용되는 기술은 검증이 많이 된 기술일 확률이 높음

  2. 용량에 따른 근거: 트리 셰이킹이 되지만, 그럼에도 사용되는 용량이 적을수록 더 빠른 서비스를 제공할 수 있음

  3. 트랜드에 의거한 근거: 가파르게 사용률이 올라가는 경우(NPM Trends), 사용하는 근거가 있을 확률 높음

  4. 편의성에 의거한 근거: 코드상에서 개발자 관점으로 얼만큼 친절한 인터페이스, 주석을 제공하는지, 공식문서 등

기술을 통합하기 위한 방안

  • 명확하게 사용하는 곳이 드러나고, 교체해도 큰 이슈가 없는 경우

    • 라이브러리를 언인스톨하거나, 모듈을 제거

    • Type checker, Runtime error를 확인하면서 작업

  • 당분간 필수적으로 사용되어야 하며, 교체했을 시 큰 이슈가 있는 경우

    • 사용되는 코드를 컴포넌트 혹은 페이지 단위로 격리

    • 격리 시킨후, adapter pattern 혹은 strategy pattern 등으로 로직을 여러개 허용되도록 구축

    • 완전한 전환 및 확인 후 전환한다.

  • 인프라적인 측면

    • 완전히 잘 동작하는 빌드 파일과, 변경 교체한 빌드 파일로 각각 인프라 구축

    • 페이지 단위로 완전히 이관이 끝난 페이지만 route53 혹은 cloudfront에 랜딩되도록 경로 제공

서버 상태와 클라이언트 상태 분리하기

가장 많이 하는 상태관리 실수 -> 서버 상태와 클라이언트 상태 공존

  • 상태(state): 사물 현상이 처해 있는 형편이나 모양

    • 물리계가 어떤 시각에 대하여, 그 미래 시간 변화를 예측할 수 있게 하는 데이터들을 일컫는다.

  • 클라이언트 상태: 웹에서 레이아웃, 캐시된 데이터, 유저의 데이터 등, 현재 시각적으로 나타내기 위한 정보를 가지고 있는 경우

    • 컴포넌트 레벨: useState, useRef와 같이 React(혹은 js)에서 저장하는 상태 값

    • 브라우저 레벨: cache, url, query, cookie 등 브라우저 자체에서 내장하는 저장된 값

  • 서버 상태: DB 혹은 Radis 등 저장된 유저의 현재 상황에 대한 값

    • 제일 공신력이 높고 100% 따라야 하는 값

    • 개인화된 유저데이터는 클라이언트가 아닌 서버에서 무조건 가져와야 한다.

서버 상태를 클라이언트 상태로 들고 있게 되면 상태를 주기적으로 업데이트 해주어야 한다.

이를 처리하기 위한 부가적인 코드가 늘어나게 됨, 실제 코드를 두개 나눠서 관리하게 되고, 관리 포인트가 한군데가 늘어나는게 아닌, 연관된 hook, 코드 전역적으로 늘어나게 된다. 서버 상태 변화에 따라 추가해야할 부분 또한 많아진다.

권장되는 방법

  • 서버 상태: 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)

관점지향 프로그래밍 객체지향 프로그래밍에서 어떤 문제에 대해 횡단 관심사로 해결하기 위한 프로그래밍 기법

  • 독립적으로 분리하기 어려운 부가 기능을 모듈화하는 방식

  • 로그 등 횡단 관심사 (여러 컴포넌트, 객체가 공통적으로 갖고 있는 관심사)를 분리하는 데 용이

로그 모듈의 설계

사용 및 동작 플로우 설계

  1. 로그를 추적할 페이지에 "useLog(pageRef)"를 선언

  2. parameter로 받는 ref를 이용해 page 내부 custom attribute를 찾는다.

  3. parameter를 추가적으로 등록하여 view 로그를 남길 수 있도록 한다.

  4. custom attribute의 string을 읽는다.

    1. event, ctr 등 어떤 이벤트인지 string에 기입할 수 있도록 한다. 또한 unique한 이름을 갖도록 한다.

    2. event는 다양한 동작이 있을텐데 이또한 알 수 있는 이름 패턴을 만든다.

  5. 읽은 string을 토대로 ctr은 클릭 이벤트가 발생했을 때 마지막으로 실행, event는 이벤트 발생 시 자연스럽게 발생하도록 한다.

  6. 커스텀으로 여러 동작이 필요한 경우 커스텀을 제공할 수 있도록 페이지 별 extension file을 만들어 동작에 대한 커스터마이징을 할 수 있도록 한다.

설계대로 구현이 된다면, 코드에 작성하는 것은 useLog와 Element 단위의 custom attribute 기입만 진행하게 됨

  • 각 컴포넌트 별 수정이 적어짐

  • 문제는 로그의 세부적인 작업이 나올 때마다 복잡해 질 수 있는 문제를 가짐

  • 가장 중요한건 관심사의 분리를 통해 코드줄 수가 적어지며, 동일한 로직 개선의 효과를 갖게됨

Last updated