[5팀 김유현] Chapter 2-3. 관심사 분리와 폴더구조#33
Conversation
JunilHwang
left a comment
There was a problem hiding this comment.
안녕하세요, 김유현 님! 5주차 과제 잘 진행해주셨네요. 고생하셨습니다 👍🏻👍🏻
현재 남기는 코멘트는 GPT-5-mini model을 사용하여 AI가 남기는 피드백입니다 🤖
과제 피드백과는 별개이므로 참고해주세요!
1. 🏗️ FSD 아키텍처
💡 개념 정의
Feature-Sliced Design(FSD)은 기능(Feature)별로 관심사를 분리하고, 계층을 명확히 하여 변경에 의한 파급을 최소화하는 아키텍처 원칙입니다. 일반적으로 app → pages → widgets → features → entities → shared 순서로 의존성이 흐르도록 설계합니다.
⚡ 중요성
FSD를 통해 새로운 기능 추가, 아키텍처 변경(모노레포, 마이크로프론트엔드), UI 라이브러리 교체 시 최소한의 파일만 수정하면 되어 유지보수성과 확장성이 크게 향상됩니다.
📊 현재 상황 분석
PostsManagerPage.tsx에 서버 호출(fetch), 로컬 상태(useState로 서버/클라이언트 상태 혼합), 라우터 파라미터 동기화, 여러 다이얼로그 렌더링이 모두 혼재. shared/ui는 components/index.tsx로 추상화되어 있어 디자인 변경 대응은 상대적으로 용이하지만, 핵심 도메인 로직이 분산되지 않아 FSD의 이점을 못 받고 있습니다.
📝 상세 피드백
개요 및 평가 요약:
현재 코드베이스는 FSD(Feature-Sliced Design)의 일부 원칙(components, pages 분리)을 따르고 있으나 실제 계층 책임 분리가 미흡합니다. PostsManagerPage.tsx 파일에 UI, 비즈니스 로직, 서버 통신, 라우팅 URL 동기화, 모달 상태 등 거의 모든 관심사가 한 파일에 집중되어 있어 FSD의 계층(entities/features/widgets/shared/pages/app) 방향성(상위→하위)과 역할 분리가 지켜지지 않습니다.
핵심 문제: 단일 페이지가 entities(도메인), feature(행동), shared(UI) 책임을 혼합하고 있어 변경의 파급 범위가 큽니다. 예: API 엔드포인트 변경 또는 서버 상태 관리 전략 변경 시 PostsManagerPage.tsx 내 수십 지점을 수정해야 할 가능성이 큽니다.
권장 TO-BE 구조 요약:
- entities/post: types, api (postApi.getPosts, postApi.getByTag 등), model (데이터 가공)
- features/posts-list: usePosts hook (서버 상태/쿼리 키), UI 조합(테이블 + 페이징 로직)
- widgets/post-detail / post-editor: 재사용 가능한 UI/상태(로컬 UI 상태)
- shared/ui: Button, Select 같은 디자인 컴포넌트(현재 존재)
정량적 근거/시나리오: - AS-IS: PostsManagerPage.tsx에 약 20개의 fetch 호출 지점(추정). API 클라이언트 교체 시 약 20개 파일 내(실제로 1파일 내 20지점) 수정 필요.
- TO-BE: postApi 한 곳으로 추상화하면 API 교체 시 1파일 수정(entities/post/api/postApi.ts)로 해결.
구체적 개선 단계(예상 소요):
- 단기(1일): PostsManagerPage.tsx에서 fetch 호출을 추출해 entities/post/api/postApi.ts 생성. (변경범위: PostsManagerPage에서 호출부 교체)
- 중기(1-2일): usePosts/usePostByTag/useComments 등 커스텀 훅으로 추출(Features 레이어). Post 목록 렌더링은 features/posts-list에서 담당.
- 장기(2-4일): 모듈화 및 테스트 추가, 폴더 구조 정리(FSD 표준에 맞춘 slice 생성).
❌ 현재 구조 (AS-IS)
AS-IS 예시 (PostsManagerPage.tsx 발췌):
fetch(`/api/posts?limit=${limit}&skip=${skip}`)
.then((response) => response.json())
.then((data) => { /* ... */ })
// 컴포넌트 내부에 fetch/then 체인이 여러 곳 존재, UI/상태/로직 혼재✅ 권장 구조 (TO-BE)
TO-BE 예시:
// entities/post/api/postApi.ts
export const postApi = {
getPosts: (params) => httpClient.get('/posts', { params }),
getByTag: (tag) => httpClient.get(`/posts/tag/${tag}`),
addPost: (payload) => httpClient.post('/posts/add', payload),
}
// features/posts/hooks/usePosts.ts (TanStack Query 사용)
export const usePosts = (params) => useQuery(queryKeys.posts(params), () => postApi.getPosts(params))
// pages/PostsManagerPage.tsx에서는 usePosts를 사용해 UI에 데이터를 주입만 함🔄 변경 시나리오별 영향도
- 디자인 시스템(Material UI 등) 변경: shared/ui만 교체하면 UI 변경량은 적음(components/index.tsx가 추상화 레이어 역할을 함).
- API 엔드포인트 변경: AS-IS는 PostsManagerPage.tsx 내부의 모든 fetch 호출 수정(약 15~25곳). TO-BE는 entities/post/api 단 한 곳 수정.
- Features 추가(예: 오프라인 모드): 현재 구조는 PostsManagerPage에 많은 책임이 있으므로 오프라인 캐시 도입 시 수많은 로직을 변경해야 함; 분리하면 feature별 훅/스토어만 확장하면 됨.
🚀 개선 단계
- 1단계: 단계 1 (1일): 모든 fetch 호출을 entities/post/api/postApi.ts 로 이전. PostsManagerPage에서는 postApi를 호출하도록 변경.
- 2단계: 단계 2 (1-2일): 서버 상태는 TanStack Query 훅(usePosts, usePost, useComments)으로 전환하고 컴포넌트는 훅을 통해 데이터만 소비하도록 변경.
- 3단계: 단계 3 (1-2일): UI(모달/폼)은 widgets 또는 features 폴더로 분리. 각 모듈은 단일 책임을 갖게 함.
- 4단계: 단계 4 (2-4일): 폴더구조를 FSD 규칙에 맞춰 정리 및 문서화, 라우트/페이지는 최소한의 조합 레이어로 유지.
2. 🔄 TanStack Query
💡 개념 정의
TanStack Query는 서버 상태 관리를 선언적, 캐시 기반으로 처리하는 라이브러리입니다. useQuery/useMutation 훅을 통해 데이터 페칭, 캐싱, 리패칭, 에러 및 로딩 상태 관리를 중앙화합니다.
⚡ 중요성
복잡한 비동기 흐름을 간결하게 만들고, 캐싱/리패칭 전략을 통해 성능 개선과 네트워크 비용 절감을 제공합니다. 또한 API 변경 시 쿼리 키와 API 래퍼만 수정하면 되므로 확장성과 유지보수성이 향상됩니다.
📊 현재 상황 분석
PostsManagerPage.tsx 내부에 여러 fetch 호출 및 상태관리(useState)가 존재합니다. 예: fetch(/api/posts?limit=${limit}&skip=${skip}) 등. 쿼리 키가 없고, 캐싱/낙관 업데이트 전략이 전혀 없습니다.
📝 상세 피드백
개요 및 평가 요약:
심화과제로 요구된 TanStack Query 적용이 PR 내에 거의 적용되어 있지 않습니다. 현재 PostsManagerPage.tsx에서는 fetch + useState/useEffect 패턴을 사용해 서버 데이터를 처리하고 있어 선언적 서버 상태 관리의 장점을 활용하지 못하고 있습니다.
핵심 문제: 쿼리 키의 일관성 부재, API 호출의 분산, 캐싱/리패칭 전략 부재, 낙관적 업데이트 없음. 변화 시 수정範囲이 큼(예: 에러 처리 전략 변경 또는 새로운 데이터 소스 추가).
권장 사항 요약:
- queryKeys 팩토리화 (shared/api/queryKeys.js)
- entities/*/api에 순수 CRUD 함수 집약
- features/*/api에서 useQuery/useMutation 래핑(비즈니스 로직 분리)
- 캐싱 전략(staleTime, cacheTime)과 에러/로딩 처리를 일관되게 설정
- 낙관적 업데이트는 useMutation의 onMutate/onError/onSettled를 사용하여 구현
구체적 TO-BE 코드 예시 제공 및 단계별 개선 계획 포함.
❌ 현재 구조 (AS-IS)
AS-IS (PostsManagerPage.tsx 발췌):
const fetchPosts = () => {
setLoading(true);
fetch(`/api/posts?limit=${limit}&skip=${skip}`)
.then((r) => r.json())
.then((data) => { /* setPosts, setTotal */ })
.finally(() => setLoading(false));
}
// 여러 곳에 동일 패턴 반복✅ 권장 구조 (TO-BE)
TO-BE (TanStack Query 적용 예시):
// shared/api/queryKeys.ts
export const queryKeys = { posts: (params) => ['posts', params] };
// entities/post/api/postApi.ts
export const postApi = { getPosts: (params) => httpClient.get('/posts', { params }) };
// features/posts/hooks/usePosts.ts
export const usePosts = (params) => useQuery({
queryKey: queryKeys.posts(params),
queryFn: () => postApi.getPosts(params),
staleTime: 1000 * 60, // 1분
});
// 페이지
const { data, isLoading } = usePosts({ skip, limit });🔄 변경 시나리오별 영향도
- API 엔드포인트 변경: AS-IS는 페이지 내 모든 fetch 호출을 찾아 변경해야 함(약 15~25곳). TO-BE는 postApi만 수정하면 됨.
- 새 데이터 소스(예: 외부 사용자 서비스) 추가: useQuery 훅을 추가하고 queryKeys와 postApi를 연결하면 페이지 수정 최소화.
- 에러 처리 방식 변경(글로벌 to 지역): TanStack Query 설정과 onError 핸들러만 조정하면 전역 정책 적용 가능.
🚀 개선 단계
- 1단계: 단계 1 (반나절): queryKeys 유틸 생성 및 postApi로 fetch 호출 이동.
- 2단계: 단계 2 (1일): usePosts/usePostByTag/useComments 등 TanStack Query 훅 구현 및 PostsManagerPage에서 대체.
- 3단계: 단계 3 (반나절): useMutation으로 add/update/delete 구현, 낙관적 업데이트(onMutate/onError rollback) 적용.
- 4단계: 단계 4 (반나절): Devtools 추가, 전역 에러 핸들러 설정 및 문서화.
3. 🎯 응집도 (Cohesion)
💡 개념 정의
응집도(Cohesion)는 모듈 내부 요소들이 얼마나 관련성 높게 묶여 있는지를 뜻합니다. 높은 응집도는 '같은 이유로 변경되는 코드'가 한 곳에 모여 있어 유지보수가 쉽습니다.
⚡ 중요성
응집도가 높으면 한 기능을 변경할 때 수정 범위가 작아지고 테스트가 쉬워지며, 모듈 단위로 패키지화/분리(예: 도메인 패키지화)가 용이합니다.
📊 현재 상황 분석
파일이 비대해 유지보수성과 온보딩 난이도가 높습니다. 기능 단위로 분리하면 새 기능(예: 댓글 알림, 실시간 업데이트) 통합이 훨씬 쉬워집니다.
📝 상세 피드백
개요 및 평가 요약:
현재 응집도는 낮습니다. '게시물' 관련 기능(목록, 페이징, 검색, 태그 필터링, 댓글, 사용자 모달, CRUD)이 PostsManagerPage.tsx 한 파일에 섞여 있어, 하나의 기능 변경(예: 댓글 엔드포인트 변경)이 여러 코드 블록을 건드리게 합니다.
왜 문제인가?
응집도가 낮으면 변경 시 수정 파일 수 증가, 버그 유입, 테스트 난이도 상승 등이 발생합니다. 예를 들어 댓글 구조가 변경되면 댓글 관련 로직과 댓글을 표시하는 UI, 댓글 추가/삭제/좋아요 로직이 분리되어 있지 않으므로 여러 곳을 수정해야 합니다.
개선 방향(TO-BE):
- 댓글 관련 기능은 features/comments로 묶어 useComments 훅과 CommentsWidget으로 분리
- 게시물 목록 + 페이징은 features/posts-list에서 담당
- API는 entities/post/api에 집중
❌ 현재 구조 (AS-IS)
AS-IS: Comments 로직과 UI가 같은 파일에 섞여 있음:
const fetchComments = async (postId) => { /* fetch & setComments */ }
const renderComments = (postId) => (/* UI + 버튼 핸들러(좋아요, 수정, 삭제) */)✅ 권장 구조 (TO-BE)
TO-BE:
// features/comments/useComments.ts
export const useComments = (postId) => useQuery(queryKeys.comments(postId), () => commentApi.getByPost(postId));
// widgets/CommentsWidget.tsx
const CommentsWidget = ({ postId }) => {
const { data: comments } = useComments(postId);
return <CommentsList comments={comments} />;
};🔄 변경 시나리오별 영향도
- 댓글 구조 변경: AS-IS는 PostsManagerPage 내 댓글 관련 여러 지점을 수정해야 함. TO-BE는 features/comments만 수정.
- 검색 로직 변경(서버 vs 클라이언트): 현재 검색 로직이 페이지에 섞여 있어 변경 시 리스크가 큼; 검색 훅으로 이전하면 영향 범위 축소.
🚀 개선 단계
- 1단계: 단계 1 (반나절): 댓글 관련 함수(fetchComments, addComment, updateComment, deleteComment, likeComment)를 entities/comment/api로 이동.
- 2단계: 단계 2 (1일): comments 훅(useComments)과 CommentsWidget 분리, PostsManagerPage에서는 위젯 조합만 담당.
- 3단계: 단계 3 (1일): 게시물 편집/추가/삭제도 각 feature로 분리하여 응집도 향상.
4. 🔗 결합도 (Coupling)
💡 개념 정의
결합도(Coupling)는 모듈 간 의존성의 강도를 말합니다. 낮은 결합도는 한 모듈의 변경이 다른 모듈에 미치는 영향을 줄여 유연성을 높입니다.
⚡ 중요성
낮은 결합도는 기술 스택 교체(예: axios→fetch), 아키텍처 변화(모노레포 분리), 외부 서비스 변경에 더 유연하게 대응할 수 있게 합니다.
📊 현재 상황 분석
HTTP 클라이언트 변경 시 약 15~25 지점의 수정이 필요할 것으로 보이며, 이는 큰 리팩토링 부담을 의미합니다. 의존성 주입이나 추상화가 없으므로 테스트 시 전역 fetch mocking에 의존해야 합니다.
📝 상세 피드백
개요 및 평가 요약:
결합도는 비교적 높은 편입니다. PostsManagerPage.tsx가 구체적인 fetch 구현과 window URL 동기화, 직접 DOM 이벤트 처리를 포함하고 있어 다른 모듈(예: auth, http 클라이언트)으로의 교체가 어렵습니다.
핵심 문제: HTTP 클라이언트(현재 fetch)와 컴포넌트가 직접 결합되어 있어 axios나 다른 클라이언트로 교체할 때 많은 지점을 수정해야 합니다. 또한 알림/로깅(analytics)과 같은 부가 기능을 주입할 수 있는 추상화가 없습니다.
개선 방향:
- http client abstraction(httpClient) 도입(axios/fetch wrapper) 및 DI 패턴 적용
- postApi, userApi 등의 인터페이스에 의존하도록 변경
- 훅이나 props로 콜백(onSuccess/onError)을 주입해 UI-로직 결합 완화
❌ 현재 구조 (AS-IS)
AS-IS 예시:
await fetch('/api/posts/add', { method: 'POST', headers: {...}, body: JSON.stringify(newPost) })
// 컴포넌트 내부에 직접 fetch 사용✅ 권장 구조 (TO-BE)
TO-BE 예시:
// shared/http/httpClient.ts
export const httpClient = { get: (url, opts) => fetch(url, opts).then(r => r.json()), post: (...) }
// entities/post/api/postApi.ts
export const postApi = { addPost: (payload) => httpClient.post('/posts/add', payload) }🔄 변경 시나리오별 영향도
- HTTP 클라이언트 교체(axios→fetch): AS-IS는 각 fetch 호출 수정 필요(약 15~25곳). TO-BE는 httpClient wrapper 1곳 수정.
- 상태관리 라이브러리 변경(redux→zustand): 현재는 로컬 useState 기반이라 migration 범위는 페이지 단위이지만 전역 상태가 확장되면 추상화 필요.
🚀 개선 단계
- 1단계: 단계 1 (반나절): httpClient wrapper 생성 (get/post/put/delete) 및 기존 fetch 호출을 httpClient로 전환.
- 2단계: 단계 2 (반나절): entities/*/api로 API 호출 집중화.
- 3단계: 단계 3 (1일): DI 또는 context를 통해 httpClient를 주입 가능하게 만들어 테스트 및 스택 변경을 용이하게 함.
5. 🧹 Shared 레이어 순수성
💡 개념 정의
Shared 레이어는 도메인에 독립적인 UI 컴포넌트와 유틸을 포함하여 다른 slice에서 재사용 가능한 코드를 제공합니다. 도메인 로직이 들어가면 재사용성이 떨어집니다.
⚡ 중요성
Shared의 순수성은 새로운 프로젝트나 다양한 도메인으로 컴포넌트를 재사용할 때 유지보수 비용을 낮추고, 디자인 시스템 변경 시 수정 범위를 최소화시킵니다.
📊 현재 상황 분석
Shared 계층 자체는 비교적 순수하지만, 일부 컴포넌트의 displayName 또는 forwardRef 타입 정의가 반복적이므로 코드 스타일 정리가 필요합니다. 전반적으로 shared/ui는 잘 구성되어 있습니다.
📝 상세 피드백
개요 및 평가 요약:
components/index.tsx는 Button, Input, Card, Dialog 등 많은 UI primitives를 제공하고 있어 shared/ui 레이어로서 역할을 잘 하고 있습니다. 이 점은 디자인 시스템 교체(예: Tailwind → Chakra) 시 이 파일만 교체하면 되는 장점을 제공합니다.
주의 사항: shared 레이어 내에 도메인 로직(예: user-specific label 등)이 섞여있지 않은지 확인 필요. 현재 PR에서는 shared가 범용 컴포넌트로 잘 분리되어 있어 재사용성과 순수성이 확보되어 있습니다.
❌ 현재 구조 (AS-IS)
AS-IS: shared/ui (components/index.tsx)에는 Button, Card, Dialog 등이 정의되어있음. PostsManagerPage는 import { Button, Card, Select } from '../components' 형태로 사용.✅ 권장 구조 (TO-BE)
TO-BE: shared/ui는 완전히 도메인 독립적으로 유지하고, 도메인별 Badge 같은 것은 features/user/ui에서 Badge를 조합하여 생성.
// shared/ui/Badge.tsx (도메인 없음)
// features/user/UserStatusBadge.tsx (도메인 로직 포함)🔄 변경 시나리오별 영향도
- 디자인 시스템 변경: components/index.tsx에서 스타일 클래스만 대체하면 프로젝트 전반에 적용 가능(수정 파일 수: 1~2개).
- 도메인 요구(예: UserStatusBadge 도메인 의존): 도메인 로직은 features에서 구현하고 shared는 단순 Badge로 유지.
🚀 개선 단계
- 1단계: 단계 1 (반나절): components/index.tsx에 있는 컴포넌트들의 prop-types/타입을 정리하고 문서화.
- 2단계: 단계 2 (반나절): shared 레이어에 도메인 로직이 섞여있는지 스캔하고 분리.
- 3단계: 단계 3 (1일): 디자인 시스템 교체 시나리오로 시도적 리팩토링(components 레이어만 변경) 수행.
6. 📐 추상화 레벨
💡 개념 정의
추상화는 복잡한 구현 세부사항(네트워크, DB, 라이브러리 특성)을 숨기고 재사용 가능한 인터페이스(함수, 훅, 클래스)만 노출하는 것입니다.
⚡ 중요성
높은 추상화는 기술 교체, 테스트, 재사용성을 용이하게 합니다. 특히 API나 상태관리 전략 변경 시 수정 범위를 줄여줍니다.
📊 현재 상황 분석
비즈니스 규칙(예: postsWithUsers 생성)은 페이지 내부에서 작성되어 재사용 불가. 또한 URL 업데이트 로직(updateURL)도 페이지 내부에 있어 재사용성이 없음.
📝 상세 피드백
개요 및 평가 요약:
비즈니스 로직과 기술적 세부사항(HTTP, 라우터 파라미터 처리, 로컬 UI 상태)이 PostsManagerPage 내에 섞여 있어 추상화 수준이 낮습니다. 핵심 비즈니스 개념(Posts, Comments, Users)이 코드에서 명확히 캡슐화되어 있지 않아 재사용 및 테스트가 어렵습니다.
권장 방향:
- API 추상화(entities/*/api)
- 비즈니스 훅(features/*/hooks)으로 도출
- UI는 단순 표현(프레젠테이셔널 컴포넌트)으로 유지
❌ 현재 구조 (AS-IS)
AS-IS 예시:
const fetchPosts = () => { fetch('/api/posts') /* 후처리(유저 매칭) */ }
// 유저 매칭 로직도 이 함수 내부에 있음✅ 권장 구조 (TO-BE)
TO-BE 예시:
// entities/post/model/postModel.ts
export const attachAuthors = (posts, users) => posts.map(p => ({ ...p, author: users.find(u => u.id === p.userId) }));
// pages/usePosts.ts
const { data } = useQuery(...)
const postsWithAuthors = useMemo(() => attachAuthors(data.posts, users), [data, users]);🔄 변경 시나리오별 영향도
- 새로운 데이터 소스(서버 B) 통합: 추상화가 잘 되어있으면 postApi만 확장하면 되지만, 현재는 페이지 곳곳을 수정해야 함.
- 인증 방식 변경: API 호출마다 토큰 첨부 로직을 httpClient로 옮기면 쉽지만, 현재는 분산되어 있음.
🚀 개선 단계
- 1단계: 단계 1 (반나절): 도메인 유틸(attachAuthors 등)을 entities/*/model로 추출.
- 2단계: 단계 2 (1일): API abstraction과 비즈니스 훅으로 분리하여 컴포넌트는 뷰만 담당하도록 변경.
- 3단계: 단계 3 (1일): 컴포넌트 경량화(프레젠테이셔널 / 컨테이너 패턴 적용).
7. 🧪 테스트 용이성
💡 개념 정의
테스트 용이성은 코드가 얼마나 독립적으로, 빠르게, 신뢰성 있게 테스트될 수 있는지를 나타냅니다. 순수 함수, 의존성 주입, 모듈화가 주요 요인입니다.
⚡ 중요성
테스트 가능한 구조는 회귀 버그를 줄이고 리팩토링 시 안정성을 보장합니다. 특히 서버 API 변경, UI 리팩토링 같은 변화에 대해 빠른 피드백을 제공합니다.
📊 현재 상황 분석
단위 테스트를 위해선 PostsManagerPage를 쪼개서 훅(데이터)과 프레젠테이셔널 컴포넌트(뷰)로 분리해야 합니다. 이렇게 하면 훅은 mock된 api로, 뷰는 props 주입으로 테스트 가능.
📝 상세 피드백
개요 및 평가 요약:
현재 구조는 테스트하기 어렵습니다. fetch가 컴포넌트에 분산되어 있고 많은 로컬 상태(useState)와 사이드 이펙트(useEffect)에 의존하고 있어 단위 테스트와 통합 테스트 작성 시 많은 mocking과 복잡한 셋업이 필요합니다.
권장 개선:
- API 호출을 entities/*/api로 이동하면 api 레이어만 모킹하면 됨
- 비즈니스 훅(usePosts 등)을 만들면 훅 단위 테스트가 가능
- 프레젠테이셔널 컴포넌트로 분리하면 snapshot/unit 테스트 용이
❌ 현재 구조 (AS-IS)
AS-IS 예시: 컴포넌트 내부에서 fetch 호출 후 setState + analytics 트래킹 등 사이드 이펙트가 섞여 있음 -> 테스트하려면 네트워크/analytics 모두 mocking 해야 함.✅ 권장 구조 (TO-BE)
TO-BE 예시: usePosts 훅을 만들고 postApi를 주입하여 훅 단위로 mock postApi를 이용한 테스트 작성.
const { result } = renderHook(() => usePosts(), { wrapper: QueryClientProvider });🔄 변경 시나리오별 영향도
- 새 외부 API 연동: api 레이어를 모킹하면 훅/컨테이너 테스트만으로 검증 가능.
- 복잡한 비즈니스 로직 추가: 도메인 로직을 순수 함수로 추출하면 단위 테스트로 빠르게 검증 가능.
🚀 개선 단계
- 1단계: 단계 1 (반나절): API 추상화 및 postApi 모킹 가능한 인터페이스로 전환.
- 2단계: 단계 2 (1일): 훅/프레젠테이셔널 분리 후 훅 단위 테스트, UI 스냅샷/유닛 테스트 추가.
- 3단계: 단계 3 (1일): CI에 테스트 실행 파이프라인 추가.
8. ⚛️ 현대적 React 패턴
💡 개념 정의
현대 React 패턴은 선언적 비동기 처리(Suspense), 에러 경계(ErrorBoundary), 재사용 가능한 커스텀 훅 등을 활용해 UI와 로직의 관심사를 분리합니다.
⚡ 중요성
이 패턴들은 로딩/에러 상태 처리를 일관되게 만들고, UI가 복잡해졌을 때도 컴포넌트 책임을 명확히 유지하도록 돕습니다.
📊 현재 상황 분석
로딩 처리(setLoading)와 에러 로깅(console.error)가 분산되어 있어 일관된 사용자 경험 제공이 어렵습니다. ErrorBoundary나 Suspense 미사용으로 예외 상황에 대한 복원력도 낮습니다.
📝 상세 피드백
개요 및 평가 요약:
현재 코드에서는 Suspense, ErrorBoundary, React Query(데이터 패칭의 선언적 패턴) 등이 활용되지 않았습니다. 대신 useEffect/useState와 명령형 패턴을 사용하고 있어 로딩/에러 처리가 분산되어 있습니다.
권장 사항:
- TanStack Query + Suspense 조합(가능한 경우)으로 선언적 로딩 처리
- ErrorBoundary로 페이지/위젯 단위 에러 격리
- 비즈니스 로직은 커스텀 훅으로 분리하여 컴포넌트는 단순 렌더링만 담당
❌ 현재 구조 (AS-IS)
AS-IS: setLoading(true) / setLoading(false) 패턴이 여러 fetch에 반복되어 있음.
AS-IS 예: fetchPosts() { setLoading(true); fetch(...).finally(() => setLoading(false)); }✅ 권장 구조 (TO-BE)
TO-BE: usePosts 훅 + Suspense
<Suspense fallback={<Spinner/>}>
<PostsList />
</Suspense>
// PostsList 내부는 데이터가 준비된 상태에서 렌더링됨🔄 변경 시나리오별 영향도
- 로딩 UX 변경: 현재는 각 로딩 플래그를 변경해야 하지만, Suspense로 전환하면 상위 fallback 컴포넌트만 교체하면 됨.
- 에러 처리 중앙화: ErrorBoundary로 래핑하면 특정 컴포넌트의 에러가 전체 앱을 중단시키지 않음.
🚀 개선 단계
- 1단계: 단계 1 (반나절): TanStack Query 훅 적용으로 로딩/에러 상태 관리를 중앙화.
- 2단계: 단계 2 (반나절): 페이지 레벨에 Suspense와 ErrorBoundary 적용(점진적 적용 권장).
- 3단계: 단계 3 (반나절): 커스텀 훅으로 비즈니스 로직 분리.
9. 🔧 확장성
💡 개념 정의
확장성(Extensibility)은 새로운 요구사항을 도입할 때 기존 구조를 최소한으로 변경하고 쉽게 통합할 수 있는 능력입니다.
⚡ 중요성
프로덕트 성장과 요구사항 변화에 빠르게 대응하려면 확장성이 중요합니다. 초기 설계가 확장성을 뒷받침하면 장기 유지비용이 감소합니다.
📊 현재 상황 분석
예: 실시간 댓글 동기화 기능을 추가할 때 socket 연결과 메시지 수신 핸들러를 페이지에 붙이면 테스트/유지보수가 어려움. 대신 useRealtimeComments 훅으로 분리하면 영향 범위를 줄일 수 있음.
📝 상세 피드백
개요 및 평가 요약:
현재 구조는 새로운 기능(다국어, 실시간 소켓 연동, 오프라인 모드 등)을 추가할 때 확장성이 떨어질 가능성이 큽니다. 많은 기능이 단일 페이지 파일에 집중되어 있어 cross-cutting concern(예: i18n, A/B 테스트) 적용 시 넓은 수정 범위를 초래합니다.
권장 접근법:
- cross-cutting 기능(i18n, feature flags)은 App 레벨 또는 shared 레이어에 도입
- 실시간 기능은 features/posts에서 소켓 훅으로 분리
- 오프라인은 캐싱 전략을 담당하는 레이어(entities/api / service worker)에서 처리
❌ 현재 구조 (AS-IS)
AS-IS: PostsManagerPage에 하드코딩된 텍스트(예: '로딩 중...', '이전', '다음')와 다수의 UI 문자열이 포함되어 있음.✅ 권장 구조 (TO-BE)
TO-BE: i18n 적용 후
<Text>{t('pagination.prev')}</Text>
// i18n provider로 전체 앱 래핑🔄 변경 시나리오별 영향도
- A/B 테스트 도입: feature-flag를 통해 컴포넌트별 분기 처리를 하면 Pages는 훅/컴포넌트 교체만으로 대응 가능.
- 다국어(i18n) 도입: 텍스트 리터럴을 컴포넌트에서 제거하고 i18n 키를 사용하면 전체 검색/교체가 쉬워짐.
🚀 개선 단계
- 1단계: 단계 1 (반나절): 문자열 리터럴을 i18n 키로 추출(필요 시 간단한 i18n 라이브러리 도입).
- 2단계: 단계 2 (1일): 실시간/오프라인 등의 cross-cutting concern을 훅/서비스로 추상화.
- 3단계: 단계 3 (1-2일): Feature Flags 또는 Config 기반 환경 분기 도입.
10. 📏 코드 일관성
💡 개념 정의
코드 일관성은 네이밍, 파일명, import/export 패턴 및 스타일 가이드가 일관되게 적용되어 있는 상태를 말합니다. 일관성은 가독성과 협업 효율을 높입니다.
⚡ 중요성
일관된 스타일은 새로운 개발자 온보딩을 빠르게 하고, 자동화 도구(빌드/분석)가 안정적으로 동작하게 합니다. 또한 불필요한 충돌을 줄입니다.
📊 현재 상황 분석
components/index.tsx는 스타일 정리 및 displayName 통일이 잘 되어있음. 반면 PostsManagerPage.tsx는 포맷팅/문법 문제뿐 아니라 중복으로 인해 export가 여러 번 선언되는 등 치명적 상태입니다.
📝 상세 피드백
개요 및 평가 요약:
PR에는 스타일 정리(따옴표 통일, 세미콜론 추가 등)가 전반적으로 적용되어 있어 코드 일관성 측면에서는 개선이 보입니다. 다만 PostsManagerPage.tsx에 심각한 문제(파일 끝부분의 대량 중복 복붙/잔여 텍스트)가 포함되어 있어 문법 오류 및 유지보수 위험이 큽니다.
구체적 이슈:
- 파일 말미에 중복된 블록(electedUser?.age} ... export default PostsManager)이 여러 번 붙어 있어 빌드 오류를 유발하거나 예기치 않은 동작을 초래함.
- Import/Export 패턴: 대부분 named/default export 일관되게 사용되고 있으나 components/index.tsx는 많은 named exports로 잘 구성되어 있음.
권장 조치: - PostsManagerPage.tsx의 중복/잡다한 텍스트 제거(즉시 수정 필요)
- ESLint/Prettier 설정으로 코드 스타일 및 빌드 에러 예방
❌ 현재 구조 (AS-IS)
문제 예시(파일 말미 발췌):
... export default PostsManager
electedUser?.age}
</p>
<p> <strong>이메일:</strong> {selectedUser?.email}</p>
... // 동일 블록 여러 번 반복✅ 권장 구조 (TO-BE)
정상 예시:
export default PostsManager;
// 파일 끝에 중복 텍스트/코드 없음🔄 변경 시나리오별 영향도
- 자동화 도구 도입(ESLint/Prettier): 코드 스타일 문제는 PR 단계에서 자동으로 잡을 수 있음.
- 파일 중복 제거: 즉시 제거하면 빌드가 정상화되고 코드 리뷰가 용이해짐.
🚀 개선 단계
- 1단계: 긴급(즉시): PostsManagerPage.tsx 말미의 중복 블록과 잔여 텍스트를 제거하여 문법/빌드 오류 해결.
- 2단계: 단기(반나절): ESLint/Prettier 설정 추가 및 pre-commit 훅(Husky + lint-staged) 적용으로 스타일/문법 자동 검증.
- 3단계: 중기(1일): 코드 리뷰 체크리스트에 스타일/빌드 검증 항목 추가.
🎯 일관성 체크포인트
Import/Export 패턴
- components/index.tsx는 named exports를 일관되게 사용 중이라 양호함
변수명 규칙
- 전반적으로 camelCase 사용, 큰 이슈는 발견되지 않음
코드 스타일
- PostsManagerPage.tsx 말미의 중복 및 문법 오류(잉여 텍스트/중복 export) — 즉시 제거 필요
- ESLint/Prettier 자동화 부재(또는 적용 누락) 가능성
11. 🗃️ 상태 관리
💡 개념 정의
데이터 흐름 및 상태 관리는 전역 서버 상태(server state), 전역 클라이언트 상태(global UI state), 지역 컴포넌트 상태(local state)를 명확히 분리하는 것을 말합니다.
⚡ 중요성
서버 상태와 클라이언트 상태를 명확히 분리하면 캐싱, 리패칭, 낙관적 업데이트, 그리고 스케일링(실시간/오프라인) 시 복잡도를 관리하기 쉬워집니다.
📊 현재 상황 분석
다음 개선을 권장합니다:
- useQuery 기반 훅으로 posts/comments/tags 관리
- 다이얼로그 등 UI 토글은 local state 또는 feature-level store로 유지
- URL 파라미터(updateURL 함수)는 useEffect 훅 안으로 옮겨 캡슐화 또는 useUrlSync 훅으로 추출
📝 상세 피드백
개요 및 평가 요약:
서버 상태와 클라이언트 UI 상태가 PostsManagerPage.tsx에 혼재되어 있습니다. 예: posts, comments, tags(서버 상태)와 showAddDialog, selectedPost(로컬 UI 상태)이 동일 레이어에 존재합니다. 전역 상태관리 도구(예: Jotai, Zustand 등)는 사용되지 않았고, TanStack Query로의 전환도 미적용 상태입니다.
권장 접근:
- 서버 상태: TanStack Query로 중앙화
- 클라이언트 UI 상태: feature/local state 또는 lightweight store(Jotai/Zustand)로 분리
- URL 동기화는 별도의 훅(useUrlSync)으로 분리
❌ 현재 구조 (AS-IS)
AS-IS 예시:
const [posts, setPosts] = useState([]);
const fetchPosts = () => { fetch(...).then(data => setPosts(data.posts)) }
// 검색/필터링/페이징이 모두 로컬 상태와 섞여 구현됨✅ 권장 구조 (TO-BE)
TO-BE 예시:
const { data: posts } = usePosts({ skip, limit });
const { mutate: addPost } = useAddPostMutation();
// UI는 훅만 사용하여 렌더링🔄 변경 시나리오별 영향도
- 실시간 동기화: 서버 상태를 TanStack Query로 관리하면 websocket 이벤트로 invalidateQueries만 호출하면 되므로 적용 용이.
- 오프라인 모드: 캐싱 전략과 service worker 연동으로 오프라인 UX 개선 가능.
🚀 개선 단계
- 1단계: 단계 1 (반나절): posts/tags/comments 관련 fetch를 postApi/commentApi로 이동.
- 2단계: 단계 2 (1일): TanStack Query 훅(usePosts/useComments/useTags)로 전환.
- 3단계: 단계 3 (반나절): 로컬 UI 상태(다이얼로그 등)는 useState 또는 작은 상태관리 라이브러리로 유지하여 역할 분리.
🤔 질문과 답변
질문: '리뷰 받고 싶은 내용이나 궁금한 것' 항목이 비어 있습니다. 대신 아래 사항들을 중심으로 권장드립니다:
- 현재 코드의 최우선 수정은 무엇인가요?
- 답: PostsManagerPage.tsx 말미의 중복/잡다한 텍스트 제거(빌드 오류 우려)와 API 호출 추출(postApi 생성)입니다. 우선 빌드가 통과하도록 문법 오류를 제거하고, fetch 호출을 한 곳으로 모으는 작업을 권장합니다.
- TanStack Query를 어떻게 도입하면 좋을까요?
- 답: posts/tags/comments 각 리소스별 entities//api에 순수 CRUD 함수를 만들고, features//hooks에서 useQuery/useMutation 래핑을 만든 뒤 페이지는 훅을 통해 데이터만 소비하도록 리팩터하세요. 낙관적 업데이트는 useMutation의 onMutate/onError/onSettled로 구현하면 됩니다.
- 적용 우선순위와 예상 시간은?
- 답: (1) 긴급: 문법 오류/중복 제거(0.5일). (2) 단기: API 추출(postApi/commentApi)(0.5
1일). (3) 중기: TanStack Query 훅 전환(12일). (4) 장기: FSD 폴더 재배치 및 테스트 추가(2~4일).
🎯 셀프 회고 & 제안
작성자의 회고(출장으로 과제 미진행, FSD 개념 학습 등)에 대한 피드백 및 확장 질문:
인사이트: FSD의 핵심 개념을 이해하신 점은 훌륭합니다. 현재 PR에서 문제는 '이해'는 되어 있으나 '적용' 단계에서 미완성(특히 TanStack Query와 관심사 분리 미적용)이라는 점입니다. 학습 곡선 상의 자연스러운 단계로 보입니다.
생각을 더 확장해볼 질문들:
- '내가 FSD에서 가장 먼저 정리하고 싶은 slice는 무엇인가요?' (예: posts, comments, users 중 우선순위 결정)
- '현재 한 파일에 있는 책임을 나눈다면 첫 번째로 어떤 함수를 추출하겠는가?' (fetch -> api, highlightText -> util 등)
- '향후 팀 규모가 커졌을 때, 누가 어느 slice를 담당할지 소유권을 어떻게 나눌 것인가?'
추가 제안: 과제 1~2단계(문법/중복 제거, API 추출)를 먼저 수행하면서 작은 성공사례를 만들고(예: usePosts 훅 하나 완성), 점차 다른 기능으로 확장해 보세요. 이렇게 하면 학습 부담을 줄이며 FSD 적용 경험을 쌓을 수 있습니다.
추가 논의가 필요한 부분이 있다면 언제든 코멘트로 남겨주세요!
코드 리뷰를 통해 더 나은 아키텍처로 발전해 나가는 과정이 즐거웠습니다. 🚀
이 피드백이 도움이 되었다면 👍 를 눌러주세요!
과제 체크포인트
기본과제
목표 : 전역상태관리를 이용한 적절한 분리와 계층에 대한 이해를 통한 FSD 폴더 구조 적용하기
체크포인트
심화과제
목표: 서버상태관리 도구인 TanstackQuery를 이용하여 비동기코드를 선언적인 함수형 프로그래밍으로 작성하기
체크포인트
최종과제
과제 셀프회고
해외 출장 이슈로 과제를 진행하지 못 했습니다..
이번 과제를 통해 이전에 비해 새롭게 알게 된 점이 있다면 적어주세요.
본인이 과제를 하면서 가장 애쓰려고 노력했던 부분은 무엇인가요?
아직은 막연하다거나 더 고민이 필요한 부분을 적어주세요.
이번에 배운 내용 중을 통해 앞으로 개발에 어떻게 적용해보고 싶은지 적어주세요.
챕터 셀프회고
클린코드: 읽기 좋고 유지보수하기 좋은 코드 만들기
결합도 낮추기: 디자인 패턴, 순수함수, 컴포넌트 분리, 전역상태 관리
응집도 높이기: 서버상태관리, 폴더 구조
리뷰 받고 싶은 내용이나 궁금한 것에 대한 질문