Toss Frontend Fundamentals 모의고사 2회차 회고

KimYoungWoong·2026년 3월 29일
post-thumbnail

1회차에 이어서 2회차에도 참여했다. 개인적으로 토스 엔지니어 분들과 동료 개발자 분들의 코드와 생각들을 듣고 나눌 수 있는 좋은 프로그램이라고 생각해서 꼭 참여해보는 것이 좋다고 생각한다.

라이브 세션과 PR 리뷰를 보고 기억에 남았던 것들을 모아 정리해봤다.


UI와 1:1

// AS-IS
function RoomBookingPage() {
  return (
    <div style={{ padding: 20 }}>
      <div style={{ fontSize: 24, fontWeight: 'bold' }}>회의실 예약</div>
      <div className="calendar-wrapper">...</div>
      <div style={{ marginTop: 40 }}>
        {reservations.map(r => <div key={r.id}>...</div>)}
      </div>
    </div>
  );
}

// TO-BE
function RoomBookingPage() {
  return (
    <main>
      <TopNavigation title="회의실 예약" />
      <section>
        <h2>날짜 선택</h2>
        <DatePicker value={date} onChange={setDate} />
      </section>
      <ReservationStatusSection selectedDate={date} />
      <section>
        <MyReservationList items={myReservations} />
      </section>
      <Button onClick={handleOpenBooking}>예약하기</Button>
    </main>
  );
}

사람의 기억력과 인지 능력은 한계가 있기 때문에 HTML이 그림처럼 위에서 아래로 읽히는 것처럼, 컴포넌트들도 UI와 1:1 대응이 되게 해서 예측을 쉽게 해줄 수 있어야 한다.


세부 구현은 컴포넌트 안으로

내장이 쏟아져 나온 것 같다

다소 직설적이지만 적절한 표현이었던 것 같아서 기억에 남았서 적었다.

// 페이지에 계산 로직이 흘러넘침
const timeSlots = Array.from({ length: 24 }).map((_, i) => {
  const hour = Math.floor(i / 2) + 9;
  const min = i % 2 === 0 ? '00' : '30';
  return `${String(hour).padStart(2, '0')}:${min}`;
});

return <페이지 컴포넌트 />;

---

// 의도만 전달하고, 구현은 안으로
<TimeSelect value={start} onChange={setStart} minTime="09:00" maxTime="20:00" />

페이지 레벨에서 Array.from().map() 같은 계산 루프가 돌아가고 있다면, 굳이 페이지가 시간 배열 만드는 방법 -> 세부 구현까지 알아야 할 필요가 있나 생각해 볼 필요가 있다.
TimeSelect에는 "09시부터 20시까지"라는 의도만 전달하면 충분하다.


Props 이름에 세부 구현은 좀

DateSelectorsetDate를 props로 내리면, 이 컴포넌트는 영원히 "상태를 변경하는 용도"로만 쓰일 수밖에 없다. 컴포넌트는 자신이 할 행위만 props로 요청해야 하고, 외부에서 어떻게 처리할지는 관여하면 안된다.

// AS-IS 외부 구현 방식에 종속됨
<DateSelector setDate={setDate} />

// TO-BE HTML 표준 인터페이스를 따름
<DateSelector value={date} onChange={setDate} />

value, onChange는 HTML input이 쓰는 방식이고, 우리가 이미 제일 익숙한 인터페이스이다.
2부에서 문동욱 님이 인터페이스는 구멍이라는 표현을 쓰셨는데, 규격이 안 맞는 구멍은 아무것도 연결하지 못한다는 게 적절한 비유였던 것 같다.

추가로 PR 리뷰에서도 비슷한게 있었는데, props 이름이 컴포넌트 이름과 그대로 겹치는 문제였다. <MyReservationList reservationList={data} />처럼 쓰면 "마이 레저베이션 리스트의 레저베이션 리스트"가 되는데, 문맥이 명확하다면 그냥 items정도면 OK다.


함수 이름의 배신

// ❌ 호출부에서는 알 수 없는 사이드 이펙트
const navigate = (url) => {
  if (hasMessage) setMessage("예약이 완료되었습니다.");
  router.push(url);
};

<Button onClick={() => navigate('/booking')}>예약하기</Button>

// ✅ 호출부에서 흐름이 그대로 보임
const handleBooking = () => {
  setMessage("예약이 완료되었습니다.");
  router.push('/booking');
};

<Button onClick={handleBooking}>예약하기</Button>

이름은 단순 이동인데 내부에서 외부의 메세지까지 조작한다. 함수 이름이 예측을 유도하는데 내부에서 전혀 다른 동작을 하면, 이해 하는 것에 추가적인 리소스가 필요하게 된다.
이런 코드가 쌓일수록 이해하는 것이 더 피로해지고, 버그를 추적하기가 점점 힘들어진다.
예측 가능한 코드와 이해하기 쉬운 코드는 결국 같은 말인 것 같다.


마무리

모의고사를 풀 때에는 거대한 코드에 당황하여 목적 없이 마구 잡이로 리팩토링을 했던 것 같다.
라이브 세션을 듣고 복습 겸 추가적인 리팩토링을 할 때에는 중요한 원칙을 세우고 차근차근 진행해보니 이전 코드 보다는 훨씬 나아진 것 같았다.
또한 동료 개발자 두분과 코드 리뷰 및 온라인 밋업으로 리뷰와 궁금증을 해결하는 시간을 가졌었는데 이 시간도 굉장히 재밌고 유익했다.
이른 시간 안에 3회차가 시작되어 다시 해볼 수 있다면 좋겠다.

0개의 댓글