티스토리 뷰

항해 플러스 프론트엔드 5기

이번 주차 목표 (클린코드와 리팩토링)

(1) 내가 작성한 코드가 클린코드임을 설명할 수 있다.

(2) 클린하지않은 코드를 클린하게 만들 수 있다.

 

 

1. 문제 (과제, 프로젝트를 진행하면서 부딪혔던 기술적인 문제)

- 화요일에 대한 테스트코드 실패

할인과 포인트 계산에 대한 테스트 코드가 계속 실패해서 calcCart 함수의 로직을 여러 번 수정했다. 알고 보니 로직이 틀린 게 아니라, 테스트 시점의 날짜가 실제로 화요일이라 할인 로직이 정상 작동한 것이 원인이었다. 테스트에 실패하자 일부러 수요일로 바꾸고 흐린눈으로 넘겼던 기억도 있다... 😭

 

- React 마이그레이션 시 코드 작성과 구조 분리의 어려움

Basic 코드에서 사용하던 로직을 그대로 React로 옮긴 뒤 개선을 시도했지만, "React에서는 어떻게 분리해야 하지?", "어떤 코드가 '리액트스럽다'고 할 수 있지?" 같은 고민이 계속해서 따라붙었다.

결국 App.tsx 안에 상태와 로직이 뒤섞인 채로 남아버렸고, 커스텀 훅이나 모듈화된 유틸로 분리할 수 있었던 부분까지는 미처 손대지 못했다. 처음부터 구조를 어떻게 잡을지 가볍게라도 계획하고 접근했더라면, 훨씬 나았을 거란 아쉬움이 남는다.

 

 

2. 시도 및 해결

1. 성능 최적화를 고려한 useMemo 활용

const { totalAmount, discountRate, points } = useMemo(() => {
    const summary = cartItems.reduce(
    
// 여러 계산 로직들

    return { totalAmount, discountRate, points };
  }, [cartItems, products]);

 

장바구니에 담긴 상품 수가 많아질수록, 총액이나 할인율을 계산하는 과정에서 reduce와 find 같은 연산이 매 렌더링마다 반복된다는 점이 성능 측면에서 아쉬웠다.
이를 최적화하기 위해 저번주에 배운 React의 useMemo를 활용하여, cartItems나 products가 변경되지 않는 한 계산이 재실행되지 않도록 캐싱해보았다.

 

 

2. usePromotionEffects 리팩토링

export const usePromotionEffects = ({
  setProducts,
  lastSelectedProductId,
}: UsePromotionEffectsProps) => {
  const lightningInterval = useRef<NodeJS.Timeout | null>(null);
  const suggestionInterval = useRef<NodeJS.Timeout | null>(null);

  // 번개 세일
  useEffect(() => {
    const delay = Math.random() * 10000;

    const timeout = setTimeout(() => {
      lightningInterval.current = setInterval(() => {
        // 번개 세일 로직
      }, 30000);
    }, delay);

    return () => {
      clearTimeout(timeout);
      if (lightningInterval.current) clearInterval(lightningInterval.current);
    };
  }, [setProducts]);

 

처음에는 App.tsx에 모든 할인 로직(임의의 시간마다 깜짝세일 20%, 추천세일 5%)을 그냥 넣어서 구현했었는데, 구조적으로 분리해야겠다는 생각이 들어 usePromotionEffects라는 커스텀 훅으로 따로 뺐다.
Vue의 watch와 비슷하다고 생각해서 useEffect를 썼는데, 할인 로직 두 개가 서로 얽히고 클린업이 안 돼 중복 호출이 발생하는 문제가 있었다.


이후 setInterval은 useRef로 저장하거나 바깥으로 빼서 clearInterval이 되도록 수정했다. useEffect의 return에서 타이머를 정확히 정리하면서 구조도 훨씬 안정적으로 바꿀 수 있었다.

 

 

3. 장바구니 수량 증가 시 재고 체크 오류 개선

처음에는 상품을 장바구니에 추가할 때, 아래와 같이 itemToAdd.q(재고 수량)만 기준으로 조건을 검사하고 있었다.

if (newQty <= itemToAdd.q) {
  // 수량 증가 및 UI 갱신
} else {
  alert('재고가 부족합니다.');
}

 

이 방식은 이미 장바구니에 담긴 수량은 고려하지 않기 때문에, 예를 들어 재고가 5개인데 이미 5개 담아둔 상태에서 하나 더 담으려고 하면 조건을 잘못 판단해 alert이 뜨는 문제가 발생했다.
(특히 리스트에서 추가할 때만 오류가 나고, 마이너스/플러스 클릭으로는 10개까지 잘 담기는 상황이었다.)

이 문제를 해결하기 위해,

 

if (newQty > currentQuantity + itemToAdd.quantity)


위와 같이 수정해서 현재 장바구니 수량 + 남은 재고를 기준으로 비교하도록 바꿨다.
그 결과, 정해진 재고 수량을 다 채웠을 때만 alert이 뜨도록 동작이 올바르게 변경되었다.

 

 

3. 개선해야 할 것

- 계산 로직이 useMemo 내부에 몰려 있어 구조적으로 분리할 여지가 있음.

  • calculateCartSummary() 같은 유틸 함수로 빼내면 컴포넌트는 UI에 집중하고 계산은 따로 관리할 수 있을 것.

- 할인 로직 같은 경우엔 아직 useEffect에 대한 이해가 부족한 상태에서 쓴 느낌이 있었고, 더 좋은 구조가 있을 것 같다는 생각에 아쉬움이 남는다.

- 함수마다 단일 책임 원칙을 적용하려고 노력하긴 했지만, 아직 모든 함수가 명확하게 역할이 분리되었다고는 자신 있게 말하기 어렵다... 좀 더 명확하게 분리할 필요가 있다... 역할이 모호하다 보니, 쓸데없이 많은 인자를 받게 되는 함수들도 생겨났다. 😔

 

 

4. 알게된 것

- innerHTML vs CreateElement를 상황에 맞게 적재적소에 사용하기.

  • 복잡한 구조의 HTML 마크업이 들어가는 부분(main 등)에서는 여러 줄의 DOM 생성 코드를 innerHTML로 한 번에 처리하여 가독성과 유지보수성을 높였다.
    반대로, 작고 단순한 요소를 재사용하거나 반복적으로 생성해야 할 때는 범용적으로 쓸 수 있는 createElement() 유틸 함수를 만들어 활용했다.

- 더티 코드란 어떤 것인지 알게 되었다... 다 지키지는 못하더라도 "적어도 이렇게는 짜지 말자"는 기준이 생긴 계기였다. 

- Vue에 익숙했던 기존 방식과 달리, React에서는 상태 관리와 컴포넌트 분리를 어떻게 해야 ‘깔끔한 구조’가 되는지에 대해 고민이 깊어졌다.

 

 

---

Keep : 현재 만족하고 계속 유지할 부분

PR 열심히 쓰기

 

이번 주에는 야근으로 몸이 피곤했음에도 불구하고, 목요일 밤에 거의 밤을 새워가며 PR을 꼼꼼히 작성했다. 사실 처음엔 너무 피곤해서 대충 넘기려고 했지만, 내가 작성한 코드 중 괜찮았던 부분이나 중요하게 생각한 요소들을 PR에 담고 싶은 마음이 들어 점점 글이 길어졌다.


누구에게 보여주기 위해서라기보다는, PR을 작성하면서 나 자신이 완전히 이해하지 못했던 부분을 설명할 수 있도록 다시 공부하고 정리하는 과정 자체가 큰 도움이 되었던 것 같다. 앞으로도 이렇게 PR을 단순한 기록이 아닌, 복습과 정리의 기회로 삼고 싶다.

Problem : 개선이 필요하다고 생각하는 문제점

무작정 코드를 작성하지 않기.

 

기획이나 구조에 대한 고민 없이 코드부터 작성하다 보니, 이후에 테스트 코드나 기능 요구사항을 맞추기 위해 많은 부분을 되돌려야 했다. 이번 주차에서 특히 이런 방식의 비효율성을 크게 느꼈다. 처음부터 코드의 분리 방식이나 모듈화 방향을 가볍게라도 구상하고 시작하는 습관이 필요하다는 걸 절실히 깨달았다. (🥲 -> 리액트로 마이그레이션 하는 과제에서 무작정 Basic 코드를 옮기다가 큰 코 다친 나)

Try : 문제점을 해결하기 위해 시도해야 할 것

작성 전에 가볍게 구조부터 생각하기

 

앞으로는 무작정 코드를 작성하기 전에, 최소한 기능 단위로 어떻게 분리할지, 어떤 흐름으로 동작할지, 반복될 로직은 어떤 식으로 모듈화할지 가볍게 시뮬레이션해본 후 작업을 시작하려 한다.

꼭 거창한 설계를 하지 않더라도, 간단하게 메모하거나 도식화해서 구조를 잡고 나면 테스트 코드나 기능 요구사항에 발목 잡히는 일을 줄일 수 있다. 급하더라도 5~10분 정도 구조를 정리하는 시간을 꼭 확보해서... 덜 고치고 더 이해되는 코드를 작성하는 습관을 들이는 것이 목표다. 그래야 나중에 더 큰 수정을 줄이고, 고생도 덜할 수 있을 것 같다. 🫡