토스 Frontend Fundamentals 모의고사 2회차 후기 — "우리가 이걸 알아야 할까?"
토스 Frontend Fundamentals 모의고사 2회차 후기 — "우리가 이걸 알아야 할까?"
들어가며
운이 좋게도 토스의 프론트엔드 모의고사 2회차에 참여하게 되었다. 이전에 참여했던 토스 액셀러레이터에서 배운 것들을 실제로 써보며 리팩터링 과제를 진행했는데, 주제는 "회의실 예약" — 처음부터 만드는 게 아니라 이미 있는 코드를 리팩터링하는 작업이었다.
코드를 열기 전에 리팩터링의 기준을 먼저 잡았다.
- 관련된 코드들을 최대한 응집시키자.
- 분리를 하였을 때, 분리된 코드가 보이지 않게 되는 트레이드오프를 생각하고 신중히 분리하자.
- 나의 의도는 설명이 가능해야 한다.
내가 리팩터링한 것들
"우리가 이걸 알아야 할까?"로 시작하기
과제를 받고 가장 먼저 한 건 UI를 보면서 README에 사고 과정을 적는 것이었다. 코드를 고치기 전에, 화면을 보고 "이 컴포넌트가 알아야 할 것"과 "몰라도 되는 것"의 경계를 먼저 그렸다. (중간에 귀찮아서 사고 과정 적는 것을 멈췄는데 다하고 후회했다..항상 적거나 남기는게 더 좋은 것 같다)
예를 들어, 타임라인의 시간 헤더. 원본 코드에는 HOUR_LABELS를 만드는 로직이 인라인으로 들어가 있었다. 그런데 타임라인을 쓰는 쪽에서 시간 라벨이 어떻게 생성되는지 알아야 할까? 시작 시간과 끝 시간만 알면 충분하지 않나?
회의실 이름도 마찬가지였다. rooms를 map 돌리면서 CSS와 HTML이 섞여 있었는데, 읽는 사람 입장에서 궁금한 건 "룸 이름이 어디에 노출되는지"뿐이다. CSS는 세부사항이니 감추고, 이름만 보이도록 했다.
그리고 이 TimeLineHeader, TimeLineRoom, TimeLineTrack은 같은 파일(ReservationTimeline.tsx) 안에 뒀다. 별도 파일로 나눌 수도 있었지만, 이것들은 타임라인 안에서만 의미를 가지는 구성 요소다. 파일을 열었을 때 타임라인의 전체 구조가 한눈에 보이는 게 더 낫다고 판단했다. 기준 1번(응집)과 2번(분리의 트레이드오프)이 만나는 지점이었다.
필터 로직 — 한국어 함수명을 쓴 이유
회의실 필터링 로직은 원본에서 filter 안에 조건들이 인라인으로 들어가 있었다. 이걸 유틸로 빼면서 함수 이름을 한국어로 지었다.
영어로 hasEnoughCapacity, meetsEquipmentRequirements라고 쓸 수도 있었다. 그런데 이 코드를 읽을 사람은 한국어를 쓰는 팀원이고, 이 필터 조건들은 비즈니스 도메인 용어다. 수용가능이라고 쓰면 기획 문서의 용어와 1:1로 대응되니까, 코드를 읽을 때 "이 조건이 기획에서 말한 그 조건이구나"가 바로 매칭된다. 이게 기준 3번(의도가 설명 가능해야 한다)에 해당하는 판단이었다.
AvailableRoomList — useSuspenseQueries로 데이터와 UI를 붙이기
원본에서는 rooms와 reservations를 페이지 레벨에서 가져와서 내려줬다. 리팩터링하면서 AvailableRoomList 컴포넌트가 직접 useSuspenseQueries로 데이터를 가져오도록 바꿨다.
이 컴포넌트는 "필터 조건에 맞는 회의실을 보여주는 것"이 핵심 관심사다. 그렇다면 그 데이터를 직접 가져오는 게 자연스럽다. 바깥에서 props로 rooms, reservations를 내려주면 페이지가 이 데이터의 존재를 알아야 하는데, 페이지 입장에서는 "필터에 맞는 회의실 목록이 여기 있다" 정도만 알면 충분하다.
대신 Suspense로 감싸서 로딩 상태는 바깥에서 제어하도록 했다. Watch 컴포넌트로 필터가 완성됐는지 확인하고, 완성되었을 때만 AvailableRoomList가 마운트되면서 데이터를 가져온다.
UI 컴포넌트 분리 — 숨기는 게 아니라 드러내기
Section, Banner, EmptyRoom, Tooltip, DatePicker, ChipGroup, NumberStepper — 꽤 많은 UI 컴포넌트를 분리했다. 기준은 간단했다: CSS가 세부사항이고, 인터페이스(props)가 본질인 경우.
DatePicker를 예로 들면:
이 컴포넌트가 궁금한 건 selectedDate, minDate, onChange — 이 세 가지뿐이다. 내부에서 input에 어떤 스타일이 적용되는지, border-radius가 몇 px인지는 알 필요가 없다. 이런 경우 분리하면 "보이지 않게 되는 것"이 CSS뿐이기 때문에, 분리의 트레이드오프가 명확하게 이점 쪽으로 기운다.
반면 ReservationTimeline 내부의 TimeLineHeader, TimeLineRoom, TimeLineTrack은 위에서 말했듯이 같은 파일에 뒀다. 이것들은 "타임라인의 구성 요소"라는 맥락 안에서만 의미가 있어서이다.
뇌의 RAM은 5~7청크
해설에서 가장 먼저 꽂힌 말:
인간의 메모리는 생각보다 많지 않다. 그렇기에 기억할 필요 없는 코드를 만들어야 한다.
그리고 이어진 설명 — HTML 500줄은 읽을 수 있다. 머릿속에 그림이 그려지니까. 반면 JS 100줄은 어려울 수 있다. 로직은 그림이 안 그려지기 때문에, 뇌가 직접 실행 흐름을 시뮬레이션해야 한다.
잘 읽히는 건 코드의 길이가 아니다.
파일이 길어도, HTML 구조가 눈에 그려지면 읽을 수 있다. 반대로 짧아도 "이 hook이 뭘 하지?" 하고 파일을 열어봐야 하는 순간 뇌의 RAM을 쓰게 된다.
Props가 호출자의 관심사를 가지는 순간
해설에서 다뤘던 setDate={setDate} 패턴:
함수가 자신이 관심을 가지고 있는 요소를 받는 순간, 재사용성이 현저히 떨어진다.
내가 DatePicker를 만들 때 selectedDate와 onChange로 인터페이스를 잡은 이유가 여기에 있었다. 만약 setDate를 직접 받으면, "부모에 date라는 state가 있고 그걸 set한다"는 호출자의 맥락이 그대로 노출된다. 콘솔 로그를 찍거나 API를 호출해야 하는 상황이 오면 setDate라는 이름은 이미 맞지 않게 된다. 내 코드에서는 이 부분을 잘 지킨 것 같아서 다행이었다.
인터페이스를 먼저 짠다 — 연민이 생기기 전에
이 부분이 이번 해설에서 가장 오래 곱씹고 있는 포인트다.
기존 코드를 먼저 읽으면, 그 코드의 구조에 끌려가게 된다. "이 state가 여기 있으니 이걸 기반으로 고치자"가 되어버린다. 해설에서는 요구사항과 UI만 보고 이상적인 인터페이스를 먼저 정의한 다음, 기존 코드와의 gap을 좁혀나가는 방식을 제안했다.
하지만 나는 인터페이스를 먼저 짠다면 분리된 코드가 먼저 만들어지며 쓸모없는 분리를 판단하지 못할 것 같다는 두려움이 생긴다. 사용은 해보겠지만 나이겐 아직 조심할 필요가 느껴진다.
정답은 없다, 근거가 있을 뿐
해설 강의 마지막에 나온 말:
"코드를 작성한 사람으로서 근거를 제출할 수 있으면 된다." "정답이 아니니까. 주장이 중요하다. 정답이라고 믿고 따라가면 안 된다."
항상 내 코드를 설명하기 위한 생각의 정리와 연습이 필요하다 느낀다.
마무리
이번 모의고사를 한 문장으로 요약하면, 내가 감각적으로 하고 있던 것들에 언어가 붙었다는 것이다.
README에 "우리가 이걸 알아야 할까?"라고 적으면서 인터페이스를 먼저 생각했던 것이, 해설에서는 "연민이 생기기 전에 인터페이스를 먼저 짜라"로 정리됐다. 타임라인 서브 컴포넌트를 한 파일에 둔 것이, "뇌의 RAM이 5~7청크"라는 설명으로 근거가 붙었다. selectedDate/onChange로 props를 잡은 것이, "호출자의 관심사를 받지 마라"라는 원칙과 연결됐다.
이렇게 자기가 하던 것에 이름이 붙는 경험이 이번 모의고사에서 가장 값졌다. 3회차도 기회가 된다면 꼭 참여하고 싶다.

