[5팀 허정석] Chapter 2-1. 클린코드와 리팩토링#41
Conversation
- ESLint 및 Prettier 패키지 설치 - package.json에 lint, lint:fix, format 스크립트 추가
- _ 에서 - 로 변경
- eslint 실행
- .eslintrc.js 기반으로 ESLint 설정 방식 통일 - eslint-config-airbnb-typescript, eslint-plugin-prettier 등 의존성 추가 - Prettier와의 충돌 방지를 위한 설정 (eslint-config-prettier) 포함
- 화요일 테스트를 위한 코드 적용
- root > rootElement
- let, const 적용 - doRenderBonusPoints > renderBonusPoints
- prodList
- 상수 추출: 매직 넘버를 DISCOUNT_RATES, POINTS_CONFIG 등으로 구조화 - 함수 분해: handleCalculateCartStuff() 함수를 여러 작은 함수로 분할 - 유틸리티 함수 분리: findProductById(), calculateIndividualDiscount() 등 추가 - 명확한 네이밍: onUpdateSelectOptions → updateSelectOptions 등 개선 - 중복 코드 제거: renderBonusPoints() 함수 제거 및 로직 통합 - 버그 수정: selItem 변수 정의, cartItems.forEach 오류 수정
- calculateCartItems, calculateDiscounts 함수 추가
- main() 함수를 작은 단위 함수들로 분리 - 변수 명 개선 - var -> let / const 변경
- handleCartItemClick(): 메인 클릭 이벤트 처리 - handleQuantityChange(): 수량 변경 로직 - handleRemoveItem(): 상품 제거 로직 - 재고 부족 알림 로직 추가
- CommonJS와 ESM 호환성 문제 해결 - package.json의 ESLint 버전을 8.x로 다운그레이드 chore: ESLint 설정 파일 확장자 변경 (.js → .cjs)
- 코드 분석 및 개선 필요 사항 추가 - 우선순위별 개선 계획 추가
3dc452f to
8caf85c
Compare
- 타입 정의 강화 (types/index.ts) - 서비스 함수 시그니처 강화 - 커스텀 훅 타입 강화 - 컴포넌트 Props 타입 강화 - 데이터 타입 강화 - any 타입 완전 제거
- 표준화된 에러 타입 정의 - 에러 팩토리 함수 생성 - 표준화된 에러 핸들러 - 서비스 레이어 에러 처리 강화 - 서비스 레이어 에러 처리 강화
|
회사에 경험에 빗대어 글을 시작한게 인상적이네요. |
- 모듈별 논리적 단위를 명확히 구분하여 가독성 향상 - 적절한 줄바꿈과 주석으로 코드 구조 개선 - 테스트 통과를 위한 기능 수정
- 중복 import 제거 및 통합 - NodeJS.Timeout → number 타입 변경 (브라우저 환경 대응) - JSX.Element → React.ReactElement 타입 변경 - readonly 배열 타입 적용으로 불변성 보장 - Error 객체 사용으로 스택 트레이스 개선 - 중첩 삼항 연산자를 if-else로 변경 - any 타입 제거로 타입 안정성 향상
|
정석님의 AI 활용 능력이 나날이 발전하는 것 같아서 대단하다고 생각해요. |
|
솔직히 AI 진짜 잘쓰는거 같음 인정 🙂 |
|
정석님 그래서 뭐 때문이었어요?! 해결했다고만 알려주시면.. 저 궁금해서 잠 못장!!!!!!!! |
|
히스토리를 남기는 부분이 정말 인상적입니다! 저는 어떠한 이슈들을 직면했다가 해결 하고 나면 너무 여러 단계를 거쳐서 어디서부터 기록해야할지 너무 어려워서 포기했었는데 뒤늦게 후회가 됩니다.. 한 주 고생 많으셨습니다..! |
| if (!isOpen) return null; | ||
|
|
||
| // 모달 헤더 렌더링 통합 | ||
| const renderModalHeader = () => { |
|
|
||
| return ( | ||
| <> | ||
| {renderDiscountList()} |
| // 에러 처리 관련 타입 | ||
| export type ErrorType = 'error' | 'warning' | 'info'; | ||
|
|
||
| export type ErrorCode = | ||
| | 'STOCK_INSUFFICIENT' | ||
| | 'PRODUCT_NOT_FOUND' | ||
| | 'NETWORK_ERROR' | ||
| | 'VALIDATION_ERROR' | ||
| | 'TIMER_ERROR' | ||
| | 'CART_ERROR' | ||
| | 'DISCOUNT_ERROR' | ||
| | 'LOYALTY_ERROR'; | ||
|
|
||
| export interface AppError { | ||
| code: ErrorCode; | ||
| message: string; | ||
| type: ErrorType; | ||
| details?: unknown; | ||
| timestamp?: number; | ||
| } |
There was a problem hiding this comment.
AppError를 타입으로 정의했는데 저같은 경우에는 Error를 상속해서 사용하는걸 좋아합니다
export type ErrorType = 'error' | 'warning' | 'info';
export type ErrorCode =
| 'STOCK_INSUFFICIENT'
| 'PRODUCT_NOT_FOUND'
| 'NETWORK_ERROR'
| 'VALIDATION_ERROR'
| 'TIMER_ERROR'
| 'CART_ERROR'
| 'DISCOUNT_ERROR'
| 'LOYALTY_ERROR';
// AppError 클래스 정의
export class AppError extends Error {
code: ErrorCode;
type: ErrorType;
details?: unknown;
timestamp: number;
constructor({
code,
message,
type = 'error',
details,
timestamp = Date.now(),
}: {
code: ErrorCode;
message: string;
type?: ErrorType;
details?: unknown;
timestamp?: number;
}) {
super(message);
this.name = 'AppError';
this.code = code;
this.type = type;
this.details = details;
this.timestamp = timestamp;
}
}이런식으로 에러를 정의를 한다면?
throw new AppError({
code: 'STOCK_INSUFFICIENT',
message: '재고가 부족합니다.',
type: 'warning',
});이런식으로 사용할 수 있겠죠?
만약 저렇게 Error를 상속해서 사용한다면
// AppError를 Error로 변환하는 함수
export const createErrorFromAppError = (appError: AppError): Error => {
const error = new Error(appError.message);
(error as any).code = appError.code;
(error as any).type = appError.type;
(error as any).details = appError.details;
(error as any).timestamp = appError.timestamp;
return error;
};errorFactory에 있는 코드들은 필요없어질거같음~
왜냐면 어차피 다 똑같은 Error이기 때문에
err instanceof Error; // true
err instanceof AppError; // true
typeof err.stack; // string|
교수님 코멘트가 너무 좋아서 남길 게 없네여 wow |
브랜치를 gh-pages 로 갔어야 했어요... main 을 바라보게 설정을 안해놓고 왜 안돼!! 하고 있었던 거임.... 멍청한 놈 |
좋게 봐주셔서 감사합니다 ㅎㅎㅎㅎ 항해를 하면서 계속 느끼는 것 중 하나 기록을 잘하자 ㅋㅋㅋㅋ 작은 단위부터 실천해보시죠 |
과제 체크포인트
기본과제
심화과제
과제 셀프회고
유쾌한_클린코드
클린 코드라는 것을 직접적으로 체험 해보지 못 했었다. 회사 업무에서도 마냥 흘려 듣기만 했다.
그리고 내가 코드를 짜면서도 '그냥 보기좋고 깔끔하게 짜면 되는거 아냐?' 라는 생각으로 코드를 만들었다.
과제를 하면서 정말 더닝크루거효과 를 제대로 느꼈다.
(나는 정말 무식하게 용감하게 코드를 짰구나. 정말 앞뒤 맥락 없이!)
과제를 하면서 내가 제일 신경 쓴 부분은 무엇인가요?
코딩 컨벤션 또는 코딩 가이드라인
실제로 회사 프로젝트를 하면서 코딩 컨벤션을 따로 정하지 않고 있었다.
그래서 이번 과제를 하면서는 코딩 컨벤션에 관심이 생기고 최대한 지켜보자 라는 마음으로 임했다.
물론 AI 가 상당 부분을 도와줬지만, 그렇게함으로써 나 혼자 하는 것보다 질적으로 높은 수준으로
과제를 이끌어 갈 수 있었다고 생각한다. (물론 AI 를 완벽하다고 생각하진 않지만, 적어도 나보단 낫다.)
다음엔 팀원들과 코딩 컨벤션에 대해서 얘기를 나눠보면 좋겠단 생각을 했다.
마일스톤
그리고 나는 완전히 AI-driven 방식으로 과제를 진행했고, 내 손으로 코드를 바꾼 건은 맹세코 단 한 건도 없다.
그래서 더 더욱 마일스톤 을 남기는 것에 집착했다.
내 손으로 코드를 치는 것이 아니니까 돌아올 수 없는 강을 건너지 않도록
혹은 딴 길로 새지 않도록 기능별 또는 구조별 작업이 완료되면 커밋 을 이행하여 자취를 정확히 남겼다.
만약을 대비한 롤백을 위함도 있었다.
(사실 이게 제일 큼)히스토리 관리
또한, 한가지 더 커밋보다 광적으로 집착한 것은 기록이였다.
모든 작업이 끝나면 내가 AI 에게 요청한 내역, AI가 완료한 내역, 궁금증, 트러블 슈팅등
이 과제를 이루기 위한 모든 행위들을 기록했다. (물론 AI가 기록했다.)
PR 및 회고의 이유도 있지만, 타임라인 순으로 기록된 내역들을 AI 가 확인하고
같은 작업을 N 번 반복 하지 않게 만들었다.
똑같은 말 다시 안하게 하는 것이 과제 진행 속도에 영향을 많이 끼칠 것이라고 판단했다.
덕분에 내 기준보다 과제를 빨리 끝마친 것도 있다.
(질적인 것보다 제출 및 체험을 우선으로 생각했다.)
<히스토리 내역 중 일부>
과제를 다시 해보면 더 잘 할 수 있었겠다 아쉬운 점이 있다면 무엇인가요?
이번에 팀원들과 코드에 대해 많은 얘기를 나누지 못해서 다시 한다면 팀원들의 의견을 듣고
코드에 적용 해보고 싶다. 그리고 위에 적은 '히스토리 관리'를 좀 더 나의 개성을 넣어 만들어 보면 어땠을까 라는 생각을 지금 PR을 작성하면서 하게 됐다. 준일 코치 멘토링 때 주제는 기억이 안나지만
테오봇, 준일봇, 오프봇, 성호봇을 만들어서 ~~~ 해보고 ~~~ 하면 ~~ 개쩔지?이런 답변을 해줬다.(개쩔지? 란 단어는 안썼는데 그런 뉘앙스였음.)
그래서 나의 개성이 문서화가 되어 AI 한테 적용을 시키면
정석봇이랑 페어코딩을 하면 흥미로울 것 같고내가 원하는 개발 방향을 잘 이해해주고 추천도 잘 해줄 것 같다는 생각이 들었다.
그래서 기록에 조금 더 신경을 쓰는 방향으로 과제를 진행해봐야겠다.
그리고 AI 로 테스트 코드를 짜보긴 했는데 시간이 없어서 해당 테스트를 만들고 통과를 하는 코드를
작성하지 못했다. 테스트 코드가 있었다면 좀 더 촘촘한 프로젝트가 될 수 있지 않았을까? 라는 생각이 든다.
리뷰 받고 싶은 내용이나 궁금한 것에 대한 질문 편하게 남겨주세요 :)
심화과제로 리액트 + 타입스크립트를 적용해봤습니다.
리액트를 사용을 하지 않는 저에게는 지금 프로젝트 구조가 (src/advanced/**/*) 익숙치 않습니다.
현재는 도메인 중심 설계(Domain-Driven Design) 로 분류를 해놨는데 이 방식이 보편화가 된 방식인지 궁금합니다.
혹은 코치님이 선호하는 구조가 있는지, A 같은 성향의 프로젝트엔 A` 구조 같은 공식이 있는지도 궁금합니다.
src/advanced/
├── 📄 App.tsx # 메인 애플리케이션 컴포넌트
├── 📄 main.tsx # React 앱 진입점
├── 📄 index.css # 전역 스타일
│
├── 🗂️ components/ # 도메인별 컴포넌트 구조
│ ├── 🛒 cart/ # 장바구니 도메인
│ │ ├── 📄 CartContainer.tsx # 장바구니 컨테이너
│ │ ├── 📄 CartItem.tsx # 장바구니 아이템
│ │ └── 📄 index.ts # Barrel export
│ │
│ ├── 📦 product/ # 상품 도메인
│ │ ├── �� ProductSelector.tsx # 상품 선택기
│ │ ├── 📄 StockStatus.tsx # 재고 상태 표시
│ │ └── 📄 index.ts # Barrel export
│ │
│ ├── 🛍️ order/ # 주문 도메인
│ │ ├── 📄 index.ts # Barrel export
│ │ └── 📁 OrderSummary/ # 주문 요약
│ │ ├── 📄 index.tsx # 주문 요약 메인
│ │ └── �� DiscountSection.tsx # 할인 섹션
│ │
│ ├── 🔧 common/ # 공통 UI 컴포넌트
│ │ ├── 📄 Header.tsx # 헤더 컴포넌트
│ │ ├── 📄 DiscountIcon.tsx # 할인 아이콘
│ │ ├── 📄 index.ts # Barrel export
│ │ └── 📁 Toast/ # 토스트 알림
│ │ ├── 📄 Toast.tsx # 토스트 컴포넌트
│ │ ├── �� ToastContainer.tsx # 토스트 컨테이너
│ │ └── 📄 types.ts # 토스트 타입 정의
│ │
│ └── ❓ help/ # 도움말 도메인
│ ├── 📄 index.ts # Barrel export
│ └── 📁 HelpModal/ # 도움말 모달
│ ├── 📄 index.tsx # 도움말 모달 메인
│ └── 📄 PolicySection.tsx # 정책 섹션
│
├── 🔄 contexts/ # React Context
│ ├── 📄 ToastContext.tsx # 토스트 상태 관리
│ └── 📄 TimerContext.tsx # 타이머 상태 관리
│
├── 🎣 hooks/ # 커스텀 훅
│ ├── 📄 useCart.ts # 장바구니 훅
│ ├── �� useErrorHandler.ts # 에러 처리 훅
│ ├── 📄 useModal.ts # 모달 훅
│ └── 📄 useProductSelection.ts # 상품 선택 훅
│
├── ⚙️ services/ # 비즈니스 로직 서비스
│ ├── 📄 cartService.ts # 장바구니 서비스
│ ├── 📄 loyaltyService.ts # 포인트 서비스
│ ├── �� discountService.ts # 할인 서비스
│ └── 📄 timerService.ts # 타이머 서비스
│
├── 📊 types/ # TypeScript 타입 정의
│ └── 📄 index.ts # 중앙화된 타입 정의
│
├── 🛠️ utils/ # 유틸리티 함수
│ ├── 📄 errorHandler.ts # 에러 핸들러
│ ├── 📄 errorFactory.ts # 에러 팩토리
│ └── 📄 discountUtils.ts # 할인 유틸리티
│
├── 📁 data/ # 데이터 레이어
│ └── 📄 productData.ts # 상품 데이터
│
└── 📋 constants/ # 상수 정의
├── 📄 index.ts # 중앙화된 상수
└── 📄 businessRules.ts # 비즈니스 규칙