Activities2026년 3월 29일6 min read

토스 Frontend Fundamentals 모의고사 2회차 후기

회의실 예약 시스템을 리팩토링 해보기

토스 Frontend Fundamentals 모의고사 2회차 후기

토스 Frontend Fundamentals 모의고사 2회차에 참여하면서 경험한 것들과 후기를 정리해 보려 합니다.

토스 Frontend Fundamentals 모의고사는 어떤 프로그램인가요?

토스 개발자분이 과제를 제시해 주시면, 일정 기간 동안 혼자서 작업을 진행해요.
이후 라이브 세션에서 코드 리뷰와 함께 개발자분의 인사이트를 직접 들을 수 있습니다.
이번 2회차 과제는 이미 구현된 코드를 각자의 시각으로 리팩토링해보는 것이었는데요.
같은 과제를 두고 다양한 사람들이 어떻게 문제를 해결했는지 비교해볼 수 있고, 토스 개발자분들이 코드를 어떤 시각으로 바라보는지 들을 수 있는 아주 유익한 과정입니다.

직접 작업해보기

이번 회차는 이미 만들어진 회의실 예약 시스템을 리팩토링해보는 과제였어요. 크게 두 화면으로 구성되어 있었습니다.

  • 예약현황 화면
  • 예약하기 화면

페이지별로 많은 책임이 존재했고, 코드 역시 page.tsx 하나에 모든 내용이 담겨 있는 구조였습니다.
저는 먼저 역할에 맞게 코드를 분리하는 방향으로 접근했고, 크게 아래 작업들을 진행했어요.

  • 도메인 코드 분리

    • src/pages : 실제 화면을 표시하는 폴더
    • src/features/reservation : 도메인 책임을 가진 코드
    • src/shared : 전역적으로 사용 가능한 공통 코드
  • 관심사 분리
    역할별로 컴포넌트를 나누고 페이지 컴포넌트는 조합(Composition) 역할에만 집중하도록 했습니다.

    • RoomBookingPageReservationStatusPageindex.tsx는 화면 조합과 상위 흐름만 담당하도록 줄였습니다.
    • 각 페이지 아래에 components/, hooks/, types.ts를 두어 “이 페이지에서만 바뀌는 이유”를 가진 코드를 같은 폴더에 응집시켰습니다.
  • 상태 분리 (서버 상태 / UI 상태 / 파생 상태)
    예약룸 조회, 예약하기, 내 예약 삭제 등 서버와 통신하는 로직을 커스텀 훅 형태로 분리했습니다.

  • 예약하기 페이지 필터 상태 정리

    • 필터 상태를 URL searchParams 중심으로 재구성.
    • 파싱/직렬화를 useRoomBookingFilters로 모으고, updateFilter 단일 API로 필터 변경 경로를 단순화.
    • URL에 넣지 않는 선택 상태와 에러 상태는 로컬 UI 상태로 분리해 책임을 나눴습니다.

리팩토링의 중점 포인트는 "깔끔하게 정리된 코드" 였어요.
긴 코드를 나누고, 변수명을 의도가 드러나도록 수정하고, 폴더 구조를 재편했습니다.
작업을 마쳤을 때 나름 괜찮게 정리됐다고 생각했는데, 그건 어디까지나 겉보기의 이야기였어요.

라이브 세션에 참여하고 나서 그보다 훨씬 본질적인 부분에 대한 인사이트를 얻을 수 있었습니다.

얻은 것들

"코드는 읽는 것이 아니라 예측하는 것이다."

제3자가 코드를 봤을 때 "파악하기 쉽다"는 건 단순히 읽기 편한 게 아니라, 관련 파일을 일일이 따라가지 않아도 어떤 동작을 할지 예측 가능한 것이어야 한다는 뜻이었습니다.
처음 들었을 땐 생각해보지 못한 관점이었지만, 공감은 즉각적으로 됐어요. 깔끔함을 추구하다 보면 코드를 이리저리 나누고 옮기게 되는데, 그러다 오히려 전체 흐름을 파악하는 데 방해가 된 경험이 있었기 때문에 더욱 와닿았습니다.

인터페이스를 먼저 설계하기

라이브 코딩으로 직접 시연해 주셨는데, 기존 코드 분석부터 시작하는 게 아니라 핵심 요구사항에 대한 인터페이스를 제로베이스에서 먼저 설계한 뒤, 기존 코드를 그 인터페이스에 맞춰가는 방식으로 작업하셨습니다. 신선하게 다가왔던 점은 리팩토링이라 하면 '기존 코드 파악 → 책임과 역할 분리 → 추상화 → 도메인 분리' 흐름이 당연하게 느껴졌거든요.
인터페이스를 먼저 설계해두면 기존 코드와 대조했을 때 수정해야 할 부분이 훨씬 명확해지고, 작업 시작점도 더 선명해진다는 점에서 실용적인 접근법이라는 생각이 들었습니다.

setState를 이름 그대로 props로 전달할 때의 문제점

컨포넌트에 props를 전달할 때 setState라는 이름 그대로 넘기는 건 저조차도 자주사용하던 패턴이었습니다. setState 자체로 설명 가능한 의도를 담고 있다고 생각했거든요.

그런데 props를 받는 컴포넌트의 입장에서는 달라요. 만약 이 컴포넌트가 재사용성이 강한 컴포넌트라면 어떨까요?

예를 들어 DateSelector 컴포넌트는 날짜 값을 받아 계산하고 반환하면 그만이에요. 부모 컴포넌트에 어떤 상태가 있는지 알 필요가 없죠.
그런데 매개변수 이름이 setDate라면, "부모 컴포넌트에 date라는 state가 존재하고 그것을 set한다"는 외부 맥락을 컴포넌트 안에 끌어들이게 됩니다.

재사용성을 고려한다면 setDate 대신 onChange가 훨씬 자연스럽고 추상적이에요. DateSelector는 외부 맥락을 알 필요 없이 날짜를 반환하는 역할에만 집중할 수 있고, 순수성도 높아져 재사용 컴포넌트로서의 성격에 더 잘 맞습니다.

# DateSelector.tsx
interface DateSelectorProps {
  value?: string; // ✅ date 대신 value
  onChange?: (e: ChangeEvent<HTMLInputElement>) => void; // ✅ setDate 대신 onChange
  ...
}
 
export const DateSelector = forwardRef<HTMLInputElement, DateSelectorProps>(
  ({ value, onChange, ... }, ref) => {
    const inputProps = value !== undefined ? { value } : { defaultValue };
    return (
      <input  onChange={onChange} ... />
    );
  }
);

후기

지금까지 "좋은 코드를 설계한다"는 건 저에게 꽤 막연하게 다가왔습니다.
고려해야 할 요소도 많고, 같은 코드를 보고도 A가 맞다고 하는 사람과 B가 더 낫다고 하는 사람이 공존하니까요. 세션에서도 언급됐듯, 좋은 코드에 정답은 없지만 내가 내린 선택을 명확한 근거로 설명할 수 있는 것이 중요하다는 걸 이번에 깨달았습니다.
그러려면 개발하면서 겪은 경험들, 문제에 직면했던 순간들을 그냥 흘려보내지 않고 사고하면서 내 것으로 만들어가는 태도가 필요하다고 느꼈어요. 그게 결국 좋은 개발자로 성장하는 밑거름이 된다는 걸 다시금 자각한, 의미 있는 경험이었습니다.

댓글 영역을 준비하고 있습니다.