Skip to content

[3팀 이가은] Chapter 2-1. 클린코드와 리팩토링#36

Open
tooth-is-silver wants to merge 63 commits intohanghae-plus:mainfrom
tooth-is-silver:main
Open

[3팀 이가은] Chapter 2-1. 클린코드와 리팩토링#36
tooth-is-silver wants to merge 63 commits intohanghae-plus:mainfrom
tooth-is-silver:main

Conversation

@tooth-is-silver
Copy link

@tooth-is-silver tooth-is-silver commented Jul 28, 2025

과제 체크포인트

배포 url
https://tooth-is-silver.github.io/front_6th_chapter2-1/

기본과제

  • 코드가 Prettier를 통해 일관된 포맷팅이 적용되어 있는가?
  • 적절한 줄바꿈과 주석을 사용하여 코드의 논리적 단위를 명확히 구분했는가?
  • 변수명과 함수명이 그 역할을 명확히 나타내며, 일관된 네이밍 규칙을 따르는가?
  • 매직 넘버와 문자열을 의미 있는 상수로 추출했는가?
  • 중복 코드를 제거하고 재사용 가능한 형태로 리팩토링했는가?
  • 함수가 단일 책임 원칙을 따르며, 한 가지 작업만 수행하는가?
  • 조건문과 반복문이 간결하고 명확한가? 복잡한 조건을 함수로 추출했는가?
  • 코드의 배치가 의존성과 실행 흐름에 따라 논리적으로 구성되어 있는가?
  • 연관된 코드를 의미 있는 함수나 모듈로 그룹화했는가?
  • ES6+ 문법을 활용하여 코드를 더 간결하고 명확하게 작성했는가?
  • 전역 상태와 부수 효과(side effects)를 최소화했는가?
  • 에러 처리와 예외 상황을 명확히 고려하고 처리했는가?
  • 코드 자체가 자기 문서화되어 있어, 주석 없이도 의도를 파악할 수 있는가?
  • 비즈니스 로직과 UI 로직이 적절히 분리되어 있는가?
  • 코드의 각 부분이 테스트 가능하도록 구조화되어 있는가?
  • 성능 개선을 위해 불필요한 연산이나 렌더링을 제거했는가?
  • 새로운 기능 추가나 변경이 기존 코드에 미치는 영향을 최소화했는가?
  • 코드 리뷰를 통해 다른 개발자들의 피드백을 반영하고 개선했는가?
  • (핵심!) 리팩토링 시 기존 기능을 그대로 유지하면서 점진적으로 개선했는가?

심화과제

  • 변경한 구조와 코드가 기존의 코드보다 가독성이 높고 이해하기 쉬운가?
  • 변경한 구조와 코드가 기존의 코드보다 기능을 수정하거나 확장하기에 용이한가?
  • 변경한 구조와 코드가 기존의 코드보다 테스트를 하기에 더 용이한가?
  • 변경한 구조와 코드가 기존의 모든 기능은 그대로 유지했는가?
  • (핵심!) 변경한 구조와 코드를 새로운 한번에 새로만들지 않고 점진적으로 개선했는가?

과제 셀프회고

과제를 하면서 내가 제일 신경 쓴 부분은 무엇인가요?

  1. AI한테 어떤 룰을 적용해야할까?
  • 토스 프론트엔드 가이드라인 기반으로 만든 Cursor rule을 적용했었는데 추후 멘토링시 자잘한 파일로 짧은 룰을 여러개 적용하는 것이 훨씬 AI를 똑똑하게 사용하는 방법이라고 조언 받아 클로드 코드 룰을 다음과 같이 정의하였다.
    • 디스코드에 박지수 학메가 공유해준 정석호 학메의 프롬프트 첨부 (커서 룰이지만 클로드 코드에도 동일하게 적용) image
    • 과제 내부 docs 폴더의 md파일을 참고하여 ✅ 좋은 예를 참고하도록 적용했다.
      image
    • 그 외에 중요하다고 생각하는 부분, 혹은 가져가고 싶은 폴더 구조(리액트 마이그레이션 대비하여 context나 비즈니스 로직이 담긴 hooks폴더를 만들기, handler와 ui 분리 등)를 참고하도록 적용했다.
  1. 마이그레이션을 어떻게 하면 잘 할수있을까?
  • 고민 1 : 1차 시도에서 전체 파일을 전달해서 어떻게 정리할지 가늠해보려고 했으나 너무나도 더러운(...) 코드다보니 한 채팅에서 해결을 못하고 리팩토링시 에러가 나거나 놓치는 부분이 많다는 것을 느꼈다.
  • 고민 2 : 2차 시도때 UI 분리, 상수 분리, 핸들러 분리, DOM 분리 식으로 진행하여 차근차근 하나씩 해결해 나가고자 하였다. 하지만 AI가 놓치고 작업하지 않거나 기존 코드를 너무 존중해줘서 수작업이 필요한 상황이 많이 발생되었다.
  • 고민 3 : advanced 리액트 마이그레이션 과정으로 넘어가고자 했으나, basic의 틀을 잘 잡고 진행해야 마이그레이션을 수월하게 할 수 있을거라 생각되어 다시 3차 시도로 basic부터 시작하였다. 사용하지 않는 코드들을 모두 제거하고 오버엔지니어링 진행되는 부분을 수작업으로 한땀한땀 체크하면서 작업 사항을 모두 확인했다.
  • 해결 : basic을 좀 더 다듬고 그 기반으로 advanced를 진행하니 1,2차 시도때보다 훨씬 리액트스럽게 진행되었다.

