Q1-1
시작점이 `SearchPage.tsx` 185줄에 `CountBottomSheet`/`DateBottomSheet`까지 다 인라인이었는데, 어떤 기준으로 쪼갰나요?
핵심 포인트
- 시작점이 그 상태였다는 건 분해 능력 자체를 평가한다는 뜻으로 받아들였음
- 단일 책임 + 화면 단위 의미가 분해 기준 — "검색 조건의 어떤 한 필드를 책임지는가"로 잘랐음
- 페이지(
SearchPage)는 조립만, 데이터·이벤트는 자식이 자기 atom·query를 직접 읽음 - 재사용 가능성은 부차적 기준. 6시간 안에 "있을 법한 재사용"을 추상화하지 않음 (PDF: "화려한 방법보다 익숙한 방법")
- 결과:
SearchPage.tsx185줄 → 43줄 + 5개의 feature 컴포넌트(StationSection,TripTypeTab,DateSection,DateField,CountField+ 두 BottomSheet)
모범 답안먼저 답해보고 펼치기
시작점을 처음 봤을 때 두 가지 시그널을 읽었습니다. 하나는 SearchPage.tsx 안에 CountBottomSheet와 DateBottomSheet가 통째로 인라인으로 들어가 있고 모든 onClick이 () => {} 스텁이라는 점, 다른 하나는 package.json에 jotai나 react-query가 없어서 상태 라이브러리를 직접 골라야 한다는 점이었습니다. 토스가 마크업·스타일은 거의 다 깔아놓고 기능만 비워둔 거니까, 결국 평가 포인트는 "이 인라인 덩어리를 어떤 기준으로 쪼갤 줄 아느냐"라고 봤습니다.
분해 기준은 검색 조건의 어떤 한 필드를 책임지는가로 잡았습니다. 출발/도착역, 편도·왕복, 가는날·오는날, 인원 — 이렇게 네 필드가 있으니 각각을 StationSection, TripTypeTab, DateSection, CountField로 분리하고, 페이지는 그것들을 위에서 아래로 조립만 합니다. 그 결과 SearchPage.tsx는 43줄까지 줄었고, 페이지를 한 번 훑으면 화면 구조가 바로 읽힙니다.
분해할 때 의식적으로 피한 게 하나 있는데, "미래에 재사용될 수도 있으니까"라는 이유로 추상화하지 않는다는 거였습니다. 6시간짜리 과제고 토스가 "화려한 방법보다 평소에 익숙한 방법"을 권장한 만큼, 지금 화면 한 군데에서만 쓰이는 건 그냥 그 자리에 두자고 결정했습니다. EmptyResult처럼 station/ticket 두 곳에서 같은 빈 상태를 보여줘야 할 때만 components/common/으로 빼냈습니다.
이 답변, 어땠나요?
꼬리 질문
- Q-a. 그럼 더 잘게 쪼갤 수도 있었을 텐데, 어디서 멈춘 기준은요?
→ 하나의 컴포넌트가 자기 atom 한두 개와 자기 BottomSheet 하나만 책임지면 거기서 멈췄습니다.
CountField가passengerCountAtom하나 +CountBottomSheet하나를 다루는 정도가 적당했고, 더 잘게 쪼개면 props 드릴링이 늘어나서 오히려 가독성이 떨어진다고 봤습니다. - Q-b. 페이지가 너무 얇은 거 아닌가요? 그냥 import만 모아둔 파일 같은데요. → 의도한 부분입니다. 페이지는 라우트 경로와 화면 조립을 책임지고, "이 화면에 뭐가 있느냐"가 한눈에 보이는 게 가치라고 봤습니다. 데이터·상태·이벤트 핸들러는 그걸 실제로 쓰는 자식이 직접 끌어와서 처리하면 props 드릴링이 사라집니다.
- Q-c.
StationSection은 출발역/도착역 두 ListRow를 다 갖고 있는데, 두 개로 또 쪼갤까 고민 안 했나요? → 했습니다. 다만 출발역·도착역 사이에 화살표 아이콘이 끼는 시각적 단위가 있고, 둘이 항상 같이 등장하기 때문에 한 컴포넌트로 두는 게 응집도(cohesion) 측면에서 자연스럽다고 판단했습니다. 만약 둘 사이에 다른 UI가 끼어 들어오는 일이 생기면 그때 분리하는 게 합리적이라고 봤습니다.
CS · 이론
- Container vs Presentational: 원조는 Dan Abramov가 2015년에 정의한 패턴 — 데이터를 끌어오는 Container, 받아서 그리기만 하는 Presentational. Hooks 시대 이후 강박은 사라졌지만 "한 컴포넌트가 데이터까지 다 꺼내서 그리느냐, 받아서 그리기만 하느냐"는 여전히 분해 기준의 한 축. 본 과제에서는
StationList(query+atom)·CountField(atom+overlay) 같은 smart 컴포넌트와DateBottomSheet·StationSearchField같은 pure 컴포넌트가 공존. - Single Responsibility Principle (SRP): 한 모듈은 변경의 이유가 하나여야 한다 (Robert C. Martin). 본 과제에서 분해 기준 — "검색 조건의 어떤 한 필드"가 곧 변경의 이유.
- Co-location (Kent C. Dodds): 함께 변하는 코드는 함께 둔다.
components/{feature}/{ComponentName}/구조가 이 원칙. 추후.module.css,.test.tsx,types.ts같은 짝 파일이 자연스럽게 같은 폴더로 합류. - YAGNI (You Aren't Gonna Need It): "필요할지도 모르니까"의 추상화를 피한다. 6시간 제약 + 토스의 "익숙한 방법" 가이드라인과 정확히 정렬.