[6팀 이지현] Chapter 2-3. 관심사 분리와 폴더구조🧦#41
Conversation
JunilHwang
left a comment
There was a problem hiding this comment.
안녕하세요, 이지현 님! 5주차 과제 잘 진행해주셨네요. 고생하셨습니다 👍🏻👍🏻
현재 남기는 코멘트는 GPT-5-mini model을 사용하여 AI가 남기는 피드백입니다 🤖
과제 피드백과는 별개이므로 참고해주세요!
1. 🏗️ FSD 아키텍처
💡 개념 정의
Feature-Sliced Design(FSD)은 애플리케이션을 역할(앱, pages, widgets, features, entities, shared) 별로 분리하여 관심사에 따라 파일을 배치하고 올바른 의존성 방향(app → pages → widgets → features → entities → shared)을 유지하는 아키텍처 패턴입니다.
⚡ 중요성
의존성 방향이 유지되어야 모듈의 재사용성, 교체 가능성, 독립적 배포/패키징 가능성이 확보됩니다. 하위 레이어(shared, entities)가 상위 레이어를 참조하면 폴더 재사용성(다른 프로젝트/패키지로 추출)이 크게 떨어집니다.
📊 현재 상황 분석
발견된 위반 사례:
- shared/ui/layouts/MainLayout.tsx가 pages의 Header/Footer를 직접 import 합니다 (shared → pages 의존):
import Footer from "@/pages/post-manager-page/ui/Footer"
import Header from "@/pages/post-manager-page/ui/Header"
이는 shared가 pages에 의존하는 역방향 의존성입니다.
기타 관찰:
- entities와 features의 역할은 전반적으로 잘 지켜짐. posts CRUD는 entities/post/api에, 사용자 행위(모달 열기 등)는 feature/widget 레이어에 위치함.
- 그러나 일부 api 책임이 중복됨: entities/post/api, features/add-post/api, widgets/posts-with-users/api 등에서 유사한 API 호출/조합 코드가 존재합니다.
📝 상세 피드백
FSD(Feature-Sliced Design) 관점에서 전반적으로 레이어 분리(entities / features / widgets / pages / shared)가 잘 적용되어 있습니다. 다만 중요한 의존성 방향 위반이 발견됩니다: shared 레이어(공통 UI/레이아웃)가 pages(상위 레이어)를 참조하고 있어 계층 역전 문제가 발생합니다. 이로 인해 디자인 시스템/레이아웃 변경, 페이지 구조 변경 시 shared 레이어가 영향을 받아 확장성과 재사용성이 저하됩니다. 또한 일부 API/비즈니스 로직의 위치(entities vs features vs widgets)에 대해 팀 내 일관된 규칙이 필요합니다(예: CRUD는 entities, 사용자 행위/조합/오케스트레이션은 feature/widget).
❌ 현재 구조 (AS-IS)
shared/ui/layouts/MainLayout.tsx
import Footer from "@/pages/post-manager-page/ui/Footer"
import Header from "@/pages/post-manager-page/ui/Header"
// shared 레이어에서 pages 레이어를 직접 참조하고 있음✅ 권장 구조 (TO-BE)
// TO-BE: shared는 pages를 참조하지 않음
// 옵션 A: Header/Footer를 shared로 이동
// shared/ui/layouts/MainLayout.tsx
import { Header, Footer } from "@/shared/ui/layouts/parts"
// 또는 옵션 B: MainLayout을 pages 레이어로 이동시키고 shared는 순수 컴포넌트만 보유
// pages/layouts/MainLayout.tsx (페이지 전용 레이아웃)
import { SharedHeader, SharedFooter } from "@/shared/ui"🔄 변경 시나리오별 영향도
- UI 라이브러리(Material UI → Chakra) 전환: shared/ui 내부 컴포넌트만 대대적으로 수정하면 되지만, shared가 pages에 의존하면 페이지별 커스텀 레이아웃도 함께 수정해야 할 가능성 증가.
- 모노레포 전환: shared 레이어를 패키지화하려면 pages 의존을 제거해야 함(현재는 불가능).
- 기능 추가(다국어 등): shared/layout이 pages에 종속되어 있으면 루트 레이아웃 교체 시 pages 쪽도 손봐야 함.
- 패키지 분리: entities만 떼어내어 재사용하려면 features/widgets에서 entities로만 의존하는 규칙 필요.
- 팀 합병: 다른팀 코드와 병합 시 레이어 규칙 불일치는 온보딩 비용/컨플릭트 증가.
🚀 개선 단계
- 1단계: 즉시(1일): shared/ui/layouts/MainLayout.tsx에서 pages 의존 제거. Header/Footer를 shared로 옮기거나(권장) MainLayout을 pages로 이동.
- 2단계: 단기(1~3일): 레이어 의존성 규칙을 문서화(README)하고 ESLint(경로 패턴)를 통해 'shared → pages' 같은 역방향 의존을 탐지하도록 설정.
- 3단계: 중기(1~2주): 프로젝트 의존성 그래프( madge 또는 depcruise )를 생성하여 자동화된 검사 파이프라인에 통합.
- 4단계: 장기(2~4주): 레이어별 패키지화(예: entities, shared를 별도 패키지로 추출) 및 CI에서 의존성 규칙을 강제.
2. 🔄 TanStack Query
💡 개념 정의
TanStack Query는 서버 상태를 선언적으로 가져오고 캐싱, 갱신, 낙관적 업데이트를 지원하는 라이브러리입니다. 핵심은 queryKey의 일관성, queryFn/ API 분리, 캐시 정책(staleTime/cacheTime) 설정, 에러/재시도 전략입니다.
⚡ 중요성
서버 상태가 중심인 애플리케이션에서 일관된 쿼리 키와 계층화된 API 분리는 엔드포인트 변경, 리프레시 전략 변경, 새로운 데이터 소스 추가 시 수정 범위를 최소화합니다.
📊 현재 상황 분석
긍정적 포인트:
- useQuery/useMutation이 전역적으로 일관되게 사용됨.
- 낙관적 업데이트(onMutate) 및 onError rollback 구현 있음.
개선 포인트:
- Query key가 분산되어 있음(문자열/배열 리터럴 혼용). 중앙화된 queryKeys 파일이 없음.
- QueryProvider에서 queyrClient 변수명과 옵션에 gcTime 사용: 실제 옵션명은 cacheTime입니다(오타로 인해 의도한 GC 동작이 미발현될 수 있음).
- setQueriesData 호출에 queryKey를 객체({ queryKey: [...] })로 전달하는 곳과 배열로 직접 전달하는 곳이 혼재되어 일관성 저하.
📝 상세 피드백
TanStack Query를 잘 활용해 서버상태를 분리하고 캐싱/비동기 로직을 선언적으로 관리하고 있습니다. 특히 낙관적 업데이트(onMutate)와 query cache 조작(setQueriesData)을 적절히 사용한 예시(update-comment-like 등)는 긍정적입니다. 그러나 쿼리 키 관리가 중앙화되어 있지 않고, 일부 설정/옵션 오타(예: QueryProvider의 gcTime)는 잠재적 문제를 일으킵니다. 또한 query key의 일관성(팩토리 패턴)이 부족하여 리팩토링 시 수정 범위가 커질 수 있습니다.
❌ 현재 구조 (AS-IS)
// AS-IS: 분산된 쿼리 키와 오타
// src/app/providers/QueryProvider.tsx
const queyrClient = new QueryClient({
defaultOptions: {
queries: {
gcTime: 1000 * 60 * 10, // 오타: 실제 옵션은 cacheTime
refetchOnWindowFocus: false,
retry: 1,
staleTime: 1000 * 60 * 5,
},
},
})
// 훅 예시: usePostsWithUserSummaryQuery의 queryKey
queryKey: ["posts-with-users", { limit, order, search, skip, sortBy, tag }],✅ 권장 구조 (TO-BE)
// TO-BE: 중앙화된 queryKeys와 정리된 QueryClient 옵션
// shared/api/queryKeys.ts
export const queryKeys = {
posts: (params?: any) => ["posts", params] as const,
postsWithUsers: (params?: any) => ["posts-with-users", params] as const,
comments: (postId?: number) => ["comments", postId] as const,
users: (id?: number) => ["users", id] as const,
}
// QueryProvider: cacheTime 오타 수정
const queryClient = new QueryClient({ defaultOptions: { queries: { cacheTime: 1000*60*10, /* ... */ } } })🔄 변경 시나리오별 영향도
- API 엔드포인트가 변경되면 현재 구조에서는 각 훅에서 queryFn이나 엔드포인트를 찾아 수정해야 함(변경 범위: 여러 features 훅). 중앙화된 API/쿼리 키로 개선 가능.
- 새로운 데이터 소스(복수 백엔드) 추가 시 queryKey 전략이 없으면 캐싱/폴리싱 전략을 재정비해야 할 가능성 큼.
- 에러 처리 정책 변경(전역 토스트 등)을 적용할 때 일관된 onError 훅/유틸이 없어 각 훅을 수정해야 함.
🚀 개선 단계
- 1단계: 즉시(반나절): QueryProvider의 옵션 오타(gcTime → cacheTime) 수정 및 queyrClient → queryClient로 변수명 정리.
- 2단계: 단기(1~2일): shared/api/queryKeys.ts를 추가해 모든 훅이 이를 사용하도록 리팩토링(문자열 리터럴 제거).
- 3단계: 단기(1~2일): 공통 에러 핸들러(예: useQueryDefaults 또는 global onError) 도입하여 에러 처리 일원화.
- 4단계: 중기(1주): API 계층(entities/*/api)에 queryFn을 위임하고 features/hooks는 queryKeys + entities API를 조합하도록 정리.
- 5단계: 장기(2주): React Query의 suspense 옵션과 DevTools 설정을 표준화하여 로딩/에러 처리 일관성 확보.
3. 🎯 응집도 (Cohesion)
💡 개념 정의
응집도(Cohesion)는 모듈 내부의 요소들이 얼마나 관련성이 높은지 측정하는 지표입니다. 높은 응집도는 변경 시 관련 코드가 한 곳에 모여 유지보수성을 높입니다.
⚡ 중요성
응집도가 높으면 기능 추가/수정 시 수정 파일 수가 줄고 버그 리스크가 감소합니다. 모듈화를 통한 독립 배포/패키징에도 유리합니다.
📊 현재 상황 분석
문제 포인트:
- entities/post/api와 features/add-post/api가 같은 엔드포인트를 호출하는 중복(또는 역할 혼재).
- widgets/posts-with-users에서 posts+users 조합 로직이 존재하는데, 이 로직은 widgets 레이어에 위치한 것이 합리적(복합 UI)이지만 일부 로직이 feature/get-posts/api로 분산되어 있음.
긍정적 포인트:
- Post 관련 타입/CRUD는 entities에 모여 있어 타입 변경 시 영향 범위가 좁음.
📝 상세 피드백
응집도는 대체로 높은 편입니다. 도메인(entities)별 타입/모델/CRUD가 하나의 폴더로 묶여 있고, feature는 사용자 행동/모달/커스텀 훅을 중심으로 모여있습니다. 다만 API 호출 책임이 entities와 features/widgets에 분산되어 있어 동일 도메인 관련 로직이 여러 폴더에 흩어지는 경우가 존재합니다(중복/산발적 수정 범위 증가).
❌ 현재 구조 (AS-IS)
// AS-IS: 중복된 API 책임
// src/entities/post/api/index.ts -> postApi.getAll
// src/feature/add-post/api/queries.ts -> postApi.create (onSuccess에서 UI 캐시를 직접 조작)
// src/feature/get-posts/api/index.ts -> getPosts (조건부 로직 포함)
// 중복의 예: 게시물 조회 파이프라인이 여러 파일에 분산✅ 권장 구조 (TO-BE)
// TO-BE: 응집도 향상 예시
// entities/post/api/index.ts -> CRUD만 담당
export const postApi = { getAll, getById, create, update, delete }
// features/add-post/api/queries.ts -> entities.postApi.create를 호출하고 캐시 업데이트는 queryKeys를 사용
queryClient.setQueryData(queryKeys.postsWithUsers(params), (prev) => /* ... */)🔄 변경 시나리오별 영향도
- Post 응답 스키마가 변경되면 현재: entities + features + widgets에 분산된 파서를 모두 점검해야 할 수 있음(파일 약 3~6개). 개선 후: entities만 변경하면 상위 레이어 영향 최소화.
- 새로운 필터(예: 작성일 기준)가 추가되면, query serialization이 한 곳에 모여 있으면 1~2개 파일만 수정하면 됨.
🚀 개선 단계
- 1단계: 즉시(반나절): entities에 CRUD 전용 API만 남기고 feature/widget의 중복 API 함수를 entities.postApi로 통합.
- 2단계: 단기(1~2일): getPosts 같은 비즈니스 오케스트레이션은 feature 또는 widget 레이어로 위치 통일(도메인 조합은 widget에).
- 3단계: 중기(1주): 문서화: 'CRUD는 entities, 사용자 행위/오케스트레이션은 feature/widget' 규칙을 코드베이스에 명시하고 PR 템플릿에 체크리스트 추가.
4. 🔗 결합도 (Coupling)
💡 개념 정의
결합도(Coupling)는 모듈 간 의존성의 강도를 나타냅니다. 낮은 결합도는 모듈을 독립적으로 수정/교체할 수 있게 합니다.
⚡ 중요성
낮은 결합은 기술 스택 변경(HTTP 클라이언트, 상태관리 라이브러리 등)이나 아키텍처 전환 시 수정 범위를 획기적으로 줄입니다.
📊 현재 상황 분석
문제 포인트:
- shared → pages 의존성은 Critical: FSD 규칙 위반.
- useModal의 modalId 문자열 정책은 편리하지만 타입 안전성/리팩토링 저항성(예: modalId rename)은 낮음.
- 다른 결합 문제는 적음(entities ← features 방향은 잘 지켜짐).
📝 상세 피드백
결합도 측면에서 몇 가지 주의점이 있습니다. 공통적 문제는 shared → pages 의존(계층 역전)과 useModal/store의 문자열 기반 식별자 사용으로 인한 암묵적 결합입니다. useModal 훅을 통해 많은 컴포넌트가 동일한 전역 modal store에 의존하므로 모달 식별자/데이터 스키마 변경 시 광범위한 수정이 필요할 수 있습니다.
❌ 현재 구조 (AS-IS)
// AS-IS: string 기반 모달 식별자 사용
// src/shared/hooks/useModal.ts
const open = (data?: T) => openModal(modalId, data)
const isOpen = isModalOpen(modalId)
// store: src/shared/store/modal.ts uses modals: Record<string, {data?, isOpen: boolean}>✅ 권장 구조 (TO-BE)
// TO-BE: 타입 안전성 / 낮은 결합 예시
// 1) 각 모달별 typed hook 제공: useAddPostModal(), useUserProfileModal() 등
export const useAddPostModal = () => useTypedModal<'addPost', AddPostData>('addPost')
// 2) 의존성 주입: 모달 매니저를 context로 감싸고 모달 오픈 함수는 props로 주입하여 컴포넌트에서 직접 store를 참조하지 않음.🔄 변경 시나리오별 영향도
- 상태관리 라이브러리 변경(zustand → recoil 등): 현재는 useModalStore와 관련 파일( shared/store/modal.ts, shared/hooks/useModal.ts )을 수정해야 하며, useModal를 사용하는 모든 컴포넌트(약 30개 파일 추정)를 점검해야 함.
- 글로벌 모달 스펙 변경(전달 데이터 타입 변경): modalId 기반 접근법은 모든 호출 지점을 수정해야 하므로 영향 범위 큼.
- UI 라이브러리 교체: shared/ui 내 대부분의 파일(약 10~15개)에서 수정 필요.
🚀 개선 단계
- 1단계: 즉시(반나절): shared → pages 의존 제거(앞서 설명).
- 2단계: 단기(1~2일): modalId 문자열 사용을 점진적으로 타입화(예: union type 또는 enum)하고 도메인별 typed hook을 생성(useAddPostModal 등).
- 3단계: 중기(1주): useModal abstraction을 개선하여 상태관리 라이브러리 교체 비용을 줄이도록 추상화 계층을 삽입(예: adapter 패턴).
5. 🧹 Shared 레이어 순수성
💡 개념 정의
Shared 레이어는 도메인 독립적이며 재사용 가능한 UI, 유틸, 훅, API 클라이언트 등을 제공하는 곳입니다. 다른 레이어에 의존하지 않고 독립적으로 재사용되어야 합니다.
⚡ 중요성
Shared가 특정 도메인에 의존하면 다른 프로젝트에서 재사용하거나 패키지화하기 어렵습니다. 디자인 시스템/컴포넌트 교체 시 전체 코드베이스에 영향이 커집니다.
📊 현재 상황 분석
문제 포인트:
- MainLayout의 pages 의존성은 shared의 가장 큰 규칙 위반.
- 모달 관련 shared/store는 범용적이지만 'modalId' 관례가 프로젝트 전역에 확산되어 refactor 비용 증가.
긍정적 포인트:
- apiClient 래퍼로 HTTP 클라이언트 변경 비용을 최소화한 점은 훌륭합니다.
📝 상세 피드백
shared 계층은 UI 컴포넌트, hooks, apiClient 등 재사용 가능한 요소를 잘 모아두었습니다. 하지만 MainLayout이 pages를 임포트하는 심각한 위반이 있어 shared의 재사용성이 떨어집니다. shared/store/modal과 useModal 훅은 범용적이지만 modalId 정책으로 인해 상위 레이어(특정 모달 구현)와 암시적 결합이 존재합니다. shared는 도메인(예: posts) 지식을 포함하지 않아야 재사용성이 보장됩니다.
❌ 현재 구조 (AS-IS)
// AS-IS: shared 레이어가 pages 레이어를 참조
// src/shared/ui/layouts/MainLayout.tsx
import Footer from "@/pages/post-manager-page/ui/Footer"
import Header from "@/pages/post-manager-page/ui/Header"✅ 권장 구조 (TO-BE)
// TO-BE: shared는 pages에 의존하지 않음
// 옵션 1: Header/Footer를 shared로 이동
// shared/ui/layouts/parts/Header.tsx, Footer.tsx
// or
// 옵션 2: MainLayout을 pages로 이동시키고 shared에는 재사용 가능한 낮은 수준의 컴포넌트만 유지🔄 변경 시나리오별 영향도
- 새 프로젝트에서 shared 재사용 시: 현재 상태에서는 MainLayout을 그대로 가져다 쓸 수 없음(페이지 의존이 있어).
- 디자인 시스템 변경: shared/ui 내부 컴포넌트만 수정하면 큰 이득(현재는 가능), 다만 shared가 pages를 참조하면 일부 변경이 페이지 레이어에 파급될 수 있음.
🚀 개선 단계
- 1단계: 즉시(반나절): MainLayout에서 pages 의존 제거(가장 시급).
- 2단계: 단기(1~2일): modalId -> typed modal hooks로 마이그레이션(useAddPostModal 등)하여 shared의 범용성 유지.
- 3단계: 중기(1주): shared 레이어의 '도메인 독립성 체크리스트' 작성 및 CI에 static 검사(ESLint 규칙)를 추가.
6. 📐 추상화 레벨
💡 개념 정의
추상화는 복잡한 구현 세부사항을 숨기고 재사용 가능한 인터페이스를 제공하는 수준을 의미합니다. 좋은 추상화는 변화 지점(예: HTTP 클라이언트, 엔드포인트)와 비즈니스 로직을 분리합니다.
⚡ 중요성
추상화가 잘 되어있으면 기술 스택 변경이나 기능 확대 시 수정 범위를 좁힐 수 있습니다.
📊 현재 상황 분석
개선 포인트:
- query serialization/ deserialization은 shared 또는 entities에 모아 두고 usePostsQuery는 단지 URL 상태 관찰/조작 역할만 수행하도록 분리하면 테스트 및 재사용성이 좋아짐.
- 일부 네이밍/타입 오타(PostPagiantionReponse 등)가 추상화 신뢰성을 떨어뜨립니다.
📝 상세 피드백
추상화 수준은 전반적으로 적절합니다: entities는 CRUD와 타입을 제공하고 features는 사용자 행위와 비즈니스 로직을 보유합니다. 다만 일부 혼재(예: feature/get-posts/api/getPosts 내부에서 API 선택 로직과 직렬화/조건 판단이 섞여 있음)는 개선 여지가 있습니다. API 호출과 쿼리 직렬화는 entities 또는 shared의 유틸로 추출하면 변경에 더 유연해집니다.
❌ 현재 구조 (AS-IS)
// AS-IS: usePostsQuery가 URL 직렬화/역직렬화를 직접 구현
const updateQuery = useCallback((newParams) => {
const params = new URLSearchParams()
Object.entries({ ...current, ...newParams }).forEach(([key, value]) => { /* ... */ })
setSearchParams(params, { replace: true })
}, [setSearchParams])✅ 권장 구조 (TO-BE)
// TO-BE: 직렬화 유틸로 추상화
// shared/url/serializers.ts
export const serializePostQuery = (params) => { /* URLSearchParams 생성 로직 */ }
export const parsePostQuery = (searchParams) => { /* parse to domain object */ }
// usePostsQuery는 parse/serialize 유틸 사용🔄 변경 시나리오별 영향도
- API 응답 형식 변경: 추상화가 약하면 각 훅/컴포넌트에서 파서를 고쳐야 함(다수 파일).
- 새 엔드포인트 추가(복합 필터): 중앙화된 serializer/ API facade가 없다면 여러 훅을 수정해야 함.
🚀 개선 단계
- 1단계: 즉시(반나절): 직렬화/역직렬화 로직을 shared 또는 entities에 분리(serializePostQuery, parsePostQuery).
- 2단계: 단기(1~2일): 엔드포인트 선택 로직(getPosts)을 명확히 주석/문서화하고 entities/feature 경계에 맞게 재배치.
- 3단계: 중기(1주): 타입 오타(PostPagiantionReponse 등) 정리와 타입 강화(타입 검사 strict)로 추상화 신뢰성 확보.
7. 🧪 테스트 용이성
💡 개념 정의
테스트 용이성은 코드가 얼마나 쉽게 단위/통합/엔드투엔드 테스트로 분리되어 검증 가능한지를 의미합니다. 순수함수, 의존성 주입, 사이드 이펙트 분리는 테스트를 용이하게 합니다.
⚡ 중요성
테스트가 잘 구성되어 있으면 요구사항 변경 시 회귀를 빠르게 방지하고 리팩토링을 안전하게 수행할 수 있습니다.
📊 현재 상황 분석
긍정적:
- MSW 기반의 일관된 테스트 스위트가 존재.
- 통합 시나리오를 통한 핵심 기능 검증(목록/필터/CRUD/댓글 흐름).
개선 포인트:
- usePostsQuery, usePostsWithUserSummaryQuery, useModal 등 핵심 훅에 대한 단위 테스트가 부족.
- 현재 통합 테스트는 유용하지만, 훅/유틸의 단위 테스트가 없으면 리팩토링 시 에지 케이스 보장이 취약.
📝 상세 피드백
테스트 인프라(MSW + React Testing Library)를 잘 갖추어 통합 시나리오를 테스트하고 있습니다. App.test.tsx는 E2E 스타일의 통합 시나리오를 잘 검증합니다. 개선할 점은 단위 테스트와 훅 테스트 확대입니다(특히 usePostsQuery, usePostsWithUserSummaryQuery, useModal 등 핵심 훅에 대한 유닛 테스트). 현재 모달 스토어는 전역 상태라 모킹이 필요하며, typed hooks로 대체하면 테스트가 더 쉬워집니다.
❌ 현재 구조 (AS-IS)
// AS-IS: 통합 테스트 중심
// src/app/App.test.tsx 에서 목록 로드 → 필터 → 상세/댓글/CRUD 통합 시나리오 테스트✅ 권장 구조 (TO-BE)
// TO-BE: 추가 단위 테스트
// src/feature/post-query/__tests__/usePostsQuery.test.ts
// - parse/serialize 로직 테스트
// - getChangedField 로직 테스트
// src/shared/store/__tests__/modal.test.ts
// - modal store open/close/getModalData 테스트🔄 변경 시나리오별 영향도
- API 스펙 변경 시: 통합 테스트는 빨리 실패를 잡지만, 훅 단위 테스트가 있으면 실패 지점 파악이 빨라짐.
- 상태관리 라이브러리 교체 시: useModal 추상화와 단위 테스트가 있으면 변화 비용이 절감됨.
🚀 개선 단계
- 1단계: 즉시(1~2일): 핵심 훅(usePostsQuery, usePostsWithUserSummaryQuery, useModal)의 단위 테스트 추가.
- 2단계: 단기(1주): 테스트 커버리지 목표(예: 중요한 훅/유틸 80% 이상) 설정 및 CI에 커버리지 체크 추가.
- 3단계: 중기(2주): 모킹 헬퍼(예: 렌더 훅 유틸)를 작성하여 반복 코드 제거 및 테스트 작성 비용 절감.
8. ⚛️ 현대적 React 패턴
💡 개념 정의
현대적 React 패턴에는 Suspense, Error Boundary, 커스텀 훅을 통한 로직 분리, 선언적 데이터 페칭 등이 포함됩니다. 이는 로딩/에러 처리를 선언적으로 만들고 컴포넌트 책임을 명확히 합니다.
⚡ 중요성
Suspense/ErrorBoundary를 활용하면 로딩과 에러 처리의 일관성을 높여 UI 복잡도를 줄일 수 있습니다. 커스텀 훅은 비즈니스 로직을 UI에서 분리해 테스트/재사용성을 향상시킵니다.
📊 현재 상황 분석
긍정적:
- 커스텀 훅을 활용하여 로직 분리 시도.
개선 포인트:
- Suspense를 적용하려면 useQuery의 suspense 옵션과 컴포넌트 계층(상위에서 Suspense, ErrorBoundary)을 준비해야 함.
📝 상세 피드백
현대적 React 패턴 일부(커스텀 훅, 컴포넌트 분리, React Query 활용)는 잘 적용되어 있습니다. 다만 Suspense 기반 데이터 페칭과 Error Boundary의 전역 사용은 아직 없음. 또한 일부 컴포넌트는 로직과 UI가 잘 분리되어 있지만, 더 많은 관심을 모아 custom hooks의 책임 최소화(단일 책임)와 Suspense 도입을 고려할 수 있습니다.
❌ 현재 구조 (AS-IS)
// AS-IS: 개별 컴포넌트에서 로딩/에러 처리
// src/widgets/posts-with-users/ui/PostTable.tsx
if (isLoading) return <div>로딩 중...</div>
// React Query의 suspense 옵션 미사용✅ 권장 구조 (TO-BE)
// TO-BE: 상위에서 Suspense/ErrorBoundary로 처리
// src/app/App.tsx
// <ErrorBoundary fallback={<ErrorFallback/>}>
// <Suspense fallback={<Skeleton/>}>
// <AppRouter />
// </Suspense>
// </ErrorBoundary>
// 개별 컴포넌트는 데이터가 제공된다는 가정 하에 단순 렌더링에 집중🔄 변경 시나리오별 영향도
- 로딩 UX 전략(예: skeleton → suspense) 변경 시: 컴포넌트에서 선언적 로딩 처리로 전환하면 변경 범위가 작아짐.
- 에러 처리 방식 통일(전역 ErrorBoundary) 도입 시: 현재 각 컴포넌트의 에러 마크업을 제거하고 중앙화 가능.
🚀 개선 단계
- 1단계: 단기(1~2일): ErrorBoundary/ Suspense 도입 계획 수립(적용 범위 및 fallback 컴포넌트 정의).
- 2단계: 단기(2~3일): 한두 컴포넌트(예: PostsTable + PostDetailModal)를 대상으로 Suspense 적용 실험.
- 3단계: 중기(1주): 성공 사례를 바탕으로 점진적으로 전역 적용.
9. 🔧 확장성
💡 개념 정의
확장성은 새로운 기능 추가나 요구사항 변경(다국어, A/B 테스트, 실시간 등)에 대해 얼마나 적은 변경으로 대응 가능한지를 나타냅니다.
⚡ 중요성
확장성은 팀 성장, 기능 증가, 아키텍처 변화(마이크로프론트엔드/모노레포) 시 유지보수 비용을 결정짓습니다.
📊 현재 상황 분석
긍정적:
- apiClient 추상화로 axios → fetch 전환 비용은 낮음(주로 1 파일 변경).
- React Query 기반 설계로 캐시 전략/낙관적 업데이트 확장 용이.
개선 포인트:
- shared/layout/pages 의존성 제거 및 modal 시스템의 타입화 필요.
📝 상세 피드백
확장성 관점에서 서버 상태와 클라이언트 상태 분리가 잘 되어 있어 기능 추가가 비교적 수월합니다. 중요한 장점은 apiClient 래퍼와 React Query 사용으로 HTTP 클라이언트 변경/캐시 정책 변경의 영향을 최소화한다는 점입니다. 반면, 모달 전역 스토어 및 shared/layout 의존 문제는 확장성의 걸림돌입니다.
❌ 현재 구조 (AS-IS)
// AS-IS: apiClient 한 곳에 집중되어 있음
// src/shared/api/apiClient.ts (axios 래퍼) → HTTP 교체 시 영향이 적음✅ 권장 구조 (TO-BE)
// TO-BE: 모듈화된 확장성
// - shared/api/apiClient.ts 에 adapter 패턴 적용
// - shared/ui 내부 컴포넌트를 통한 디자인 시스템 교체 용이성 확보
// - typed modal hooks로 교체하여 상태관리 교체에 대한 영향 축소🔄 변경 시나리오별 영향도
- HTTP 클라이언트 변경(axios → fetch): 예상 수정 파일 수: 1 (src/shared/api/apiClient.ts) + 타입 보정(0~2) — 낮음.
- UI 라이브러리 변경(예: Radix UI → ChakraUI): shared/ui 컴포넌트(약 10
15개 파일) 수정 필요 — 중간(1015 파일). - 상태관리 라이브러리 교체(zustand → recoil): shared/store/modal + useModal 추상화 레이어 필요 + useModal 사용하는 컴포넌트(약 25~35개)에서 인터페이스 점검 — 높음.
🚀 개선 단계
- 1단계: 즉시(반나절): apiClient의 테스트 및 문서 보강(변경 시 테스트로 보호).
- 2단계: 단기(1주): shared/ui 컴포넌트를 작은 유닛(Button, Input, Dialog) 중심으로 유지하고 디자인 토큰/스타일을 중앙화해 UI 라이브러리 교체 비용 절감.
- 3단계: 중기(1~2주): modal 시스템을 typed hooks + adapter로 리팩토링하여 상태관리 라이브러리 변경 탄력성 확보.
10. 📏 코드 일관성
💡 개념 정의
코드 일관성은 네이밍, 파일명, import/export 패턴, 스타일 규칙이 프로젝트 전반에 일치하는 정도를 말합니다. 일관성은 온보딩과 코드 분석 자동화에 큰 영향을 미칩니다.
⚡ 중요성
일관성은 새로운 팀원 온보딩 속도, 코드 리뷰 효율, 자동화(리팩토링/검색) 신뢰성에 직접적인 영향을 미칩니다.
📊 현재 상황 분석
위의 오타/일관성 문제는 자동 툴(ESLint/Prettier)과 코드리뷰로 쉽게 잡을 수 있습니다. 혼재된 export 방식은 import 시 혼란을 초래할 수 있으며, 패키지화 시 문제를 야기합니다.
📝 상세 피드백
전반적으로 파일 구조/네이밍 규칙이 잘 지켜졌습니다(components는 PascalCase, hooks는 use* 패턴 등). 다만 몇 가지 일관성 이슈와 오타가 발견되어 정리하면 유지보수성이 더 좋아집니다.
❌ 현재 구조 (AS-IS)
문제 샘플들:
- src/widgets/posts-with-users/ui/Paginaition.tsx (파일명 오타)
- src/app/providers/QueryProvider.tsx: const queyrClient = new QueryClient(...)
- src/shared/hooks/useDebounceValue.ts: setDbouncedValue (오타)
- src/pages/post-manager-page/ui/Footer.tsx : export default Footer (default export 혼재)✅ 권장 구조 (TO-BE)
권장 형태:
- 파일명 통일: Pagination.tsx (PascalCase)
- 변수명/함수명 오타 수정(쿼리Client → queryClient, setDebouncedValue)
- 컴포넌트는 named export로 통일: export const Footer = () => {} / export { Footer }
- 훅: useDebounceValue.ts (export const useDebounceValue = ...)🔄 변경 시나리오별 영향도
- 새 팀원이 합류하거나 코드베이스 병합시: 일관성 없는 네이밍/exports는 학습 코스트 증가.
- 자동화 도구(코드 모듈 분석/리팩토링) 적용 시: 네이밍/파일명 일관성이 없으면 스크립트가 깨질 수 있음.
🚀 개선 단계
- 1단계: 즉시(반나절): 오타 수정(que ryClient → queryClient, setDbouncedValue → setDebouncedValue, Pagination 파일명 변경).
- 2단계: 단기(1~2일): ESLint + Prettier 규칙 강화 및 export 방식(컴포넌트는 named export) 컨벤션 문서화.
- 3단계: 중기(1주): PR 템플릿에 '네이밍/오타 체크' 항목 추가 및 CI에서 lint 실패 시 병합 차단.
🎯 일관성 체크포인트
파일명 규칙
- src/widgets/posts-with-users/ui/Paginaition.tsx (오타: Pagination)
- 일부 훅 파일/컴포넌트 경로/이름이 완전히 일관되지 않을 수 있음
Import/Export 패턴
- Footer는 default export이고 대부분의 컴포넌트는 named export로 혼재됨(권장: named export 통일)
변수명 규칙
- src/app/providers/QueryProvider.tsx: queyrClient (오타)
- src/shared/hooks/useDebounceValue.ts: setDbouncedValue (오타)
코드 스타일
- 대부분 일관적이나 작은 오타/네이밍 이슈가 존재함. ESLint/Prettier 설정 확인 추천
11. 🗃️ 상태 관리
💡 개념 정의
상태 관리는 서버 상태(원격 데이터)와 클라이언트 상태(로컬 UI 상태)를 구분하는 것입니다. 서버 상태는 React Query 같은 도구로, 클라이언트 상태는 전역 스토어나 컴포넌트 로컬 state로 관리합니다.
⚡ 중요성
적절한 상태 분리는 데이터 일관성, 디버깅, 성능(불필요한 렌더 제한)에 직접 영향 미칩니다.
📊 현재 상황 분석
긍정적:
- 서버 상태는 React Query로 관리되어 캐시/리프레시가 중앙에서 제어됨.
개선 포인트:
- 사용중인 modal store의 string key 설계는 리팩토링/타입 변경 시 취약.
- 모달이 많은 컴포넌트에서 직접 같은 전역 스토어에 의존하여 coupling 증가.
📝 상세 피드백
서버 상태(React Query)와 클라이언트 UI 상태(Zustand)를 분리한 설계는 적절합니다. React Query는 서버 상태를 캡슐화하고 있어 리프레시/캐싱 전략을 중앙에서 관리할 수 있습니다. 반면 전역 모달 관리는 Zustand 기반의 string-key 모달 스토어로 구현되어 있는데, 이는 편리하지만 타입 안전성과 리팩토링 비용 측면에서 취약합니다. 전역 state는 인증, 테마, 모달, 토스트 등 '글로벌 UI 상태'에 한정하는 것이 좋습니다.
❌ 현재 구조 (AS-IS)
// AS-IS: modal store (zustand)와 useModal 훅
// src/shared/store/modal.ts -> Zustand store
// src/shared/hooks/useModal.ts -> useModal(modalId) 반환: { open, close, isOpen, data }
// 사용 예: AddCommentButton, PostDetailButton 등에서 useModal('add-comment-<id>') 사용✅ 권장 구조 (TO-BE)
// TO-BE: typed modal hooks 또는 context를 통한 의존성 주입
// export const useAddCommentModal = () => useTypedModal<'addComment', AddCommentData>('addComment')
// 또는 모달 관리자 컴포넌트를 통해 open 함수 주입: <ModalProvider>{children}</ModalProvider>🔄 변경 시나리오별 영향도
- 실시간 동기화(웹소켓) 도입: React Query와의 통합 전략(서버 푸시 반영)을 미리 설계해 두면 변경이 적음.
- 오프라인 모드: React Query의 캐시 + 로컬저장 전략을 사용하면 비교적 수월하지만 modal 전역 구현은 별도 동기화 필요 없음.
- 상태관리 라이브러리 변경: useModal 추상화가 없으면 사용된 모든 장소를 수정해야 함(큰 비용).
🚀 개선 단계
- 1단계: 즉시(반나절): modalId를 enum 또는 union 타입으로 타입화하여 실수를 줄임.
- 2단계: 단기(1~2일): 핵심 모달에 대해 typed hook 제공(useAddPostModal, useUpdateCommentModal 등)으로 전환.
- 3단계: 중기(1주): modal 시스템 추상화 레이어를 만들어 상태관리 라이브러리 교체 비용을 줄임.
🤔 질문과 답변
Q1: 전역상태 라이브러리를 어느 부분에서 사용하면 좋을까요?
A1: 전역 상태는 '글로벌 UI 상태'에 한해서만 사용하는 것을 권장합니다. 예: 모달 오픈/클로즈, 전역 토스트/알림, 현재 인증 정보(토큰), 테마 설정 등. 서버 상태(데이터 목록, 상세 데이터 등)는 React Query에 맡기고 전역상태에서는 최소한의 UI 상태만 관리하세요. 구체적으로 이 PR에서는 모달 관리(useModal/useModalStore)와 전역 UI 설정(예: 사이드바 open/close, 테마)이 전역 상태 사용 대상입니다. 반면 포스트 리스트의 필터/쿼리 상태는 URL + React Query로 관리하여 서버 상태와의 일관성을 유지하는 것이 좋습니다.
Q2: 쿼리스트링을 관리하는 커스텀 훅(usePostsQuery)의 위치와 결합도에 대한 테스트/검토
A2: 현재 구현(usePostsQuery in feature/post-query/model/usePostsQuery.ts)은 'posts' 도메인에 특화된 로직(직렬화/역직렬화 포함)이므로 feature/post-query에 위치시키는 것이 합리적입니다. 장점: 도메인 지식을 close-to-code로 유지하여 응집도 증가. 단점: 이 훅을 여러 슬라이스가 공유하려면 shared로 옮겨야 하는데, shared에 두려면 도메인 의존(PostQueryParams 등)을 제거하거나 generic하게 만들어야 합니다. 권장 방식:
- 직렬화/역직렬화 함수(serializePostQuery/parsePostQuery)는 entities 또는 shared/url에 두어 재사용성을 높입니다.
- usePostsQuery는 feature/post-query 레이어에 두고, 내부에서 serialize/parse 유틸을 호출하도록 구성합니다(결합도 낮춤).
- 디바운스(현재 useDebounceValue) 사용은 적절합니다(검색 입력에서 URL 업데이트 시 네트워크/히스토리 오염 방지). Enter로 즉시 검색하는 UX도 유지하는 현재 설계(디바운스 + Enter 트리거)는 합리적입니다. 다만 updateQuery가 URL을 직접 조작하므로 다른 컴포넌트가 URL을 읽어 동작할 때 의존성이 강해집니다. 이 경우는 문서화(훅의 계약)와 typed 이벤트로 보완하세요.
추가 권장: query-string serialization/ deserialization과 queryKey 정책을 분리하여 훅은 'input ↔ parsed object'의 책임만 지고, 네트워크 호출 로직은 entities.postApi를 통해 수행하세요.
🎯 셀프 회고 & 제안
작성하신 회고는 매우 성찰적이고 현실적입니다. 특히 '데이터(타입/모델) 중심으로 리팩토링을 시작한 점'과 '코로케이션을 통해 응집도를 높이려는 시도'는 좋은 접근입니다. 몇 가지 더 생각해볼 질문을 남깁니다:
- Feature와 Widget의 경계에 대한 실제 규칙을 문서화해보셨나요? (예: 'widget은 2개 이상 도메인을 조합할 때만 사용'과 같은 팀 컨벤션)
- entities에 데이터를 넣을지 feature에 넣을지 혼란이 생길 때 체크리스트(데이터 순수성 / 비즈니스 규칙 포함도 / 재사용 가능성)를 만들어 적용해보는 건 어떨까요?
- 모달 및 전역 상태 설계에서 '타입 안전성'을 우선시하면 장기적으로 리팩토링이 쉬워집니다. 현재의 string-ID 전략을 typed hook으로 점진적 전환해보세요.
추가로 더 깊이 사고해볼 거리:
- '응집도를 높이되 결합도를 낮추는' 실제 trade-off를 코드로 사례화(예: 동일한 모달을 widget으로 유지 vs feature로 co-locate)해서 장단점을 팀에 공유해보세요.
- FSD를 팀 컨벤션으로 정착시키려면 작은 규칙(파일 위치, api 위치, 모달 식별 규칙)을 PR 템플릿으로 강제하면 학습 곡선이 낮아집니다.
추가 논의가 필요한 부분이 있다면 언제든 코멘트로 남겨주세요!
코드 리뷰를 통해 더 나은 아키텍처로 발전해 나가는 과정이 즐거웠습니다. 🚀
이 피드백이 도움이 되었다면 👍 를 눌러주세요!
과제 체크포인트
기본과제
목표 : 전역상태관리를 이용한 적절한 분리와 계층에 대한 이해를 통한 FSD 폴더 구조 적용하기
체크포인트
심화과제
목표: 서버상태관리 도구인 TanstackQuery를 이용하여 비동기코드를 선언적인 함수형 프로그래밍으로 작성하기
체크포인트
최종과제
과제 셀프회고
예전에 혼자서 개인프로젝트에서 fsd를 적용해본적이 있었습니다.
이 과제를 하고나니 깨달은건, 그땐 어떤 기준으로 했었는지 궁금해지더라구요. fsd가 지향하고자하는 바를 반영하지 못했던 것 같아요.
저는 더티코드나 레거시코드를 리팩토링할 때에는 뷰를 먼저 분리하기보단, 데이터나 상태를 중심적으로보고 리팩토링을 하는것을 선호하는 것 같습니다.
이번 과제에서는 사용되는 타입들을 분석하고 entities/model/types 을 채우는 것으로 시작했 던 것 같습니다.
아래의 순서로 리팩토링했습니다.
컴포넌트 분리는 데이터에 비해서 항상 어려운 것 같습니다.
그런데 feature/widget 에서 불러오는 도메인 함수를 비교하면서 분리 및 파일링을 하려고하니, 좀 더 편하고 쉬웠습니다.
계층간 역전되지 않게 하며 동일한 도메인으로 넣어준다는 관점으로 보니 파일링이 쏙쏙 들어갔습니다.
물론 shared에 들어가는 부분은 아직도 고민이 많지만요.
이번 과제를 통해 이전에 비해 새롭게 알게 된 점이 있다면 적어주세요.
5주차 과제에서 도메인중심으로 설계하였지만, 커스텀 훅 뿐만 아니라 모든 로직을 분리되지 않은 코드를 작성했습니다.
어떻게 나눠야할지 고민을 많이 했었지만 해결하지 못한채로 제출했었습니다.
이번 과제를 통해서 도메인중심을 넘어서 결합도는 떨어뜨리되 응집도는 높이는 경험을 해볼 수 있었습니다.
본인이 과제를 하면서 가장 애쓰려고 노력했던 부분은 무엇인가요?
1. 명확한 계층과 슬라이스
가장 노력했던 부분은 "이 코드가 어느 레이어에 속해야 하는가?"를 일관되게 판단하는 것이었습니다.
예를 들어 댓글 추가 기능을 구현할 때, 단순히 API 호출만 하는 로직은 entities/comment/api에, 실제 사용자 액션과 비즈니스 로직은 feature/add-comment에 분리하려고 노력했습니다.
2. colocation
평큐에서 테오와 수강생 대화를 들으며, fsd 에서 레이어 위치시키는 다양한 요소중에 하나가 코로케이션이더라구요.
응집도를 올리기위한 작업으로 이해했는데, 컴포넌트를 나누면서 해당 대화가 문득 떠올랐습니다.
컴포넌트를 분리하고 조합하고, 함께 두는 작업을 하면서 기억에 남았던 컴포넌트는 모달버튼 컴포넌트였습니다.
어느정도 진도가 진행된 저의
PostsManagerPage컴포넌트에서는 전역으로 띄울 수 있는 모달 컴포넌트들이 위치해 있었습니다저는 주스탄드를 사용하여 모달을 띄우기 때문에 모달을 종류별로 하나씩 띄울 수 있게 페이지 컴포넌트에 묶어 놨었습니다.
그런데 팀원분의 페이지 컴포넌트를 확인해보니, 모달컴포넌트가 없더라구요?
팀원분의 모달컴포넌트는 모달을 트리거하는 버튼 컴포넌트와 함께 위치시켜놓음으로써 응집도를 높였었습니다.
물론 그러면서 결합도도 증가한다는 점도 있었습니다.
페이지 컴포넌트들 page 계층에 두고있고, 모달컴포넌트를 widget에 두고있는 상황이었습니다.
모달을 트리거하는 버튼 컴포넌트는 feature에 존재했었는데, 모달 컴포넌트를 버튼컴포넌트와 함께 두게 되면 계층도 저절로 정리가 되는 좋은 점도 있었습니다.
물론 모달 컴포넌트 내부에서 두 개이상의 도메인을 사용하고 있다면 widget에 유지시켰겠지만, 그렇지 않은 경우에는 feature로 옮겨서 버튼과 함께 파일링하여 코로케이션 시켰습니다.
3. 기능 유지
테스트코드가 존재하지않고 누락된 기능이 있는 과제인 만큼, fsd를 진행하면서 기능도 제대로 동작시키고 싶었습니다.
4,5주차 리팩토링을 진행해 보면서 테스트 코드 힘을 체감할 수 있었고, 이번 주차에는 제공된 테스트코드가 없어서 직접 테스트 코드를 만들어 진행했습니다.
테스트코드에 대한 지식이 부족하며 테스트코드 작성에 시간투자할 생각은 없었기 때문에 AI를 사용하여 테스트코드를 작성한뒤에 과제를 진행했습니다.
한 파일에 통으로 되어있는 코드를 fsd로 구조를 바꾸는 리팩토링이었기 때문에, 사용자 행동(기능) 테스트에 중점을 두어 통합테스트 코드를 추가하고 진행했습니다.
생각한 거와 조금 달라서 과제 진행시에 조금씩 테스트코드를 수정했지만, 기본기능을 누락시지않고 진행할 수 있었던 점이 무척 좋았었습니다.
아직은 막연하다거나 더 고민이 필요한 부분을 적어주세요.
feature와 widget과의 경계
Features와 Widgets의 경계가 여전히 명확하지 않습니다.
예를 들어 "게시물 리스트 + 필터 + 페이지네이션"을 하나의 위젯으로 봐야 할지, 각각을 독립적인 기능으로 분리해야 할지 판단하기 어려웠습니다. FSD 가이드라인은 있지만, 실제 프로젝트의 복잡도와 팀의 컨벤션에 따라 경계가 달라질 것 같았습니다.
fsd는 하나의 폴더구조이기 때문에 사람마다 다르다는 것은 알고 있습니다.
그렇기에 자신이 생각하는 기준을 정해두고 일관성있게 분류하고 정리하는 것이 맞다는 것을, 이번 과제로 알게 되었습니다.
feature 간 다른 도메인 참조
과제 메인페이지의 postlist는 Post + User(author)의 조합 데이터를 사용하고있습니다.
수강생 분들과 모여서 postlit를 어디서, 어떻게 만들고 조합할지 한참 이야기 했었습니다.
entities 뿐만아니라 feature에서는 다른 도메인을 참조 하지 않는 것을 규칙으로 생각했기 때문에 widget에서 조합하자! 라는 결론이 많이 나왔었습니다.
하지만, 테오의 평일 QnA시간에서 feature에서의 다른 도메인 참조는 막지는 않는다. 상관이 없다. 라는 말을 들으며 제 fsd 세상이 붕괴됐습니다. 하하
이번에 배운 내용 중을 통해 앞으로 개발에 어떻게 적용해보고 싶은지 적어주세요.
지금 저희 회사는 폴더구조 뿐만아니라 컨벤션도 제대로 되어있지않습니다.
때로는 설계 구조도 잘못되어있어서 예측하는 기능대로 동작하지 않을 때도 있습니다.
이런 부분을 원인분석해서 문제가 발생하는 지점을 찾아내어도, 어떤 구조로 해결할지가 제일 고민이었습니다.
클린코드 주차를 지나며 생각해보니 두 가지의 요소의 부재였던 것 같습니다.
계층 분리과 명확하지 않았기 때문에 의도치않은 사이드 이펙트를 발생시키기 쉬웠습니다.
하나의 목적을 설계 되었으니, 다른 이유로도 수정이 일어날 수 있는 상황이었기에 단일 책임 원칙이 위배 되면서 순수한 기능으로는 동작하지 못했었습니다.
물론, 해당 설계 코드는 프로젝트 마이그레이션을 하면서 설계구조도 달라지겠지만 추후에 이와 같은 문제에 대해서 좀 더 다양한 관점으로 볼 수 있는 힘을 기르게 되었습니다.
챕터 셀프회고
클린코드: 읽기 좋고 유지보수하기 좋은 코드 만들기
해당 주차에서 과제를 진행할 때에는 클린코드로 리팩토링하겟다는 것이 아닌, 더 나은 설계구조로 변경하겠다는 잘못된 방향으로 진행했습니다.
그러다 보니, 1챕터에서 진행한 과제를 복습하며 리팩토링해보는 재미는 있었지만 과제 출제자의 의도를 파악하고 과제를 온전히 느끼고 즐기지는 못했던 것 같습니다.
더티코드를 지양하고 클린코드를 지향하는 것은 의도를 분명히 하는 것임을 깨달았습니다.
코드를 작성하는 것 보다 코드를 읽는데에 많은 시간을 소요하는 개발자 입장에서는 var와 let 로는 예측할 수 없는 위험을, 변수명과 구획화 하는 등과 같이 보다 쉬운 맥락 이해를 돕는다고 생각합니다.
코드를 더듬더듬 읽어가며 흐름을 읽는 명령형 프로그래밍 보다는, 명확한 의도가 드러나는 선언형 프로그래밍이 클린코드의 축에 더 가까운 것 같습니다.
물론 깔끔하고 명확한 것이 클린하다의 기준은 아니지만, 코드는 프로그래머의 작업물이자, 문서화, 소통방식 중 하나라고 생각하기 때문입니다.
어쩌면 자세한 순차적이 설명이 중요할 때도 있지만 흐름을 읽을때는 대체로 명확한 것이 좋다고 생각하기 때문입니다.
결합도 낮추기: 디자인 패턴, 순수함수, 컴포넌트 분리, 전역상태 관리
디자인 패턴을 사용해 볼 생각을 별로 하지 못했습니다. ㅠㅠ
그래도
데이터 > 계산 > 액션을 기준으로 잡고 진행했습니다.model > service > hook > view 의 흐름으로 만들어서 원하고자 하는 바를 이루고자 하였지만, 지엽적인 사고와 시야 때문에 도메인 중심에 응집도 뿐만 아니라 역할도 거대하게 가지고있는 수수수퍼 훅을 만들기도 했습니다.
어쩌다 보니 거대한 단일 컴포넌트를 거대한 여러개의 커스텀 훅으로 만들었던 터라, 채점하는 코치님이 당황했을지도 모를일 입니다. 하하하
훅과 컴포넌트 분리하는 시간이 부족하여 얼레벌레 진행하게 되어, 기준이 있는 것도 아니고 깔끔하지도 않고 요상한 구조가 되었기 때문입니다.
컴포넌트가 독릭적이 되어가는 과정을 깨닫지는 못했어서 무척 아쉬운 주였습니다.
도메인 주도 설계가 무엇인지 더 알게 되었고, 제 과제코드를 통해 응집도가 높으면서 결합도 까지 높아버리는 최악의 워스트 케이스도 알았던 것을 생각하면 더 많은 것들을 배우고 깨달았던 과제 였던 것 같습니다.
다시보니, 과제의도와 반대로 진행을 했었군요?
항상 과제 의도를 빨리 파악하고 시간내에 과제를 완료하는 것을 목표로 삼고있었는데 지켜진 적인 없어서 아쉬울 따름이네요. 하하하!
응집도 높이기: 서버상태관리, 폴더 구조
이번 주차가 시작되면서 과제에 대해서 스터디원 분들과 많은 이야기를 나눌 수 있었던 것이 제일 즐거웠습니다.
fsd는 결국 나의 생각과 기준이기 때문에 사람마다 다른 fsd 구조가 나올수 밖에 없더라구요.
하나의 과제를 가지고 여러 사람들과 함께 코드 위치에 대해서 고민해보고, 자신의 생각을 공유하는 시간은 값졌습니다.
같은 생각을 하고 있으면 반갑기도 하면서도 다른 생각을 하고 있으면, 어떤 이유와 관점으로 그런 방향이 도출되었는지 이야기하는 것이 재밌었습니다.
물론, 재미랑 과제 진도는 조금 달라서 과제 완성에 일정이 벅찼던게 참 아쉽지만요.
어떤 기준으로 폴더를 분리하고 코드 위치를 설정하면서, 머릿속에서는 데이터 구조를 그리기도 하고 이유와 근거에 대해서 깊게 생각했던 것이 좋았스빈다.
리뷰 받고 싶은 내용이나 궁금한 것에 대한 질문
src/feature/post-query/model/usePostsQuery.ts의 경로와 이름을 사용했습니다.src/feature/posts-filter/ui/PostsFilter.tsx가 존재하는데, 이 것과 같이 파일링하는 것이 좀 더 올바른 방향인지 궁금합니다.