과제를 다시 해보면 더 잘 할 수 있었겠다 아쉬운 점이 있다면 무엇인가요?

리팩토링은 너무 큰 단위 말고 내가 잘 아는 작은 단위부터 시작하는게 제일 좋다는 것을 경험했다. basic 과제 진행시 바닐라 자바스크립트의 경우에 리액트로 치면 컴포넌트와 state간의 정확한 분리가 어려워 UI를 어디까지 보고 핸들러를 분리해야할지 고민이 많았다. 기존 origin 파일로 실행된 사이트의 UI를 보면서 도메인을 이해하고 분리하려고 노력했다. 예를 들어 장바구니에 담기는 이벤트나 수량이 증가,감소하는 이벤트, 장바구니에서 상품이 제거되는 이벤트, 특정 수량 이상일 경우 출력되는 텍스트들과 할인율... 마치 UI/UX 기획서를 뜯어보는 듯한 느낌이었다.

그래서 다음에 진행한다면 작은 단위에서 진행하되 비즈니스 로직(도메인)에 따라서 진행해보고 싶다. 도메인에 맞춰서 하나씩 나누다보면 저절로 덩어리로 뭉쳐지고 자연스럽게 분리될 것으로 예상한다. 현재는 일단 핸들러와 ui를 분리하고, 그 이후에 도메인에 맞춰서 개선을 진행했는데 도메인에 맞춰서 덩어리화 한 다음에 도메인 별로 점진적으로 작은 단위부터 개선했다면 좀 더 빠르게 작업했을 것 같다.
이렇게 느낀 이유는, 상수, 핸들러, UI 등 해당 프로세스대로 분리하니 도메인 로직들이 정확히 어디에 어느 부분에서 동작하는지 파악하기가 어려웠다. 너무 더럽고 많은 코드에서 이 코드가 포인트에서 사용되는 코드가 맞는가? 라고 판단했을때 확실치 않았다.

리뷰 받고 싶은 내용이나 궁금한 것에 대한 질문 편하게 남겨주세요 :)

  1. 멘토링 때 답변 못받고 넘어간 부분인데, 코치님이 리팩토링을 진행하신다면 어떤 절차대로 진행하실지 궁금합니다!image

  2. AI를 적극적으로 활용하는건 너무 좋았는데, 열심히 쓰다보니 이게 맞나..? 해당 주차에서 내가 얻어가는게 뭐가 있을까, AI활용성 고민과 AI로의 문제해결 밖에 안남은 것 같은데 만약 복기하게 된다면 어떻게 복기하면 좋을지 고민입니다.

@k-sang-soo
Copy link

k-sang-soo commented Aug 1, 2025

고생하셨습니다 가은님!
멘토링 때 코치님께 AI 사용 꿀팁 못 듣고 시작한거 너무 아쉽겠네요!!ㅋㅋㅋㅋ

Copy link

@tomatopickles404 tomatopickles404 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이가튼튼님 구경왔숨니다~!
이번주 스트레스 많이 받아하셨는데 완주 하셔서 다행입니당ㅎ ㅎ
"나 이가은. 해냈다."
저의 부족한 식견이지만 조금 까불다 가보겠습니다..!
다음주도 화이팅이애요... ❤️

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

우와 저는 테스트코드 못짰는데 이것까지 해내셨군요 👍👍

Comment on lines 32 to 63
const priceDisplay = useMemo(() => {
if (isOnLightningSale && isSuggestedSale) {
return (
<>
<span className="line-through text-gray-400">
₩{originalPrice.toLocaleString()}
</span>{" "}
<span className="text-purple-600">₩{price.toLocaleString()}</span>
</>
);
} else if (isOnLightningSale) {
return (
<>
<span className="line-through text-gray-400">
₩{originalPrice.toLocaleString()}
</span>{" "}
<span className="text-red-500">₩{price.toLocaleString()}</span>
</>
);
} else if (isSuggestedSale) {
return (
<>
<span className="line-through text-gray-400">
₩{originalPrice.toLocaleString()}
</span>{" "}
<span className="text-blue-500">₩{price.toLocaleString()}</span>
</>
);
} else {
return `₩${price.toLocaleString()}`;
}
}, [isOnLightningSale, isSuggestedSale, originalPrice, price]);
Copy link

