배포링크 : https://amelia-shin.github.io/front_6th_chapter2-3
과제 체크포인트
기본과제
목표 : 전역상태관리를 이용한 적절한 분리와 계층에 대한 이해를 통한 FSD 폴더 구조 적용하기
- 전역상태관리를 사용해서 상태를 분리하고 관리하는 방법에 대한 이해
- Context API, Jotai, Zustand 등 상태관리 라이브러리 사용하기
- FSD(Feature-Sliced Design)에 대한 이해
- FSD를 통한 관심사의 분리에 대한 이해
- 단일책임과 역할이란 무엇인가?
- 관심사를 하나만 가지고 있는가?
- 어디에 무엇을 넣어야 하는가?
체크포인트
- 전역상태관리를 사용해서 상태를 분리하고 관리했나요?
- Props Drilling을 최소화했나요?
- shared 공통 컴포넌트를 분리했나요?
- shared 공통 로직을 분리했나요?
- entities를 중심으로 type을 정의하고 model을 분리했나요?
- entities를 중심으로 ui를 분리했나요?
- entities를 중심으로 api를 분리했나요?
- feature를 중심으로 사용자행동(이벤트 처리)를 분리했나요?
- feature를 중심으로 ui를 분리했나요?
- feature를 중심으로 api를 분리했나요?
- widget을 중심으로 데이터를 재사용가능한 형태로 분리했나요?
심화과제
목표: 서버상태관리 도구인 TanstackQuery를 이용하여 비동기코드를 선언적인 함수형 프로그래밍으로 작성하기
- TanstackQuery의 사용법에 대한 이해
- TanstackQuery를 이용한 비동기 코드 작성에 대한 이해
- 비동기 코드를 선언적인 함수형 프로그래밍으로 작성하는 방법에 대한 이해
체크포인트
- 모든 API 호출이 TanStack Query의 useQuery와 useMutation으로 대체되었는가?
- 쿼리 키가 적절히 설정되었는가?
- fetch와 useState가 아닌 선언적인 함수형 프로그래밍이 적절히 적용되었는가?
- 캐싱과 리프레시 전략이 올바르게 구현되었는가?
- 낙관적인 업데이트가 적용되었는가?
- 에러 핸들링이 적절히 구현되었는가?
- 서버 상태와 클라이언트 상태가 명확히 분리되었는가?
- 코드가 간결하고 유지보수가 용이한 구조로 작성되었는가?
- TanStack Query의 Devtools가 정상적으로 작동하는가?
최종과제
- 폴더구조와 나의 멘탈모데일이 일치하나요?
- 다른 사람이 봐도 이해하기 쉬운 구조인가요?
과제 셀프회고
처음 FSD 폴더 구조를 봤을 때는 정말 복잡하고 어렵게 느껴졌습니다.
entities, features, widgets 같은 생소한 용어들이 많았고, 파일을 여러 폴더에 나누는 방식이 비효율적으로 보이기도 했습니다.
하지만 하나씩 이해해 나가면서 생각이 바뀌었습니다. 복잡해 보이는 구조가 오히려 코드를 더 쉽게 관리할 수 있게 도와준다는 것을 알게 되었습니다. 예를 들어, 댓글 기능을 수정할 때 예전에는 여러 파일을 뒤져야 했지만, 이제는 features/comment 폴더만 보면 됩니다. 처음에는 폴더가 많아 헷갈렸지만, 실제로는 기능을 예측하고 빠르게 찾을 수 있도록 도와주는 구조였던 거죠.
과제 초반에는 entity, feature, widget의 개념을 명확히 알지 못해 어려움이 있었습니다. 발제 당일 팀 회의 때도 제대로 설명하지 못했었습니다.
그래서 과제를 시작하기 전, 다른 사람들이 FSD 구조를 어떻게 나눴는지 찾아보고 팀원들에게 왜 그렇게 분리했는지 많이 물어봤습니다.
대화를 나누면서 개념을 조금씩 이해하게 되었고, “엔티티는 정보, 피처는 행동”이라는 정의가 기억에 남았습니다. (휘린님의 “엔티티는 장보고, 피처는 맥주”라는 비유도 재밌어서 인상 깊었습니다. 😊)
이번 과제는 막막하게 시작했지만, 팀원들과 적극적으로 소통하면서 많은 도움을 받았습니다. 아직 부족한 부분이 많지만, 매주 사람들과 이야기하며 배우는 과정이 재미있고 값진 경험이라고 느꼈습니다. 앞으로는 저도 의견을 나누고 누군가에게 도움을 줄 수 있는 사람이 되고 싶습니다.
이번 과제를 통해 이전에 비해 새롭게 알게 된 점이 있다면 적어주세요.
FSD
FSD를 알기 전에 컴포넌트, 훅, API 등등 각 폴더를 지정하여 UI (=widget) 에 합치면 된다고 생각
- 계층별 역할 분담
src/
├── entities/ # 핵심 데이터 (사용자, 게시물, 댓글)
├── features/ # 사용자 행동 (추가, 수정, 삭제)
├── widgets/ # 재사용 가능한 큰 컴포넌트
└── shared/ # 공통으로 쓰는 것들
-
각 폴더의 정확한 역할 entities: 데이터의 "무엇"을 정의 (타입, API 호출) features: 사용자가 "어떻게" 하는지 정의 (이벤트 처리) widgets: 화면에 "어떻게 보여줄지" 정의 (UI 조합) shared: "여러 곳에서 공통으로" 쓰는 것들
-
실제 코드에서의 적용
// entities/user/model/types.ts - 사용자 데이터 구조 정의
export interface User {
id: number
username: string
}
// features/comment/add-comments/hooks.ts - 댓글 추가 기능
export const useAddComment = () => { /* 댓글 추가 로직 */ }
// widgets/comment-list/CommentList.tsx - 댓글 목록 화면
export const CommentList = () => { /* 댓글 목록 UI */ }
- 내가 생각했을 때 FSD의 장점
- 코드를 찾기 쉬워진다. (댓글 관련 코드는 features/comment에 다 있음)
- 유지보수성 향상 : 한 기능을 바꿀 때 한 곳만 보면 된다.
TanStack Query
- useQuery - 데이터 가져오기
// 기존 방식 (복잡함)
const [posts, setPosts] = useState([])
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
useEffect(() => {
setLoading(true)
fetch('/api/posts')
.then(res => res.json())
.then(data => setPosts(data))
.catch(err => setError(err))
.finally(() => setLoading(false))
}, [])
// TanStack Query 방식 (간단함)
const { data: posts, isLoading, error } = useQuery({
queryKey: ['posts'],
queryFn: () => fetch('/api/posts').then(res => res.json())
})
- useMutation - 데이터 변경하기
// 이전 방식 (복잡함)
const addPost = async (postData) => {
try {
setLoading(true)
const response = await fetch('/api/posts', {
method: 'POST',
body: JSON.stringify(postData)
})
const newPost = await response.json()
setPosts([...posts, newPost])
} catch (error) {
setError(error)
} finally {
setLoading(false)
}
}
// TanStack Query 방식 (간단함)
const addPostMutation = useMutation({
mutationFn: (postData) => fetch('/api/posts', {
method: 'POST',
body: JSON.stringify(postData)
}).then(res => res.json())
})
// 사용할 때
addPostMutation.mutate(postData)
- 쿼리 키의 마법
// 쿼리 키로 데이터를 구분하고 관리
['posts'] // 모든 게시물
['posts', 1] // ID가 1인 게시물
['comments', 5] // 게시물 5번의 댓글들
['user', 123] // ID가 123인 사용자
// 이 키들로 캐시 관리, 자동 갱신, 데이터 무효화 등을 자동으로 처리
- 자동 캐싱과 동기화 캐싱: 한 번 가져온 데이터를 자동으로 저장 동기화: 같은 데이터를 여러 곳에서 사용할 때 자동으로 동기화 백그라운드 업데이트: 사용자가 모르게 최신 데이터로 갱신
낙관적 업데이트
- 낙관적 업데이트란? 낙관적: "아마 성공할 거야"라고 믿고 미리 화면을 바꿈 즉시성: 서버 응답을 기다리지 않고 바로 화면 업데이트
기존: 제출 → 로딩 화면 → 성공/실패 메시지 변경: 제출 → 즉시 화면에 댓글 표시 → 백그라운드에서 저장
queryClient.setQueryData: 캐시를 직접 조작하는 방법
본인이 과제를 하면서 가장 애쓰려고 노력했던 부분은 무엇인가요?
- Zustand를 활용한 다이얼로그 상태 관리 다이얼로그 상태를 전역으로 관리하는 시스템을 구축했습니다.
// dialogStore.ts - 6개 다이얼로그의 상태 관리
interface DialogState {
showAddDialog: boolean
showEditDialog: boolean
showAddCommentDialog: boolean
showEditCommentDialog: boolean
showPostDetailDialog: boolean
showUserModal: boolean
// 각각의 열기/닫기 함수
openAddDialog: () => void
closeAddDialog: () => void
// ... 기타 함수들
}
처음에는 각 컴포넌트마다 개별적으로 다이얼로그 상태를 관리했습니다. 그런데 여러 다이얼로그가 동시에 열릴 수 있고, 상태가 꼬이는 문제가 발생했습니다.
// 이전 방식 - 각 컴포넌트마다 개별 상태 관리
const [showAddDialog, setShowAddDialog] = useState(false)
const [showEditDialog, setShowEditDialog] = useState(false)
const [showCommentDialog, setShowCommentDialog] = useState(false)
// 문제: 다이얼로그 간 상태가 독립적으로 관리되어 동기화 문제 발생
구체적인 문제점들
- 동시 열림 문제: 게시물 추가와 댓글 추가 다이얼로그가 동시에 열림
- 상태 불일치: 다이얼로그를 닫았는데 다른 곳에서 여전히 열려있다고 인식
- 코드 중복: 각 다이얼로그마다 비슷한 열기/닫기 로직 반복
해결 과정
-
Zustand store로 모든 다이얼로그 상태 통합 모든 다이얼로그 상태를 하나의 store에서 관리하는 구조를 만들었습니다.
-
일관된 패턴으로 함수 구현
export const useDialogStore = create<DialogState>((set) => ({
// 초기 상태 - 모든 다이얼로그는 닫힌 상태
showAddDialog: false,
showEditDialog: false,
showAddCommentDialog: false,
showEditCommentDialog: false,
showPostDetailDialog: false,
showUserModal: false,
// 열기 함수들 - 모두 동일한 패턴
openAddDialog: () => set({ showAddDialog: true }),
openEditDialog: () => set({ showEditDialog: true }),
openAddCommentDialog: () => set({ showAddCommentDialog: true }),
openEditCommentDialog: () => set({ showEditCommentDialog: true }),
openPostDetailDialog: () => set({ showPostDetailDialog: true }),
openUserModal: () => set({ showUserModal: true }),
// 닫기 함수들 - 모두 동일한 패턴
closeAddDialog: () => set({ showAddDialog: false }),
closeEditDialog: () => set({ showEditDialog: false }),
closeAddCommentDialog: () => set({ showAddCommentDialog: false }),
closeEditCommentDialog: () => set({ showEditCommentDialog: false }),
closePostDetailDialog: () => set({ showPostDetailDialog: false }),
closeUserModal: () => set({ showUserModal: false }),
// 핵심 해결책: 모든 다이얼로그를 한 번에 닫기
closeAllDialogs: () => set({
showAddDialog: false,
showEditDialog: false,
showAddCommentDialog: false,
showEditCommentDialog: false,
showPostDetailDialog: false,
showUserModal: false,
}),
}))
// src/pages/PostsManagerPage.tsx
const {
showAddDialog,
showEditDialog,
showAddCommentDialog,
showEditCommentDialog,
showPostDetailDialog,
showUserModal,
openAddDialog,
closeAddDialog,
openEditDialog,
closeEditDialog,
openAddCommentDialog,
closeAddCommentDialog,
openEditCommentDialog,
closeEditCommentDialog,
openPostDetailDialog,
closePostDetailDialog,
openUserModal,
closeUserModal,
} = useDialogStore()
// 모든 다이얼로그에서 동일한 패턴 사용
<Dialog
open={showAddDialog}
onOpenChange={(open) => open ? openAddDialog() : closeAddDialog()}
>
<DialogContent>
<DialogHeader>
<DialogTitle>게시물 추가</DialogTitle>
</DialogHeader>
<AddPostForm />
</DialogContent>
</Dialog>
- 컴포넌트에서 체계적으로 사용
// src/pages/PostsManagerPage.tsx
const {
showAddDialog,
showEditDialog,
showAddCommentDialog,
showEditCommentDialog,
showPostDetailDialog,
showUserModal,
openAddDialog,
closeAddDialog,
openEditDialog,
closeEditDialog,
openAddCommentDialog,
closeAddCommentDialog,
openEditCommentDialog,
closeEditCommentDialog,
openPostDetailDialog,
closePostDetailDialog,
openUserModal,
closeUserModal,
} = useDialogStore()
// 모든 다이얼로그에서 동일한 패턴 사용
<Dialog
open={showAddDialog}
onOpenChange={(open) => open ? openAddDialog() : closeAddDialog()}
>
<DialogContent>
<DialogHeader>
<DialogTitle>게시물 추가</DialogTitle>
</DialogHeader>
<AddPostForm />
</DialogContent>
</Dialog>
- Props Drilling 문제 해결 시도 (하지만 완전히 해결하지 못함) 가장 어려웠던 부분 중 하나는 Props Drilling을 최소화하려고 노력했지만 완전히 해결하지 못했다는 점입니다.
어려웠던 점 :
- 상태 관리 복잡성: 댓글 관련 상태가 여러 컴포넌트에 분산
- 함수 전달 체인: 이벤트 핸들러가 3-4단계를 거쳐 전달
- 데이터 일관성: 같은 데이터가 여러 곳에서 중복 관리
- 컴포넌트 결합도: 각 컴포넌트가 상위 컴포넌트의 구조에 의존
해결해보려고 시도한 방법:
// 1. Zustand store로 다이얼로그 상태는 분리했지만...
export const useDialogStore = create<DialogState>((set) => ({
showAddCommentDialog: false,
showEditCommentDialog: false,
// ... 다이얼로그 열기/닫기 함수들
}))
// 2. 하지만 댓글 데이터와 함수들은 여전히 props로 전달해야 함
// 3. Context API 도입을 고려했지만 복잡성 증가 우려
아직은 막연하다거나 더 고민이 필요한 부분을 적어주세요.
- Context API vs Zustand: 어떤 상태를 어디서 관리할지
- 컴포넌트 분리 전략: 어느 수준까지 분리해야 하는지
- 상태 공유 범위: 전역 상태와 지역 상태의 경계 설정
// 옵션 1: 댓글 관련 상태를 Zustand store로 이동
interface CommentStore {
selectedComment: Comment | null
newComment: CommentForm
setSelectedComment: (comment: Comment | null) => void
setNewComment: (comment: CommentForm) => void
}
// 옵션 2: Context API로 댓글 관련 상태 관리
const CommentContext = createContext<CommentContextType>()
// 옵션 3: 컴포넌트 합성(Composition) 패턴 사용
<CommentProvider>
<CommentAddForm />
<CommentEditForm />
</CommentProvider>
이번에 배운 내용 중을 통해 앞으로 개발에 어떻게 적용해보고 싶은지 적어주세요.
- 유지보수하기 쉬운 코드 구조를 만들어가고 싶습니다!
낙관적 업데이트: 모든 사용자 액션에 즉시 피드백 제공
체계적 상태 관리: Zustand로 UI 상태, TanStack Query로 서버 상태 분리
일관된 코드 패턴: FSD 구조와 커스텀 훅으로 재사용 가능한 코드
챕터 셀프회고
클린코드와 아키테쳑 챕터 함께 하느라 고생 많으셨습니다! 지난 3주간의 여정을 돌이켜 볼 수 있도록 준비해보았습니다. 아래에 적힌 질문들은 추억(?)을 회상할 수 있도록 도와주려고 만든 질문이며, 꼭 질문에 대한 대답이 아니어도 좋으니 내가 느꼈던 인사이트들을 자유롭게 적어주세요.
클린코드: 읽기 좋고 유지보수하기 좋은 코드 만들기
- 더티코드를 접했을 때 어떤 기분이었나요? ^^; 클린코드의 중요성, 읽기 좋은 코드란 무엇인지, 유지보수하기 쉬운 코드란 무엇인지에 대한 생각을 공유해주세요
4주차때 받은 더티코드 잊지 못할거 같습니다. ^^... 클린코드의 원칙은 실무에 있을 때도 많이 보았던 내용들이었습니다. 하지만 이게 실제로 잘 지켜지고 있는지 스스로 많이 생각했습니다. 관련해서 테오와 멘토링할 때, 위 내용에 대해서 이야기 했었는데 클린코드란 정답이 없고, 나 스스로가 아 잘짰다! 깔끔하다! 읽기 쉽다! 하고 만족하는게 바로 클린코드라고 생각합니다. 물론 자신감이 많이 붙기 위해서 다른 사람들의 코드를 많이 보고 제 기준에서 괜찮다고 생각한 부분들만 배우는게 중요한 것 같습니다.
결합도 낮추기: 디자인 패턴, 순수함수, 컴포넌트 분리, 전역상태 관리
- 거대한 단일 컴포넌트를 봤을때의 느낌! 처음엔 막막했던 상태관리, 디자인 패턴이라는 말이 어렵게만 느껴졌던 시절, 순수함수로 분리하면서 "아하!"했던 순간, 컴포넌트가 독립적이 되어가는 과정에서의 깨달음을 들려주세요
처음 PostsManagerPage.tsx 파일을 봤을 때 모든 기능이 한 파일에 모여있어, "이걸 어떻게 정리해야 하지?"라는 걱정이 들었습니다. 마치 "정리되지 않은 창고"를 보는 것 같았습니다.
상태관리의 막막함: "전역상태관리가 뭐야?": useState만으로도 충분한 줄 알았는데, 왜 더 복잡한 것을 써야 하지? -> useState만으로는 부족하다는 것을 체감 게시물 목록 관리에서 겪은 문제:
// PostsManagerPage.tsx에서 기존 코드
const [posts, setPosts] = useState([])
const [selectedPost, setSelectedPost] = useState(null)
const [showAddDialog, setShowAddDialog] = useState(false)
const [showEditDialog, setShowEditDialog] = useState(false)
const [showCommentDialog, setShowCommentDialog] = useState(false)
// 문제: 이 상태들이 다른 컴포넌트에서도 필요함
// PostTable에서 selectedPost가 필요하고
// CommentForm에서 showCommentDialog가 필요하고
// AddPostForm에서 showAddDialog가 필요함
결합도 vs 응집도?": 이론은 알겠는데, 실제 코드에 어떻게 적용하지?
처음에는 PostsManager에 게시물, 댓글, 사용자 관련 상태와 함수가 전부 몰려 있어서 결합도가 매우 높았습니다. 어떤 기능을 수정하려면 다른 기능 코드까지 함께 신경 써야 했고, 작은 변경도 리스크가 컸습니다.
FSD 구조를 적용하면서 기능을 폴더 단위로 분리하고, 컴포넌트도 아래처럼 나눠봤습니다:
const PostsManager = () => {
return (
<div>
<PostTable /> {/* 게시물만 담당 */}
<CommentSection /> {/* 댓글만 담당 */}
<UserInfo /> {/* 사용자만 담당 */}
</div>
)
}
이렇게 나누고 나니 각 컴포넌트가 자신의 역할에 집중하고, 독립적으로 동작하게 되었습니다. 특히 댓글 기능을 수정할 때는 CommentSection만 보면 되었고, 사용자 정보는 전혀 건드릴 필요가 없었습니다. 그 과정에서 결합도를 낮추고 응집도를 높이면 재사용성도 올라가고, 유지보수도 훨씬 쉬워진다는 걸 체감했습니다.
결국, “결합도는 낮게, 응집도는 높게”라는 말이 단순한 이론이 아니라, 실제 코드에서 큰 차이를 만드는 중요한 원칙이라는 걸 배웠습니다.
응집도 높이기: 서버상태관리, 폴더 구조
- "이 코드는 대체 어디에 둬야 하지?"라고 고민했던 시간, FSD를 적용해보면서의 느낌, 나만의 구조를 만들어가는 과정, TanStack Query로 서버 상태를 분리하면서 느낀 해방감(?)등을 공유해주세요
폴더 구조를 짤 때 다른 사람들과 이야기를 해보면 모두 각자 다른 생각을 가지고 있었습니다. 누구는 API 분리할 떄 read 관련한 것은 entity에 있어야하고, create, update, delete 은 feature에 있어야한다. 또 다른 누구는 API는 결국엔 데이터고 값을 바꾸는게 없으니 entity에 있어야한다. 등등 의견이 다양했습니다. 이야기를 들으면서 저는 후자에 대해 동의를 했고 그렇게 다른 사람들의 의견을 들어보며 저만의 구조를 만들어 나갔습니다.
TanStack Query는 처음 접해보았고 어떻게 상태관리를 할 수 있다는 거지?라는 생각을 했습니다. 기존에는 useState와 useEffect로 복잡하게 상태를 관리했는데, 이게 정말 더 간단해질 수 있을까 의심스러웠습니다. tanstack query를 사용하면서 코드가 간결해졌고 (useState, fetch 사용 X), 자동 캐싱이 정말 편리했습니다. (setState를 안해줘도됨 👍 ) 낙관적 업데이트 구현도 훨씬 쉬워졌습니다. setQueryData로 간단하게 처리할 수 있다는 점에서 큰 매력을 느끼게 되었습니다.
리뷰 받고 싶은 내용이나 궁금한 것에 대한 질문
Q1. 현재 entities, features, widgets 구조로 작은 프로젝트는 잘 동작하지만, 기능이 많아질 때 어떻게 구조를 관리해야 할지 궁금합니다. Q2. 실제 대기업/스타트업에서 FSD를 적용하는 사례가 있나요? 어떤 규모 이상의 프로젝트에서 FSD를 쓰는 게 좋나요?
과제 피드백
수고했습니다. 지난 3주간 클린코드를 비롯한 소프트웨어 공학적으로 결합도 낮추기 응집도 높이기를 위한 이론과 프론트엔드에서의 적용등을 통해서 좋은 코드와 구조에 대한 다각도의 시야가 생겼기를 기대합니다.
"복잡해 보이는 구조가 오히려 코드를 더 쉽게 관리할 수 있게 도와준다는 것을 알게 되었습니다." “엔티티는 정보, 피처는 행동”, “엔티티는 장보고, 피처는 맥주”
좋네요! 복잡해 보이는 구조지만 구조가 복잡한게 아니라 원래 복잡한것들을 구분을 하다보니 복잡해지는 것이지요. 구조가 복잡해 보인다고 그대로 냅두는건 정답이 아니란걸 우리는 배웠잖아요!. 그 구조가 어째서 코드를 더 잘 관리하게 해주는지를 선명하게 느껴가는 시간이 되었기를 바래요. 수고하셨습니다!
Q1. 현재 entities, features, widgets 구조로 작은 프로젝트는 잘 동작하지만, 기능이 많아질 때 어떻게 구조를 관리해야 할지 궁금합니다.
=> 기능이 많아지고 복잡해지더라도 프론트엔드 라는 직군의 특성상 데이터, 화면, 행동이라는 3가지의 관점을 중심으로 하는 분류는 여전히 도움이 됩니다. FSD가 디자인 패턴과 같다고도 한 이유는 결국 사람들의 생각들을 구조화시키려다 보니 사실은 다들 유사한 관점들을 가지고 있는 것이니까요.
=> 기능이 많아지더라도 접근은 동일합니다. 엔티티를 중심으로 데이터의 흐름을 만들고 화면을 계층별로 분리하고, 기능단위로 묶어서 관리한다. 다만 코드를 분리한다고 해도 여전히 조립이 필요하고 애매한 그레이 영역은 존재하기 마련입니다. 그것을 잘 풀어 나가는게 개발자의 역할이겠죠!
Q2. 실제 대기업/스타트업에서 FSD를 적용하는 사례가 있나요? 어떤 규모 이상의 프로젝트에서 FSD를 쓰는 게 좋나요?
=> FSD가 주목받은 몇 가지 요소 중 하나는 폴더구조라는 측면에서 표준화나 문서화를 시도한 방법이라는 점입니다. components, service, hooks, types 라는 전통적인 스타터킷은 프로젝트트 규모가 커지만 반드시 복잡성을 띄는 문제를 가져왔어요. 그렇지만 뾰족하게 이렇게 하면 좋겠다라는 표준같은건 없었어요. 저마다 생각이 다르고 폴더구조가 달랐죠. 폴더 구조에 대한 논의나 고민은 그전부터도 항상 만나면 하는 이야기중 하나였어요.
=> FSD는 모든 프로젝트에 맞는 옷은 아니에요. 과제를 하면서도 느꼈지만 이건 여기에 둬야겠다 하고 누구나 공감하지는 못합니다. 그러다보니 시행착오가 있기 마련이고 그 시행착오들이 FSD에 대한 거부감을 느끼죠. FSD의 의의는 그 구조의 완결함보다는 서로 합의 해볼 수 있는 표준과 같은 역할을 해줄 수 있는게 있기에 누군가가 미리 배워올 수 있다는 점입니다.
=> 대부분의 경우 FSD를 표준으로 하기 보다는 일부 마음에 드는 폴더나 방식등을 적당히 차용하고 있습니다. 과제를 하면서 FSD자체가 아니라 그걸 고민해보면서 계층과 모듈이라는 관점을 고민하면서 느낀 시야를 바탕으로 실제 프로젝트에 도움이 되는 구조를 한번 만들어 보세요. 그걸 잘 모르겠다면 FSD를 써보는건 어떨까요? 작은 규모에서 써보면서 오버같은 부분들을 발견하고 큰 프로젝트에서도 도움이 되거나 그렇지 않은 부분들을 직접 발견해보길 바래요!