@tomatopickles404 tomatopickles404 Aug 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jsx를 메모이제이션 하셨군요! 뭔가 안티패턴 같긴 한데.. 메모가 필요했던 이유가 있을까요?
제 생각에는 jsx에 분기처리가 필요하다면, jsx 내부에서 조건부 렌더링을 하는게 가장 적절한 방식이 아닐까 생각합니다!
메모이제이션이 필요하다면 그 상태에서 각 컴포넌트에 memo를 사용하는건 어떨까요?

{isOnLightningSale && isSuggestedSale ? (
  <SuperSalePrice price={price} originalPrice={originalPrice} />
) : isOnLightningSale ? (
  <LightningSalePrice price={price} originalPrice={originalPrice} />
) : isSuggestedSale ? (
  <SuggestedSalePrice price={price} originalPrice={originalPrice} />
) : (
  <RegularPrice price={price} />
)}

이런식으로 각 조건에 해당하는 jsx를 컴포넌트로 분리해서 표현하면 깔끔할 것 같아용 (ai가 짜준건데 사실 이것 좀..)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오~ 이런 방식으로 공용컴포넌트화 해서 나눠보겠습니다 의견 감사해요!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2c03391

분리하여 좀 더 리팩토링해보기 👍

Comment on lines +18 to +59
const [internalIsOpen, setInternalIsOpen] = useState(false);

const isOpen =
controlledIsOpen !== undefined ? controlledIsOpen : internalIsOpen;

const handleToggle = useCallback(() => {
if (onToggle) {
onToggle();
} else {
setInternalIsOpen((prev) => !prev);
}
}, [onToggle]);

const handleOverlayClick = useCallback(
(e: MouseEvent) => {
if (e.target === e.currentTarget) {
handleToggle();
}
},
[handleToggle]
);

const handleKeyDown = useCallback(
(e: KeyboardEvent) => {
if (e.key === "Escape") {
handleToggle();
}
},
[handleToggle]
);

useEffect(() => {
if (isOpen) {
document.body.style.overflow = "hidden";
} else {
document.body.style.overflow = "unset";
}

return () => {
document.body.style.overflow = "unset";
};
}, [isOpen]);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

훅으로 분리하면 좋겠네요!

Comment on lines 81 to 85
if (item.quantity > STOCK_THRESHOLDS.OUT_OF_STOCK) {
return `${item.name}: 재고 부족 (${item.quantity}개 남음)`;
} else {
return `${item.name}: 품절`;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

얼리 리턴이 조금 더 가독성에 좋을 것 같아요 ㅎㅎ

// 방법 1: early return
if (item.quantity > STOCK_THRESHOLDS.OUT_OF_STOCK) {
  return `${item.name}: 재고 부족 (${item.quantity}개 남음)`;
}

return `${item.name}: 품절`;

// 방법 2: 조건을 변수로 분리
const hasStock = item.quantity > STOCK_THRESHOLDS.OUT_OF_STOCK;

if (hasStock) {
  return `${item.name}: 재고 부족 (${item.quantity}개 남음)`;
}

return `${item.name}: 품절`;

아니면 여기서는 삼항 연산자가 조금 더 가독성이 좋을 것 같기도 하네용?

const stockMessage = item.quantity > STOCK_THRESHOLDS.OUT_OF_STOCK
  ? `${item.name}: 재고 부족 (${item.quantity}개 남음)`
  : `${item.name}: 품절`;

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

역시 대장! 삼항연상자 괜찮네요 깔끔하게 변경 완료 🫡

Comment on lines +106 to +114
const totalStock = useMemo(
() => products.reduce((total, product) => total + product.quantity, 0),
[products]
);

const isLowStock = useMemo(
() => totalStock < stockWarningThreshold,
[totalStock, stockWarningThreshold]
);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 단순한 계산들에 useMemo가 꼭 필요했을까? 라는 생각이 들어용ㅎㅎ

Comment on lines +116 to +121
const handleProductSelect = useCallback(
(e: ChangeEvent<HTMLSelectElement>) => {
onProductSelect(e.target.value);
},
[onProductSelect]
);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handleProductSelect 안에 on함수가 적절한건가?! 싶은 생각이 있습니다.
준일님의 글을 추천해요!!!! 여기에 핸들러 네이밍에 관한 언급을 읽어보시면 도움이 될 것 같습니다!

https://junilhwang.github.io/TIL/clean-code/%EC%A1%B0%EA%B0%81%EB%AA%A8%EC%9D%8C/%EC%9D%B4%EB%B2%A4%ED%8A%B8%EC%99%80_%ED%95%A8%EC%88%98%EB%8A%94_%EB%8B%A4%EB%A5%B4%EB%8B%A4/

@ckdwns9121
Copy link
Member

가은님 이번주 고생많으셨어요..!
담주차 과제는 다른 방식으로 페어코딩 시도해보죠 ㅎㅎ

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants