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

Constantly-Dev·2026년 3월 29일

회고

목록 보기
1/1

[이미지 출처 - https://github.com/toss/frontend-fundamentals]


이번 toss FF 모의고사 2회차는 단순히 개발 지식을 더해주는 것뿐만 아니라, 제가 개발하는 '사고 방식'을 다시 돌아보게 만들어준 계기가 되었습니다.

저는 이전부터 추상화라는 개념이 참 어려웠습니다. 모두에게 추상화가 무엇인지 질문하면 '복잡한 내부 구현을 숨기고 간단한 interface만을 남겨두는 것'이라는 답변만 돌아올 뿐이었죠.

이 말이 이해가 안되는 것은 아니었지만 이것이 코드로 옮겨지면 어떻게 표현이 되었어야 하는지, 정말 의미있는 추상화가 아니라 단순 물리적인 코드 분리는 아니었을지 끊임없이 고민이 됐어요.

이 고민을 저만의 생각으로 풀어나간 것이 바로 이 모의고사였습니다.
먼저 글 시작 전 여러분께 질문을 드릴게요.

"여러분이 생각하시는 의미있는 추상화는 무엇인가요?"


과제 진행

이번 2회차 과제는 단순히 UI와 로직을 구현하는 것이 아닌, '회의실 예약'이라는 도메인에서 이미 구현된 코드를 리팩터링하는 것이었어요. 특히 그중에서도 '추상화'라는 주제에 집중해야 하는 과제였어요.

저는 이 과제를 시작하기 전 평소에 의문을 가졌던, 크게 2가지 지점에 더 초점을 두고 과제를 진행했어요.

  1. 물리적인 코드 분리는 의미있는 추상화인가?
  2. 감춰야 하는 코드는 기준은 무엇인가?

이 2가지 질문에 대해서는 꼭 해당 과제를 통해 저만의 답변을 정의해보고자 했어요.


1. 물리적 분리가 추상화라는 착각

재사용성, 반복, 긴 코드로 인한 인지 부하 증가 등 근거가 있는 분리는 목적이 분명하게 있습니다. 하지만 최근 단순 물리적인 코드 분리 또한 의미있는 추상화가 될 수 있는지에 대해 의문이 들었어요.

간단한 TodoList를 예시로 들어볼게요.

우리는 자연스럽게 useTodoList라는 hook으로 만들고, useState와 handler들을 그 안으로 옮기고는 해요. 그러면 결과적으로는 컴포넌트에 코드 수가 줄어드니 가독성이 좋아지고, 추상화를 했다고 생각하죠.

그런데 우리는 생각해봐야 해요.
이 hook을 처음 보는 동료가 내부를 열어보지 않고 이 동작을 이해할 수 있을까요?

Toss 문서 상으로 표현하듯 우리는 해당 코드에 악취를 느끼고 의심을 하기 시작해요.
커스텀 훅으로 분리하긴 했지만, 코드의 물리적인 위치만 바뀌었다는 느낌이 들게 되는 것이죠. hook 내부를 여전히 전부 읽어야 동작이 이해가 간다면, 인지부하는 줄지 않아요. 오히려 시선의 이동이라는 추가 비용만 생길 뿐이에요.

(gemini 생성 이미지)

2. 추상화의 기준 What vs How

그래서 제가 정의내린 것은 추상화의 핵심은 무엇(What)을 하는지 예측 가능한 것이에요.
어떻게(How) 동작하는지를 알 필요 없이, interface만 봐도 내부를 예측할 수 있어야 한다는 것이죠.

실제로 회의실 예약 시스템에서 이 기준을 적용해봤어요.

export function useAvailableRooms(filters: ReservationFilters) {
  const { date, startTime, endTime, attendees, equipment, preferredFloor } = filters;
  const [roomsQuery, reservationsQuery] = useQueries({
    // 생략
  });

  // 생략
  const availableRooms = useMemo(
    // 생략
  );

  return {
    availableRooms,
  };
}

저라면 이 hook을 보고 아래와 같이 사고를 할 것이라고 판단했어요.
이름을 보면 사용 가능한 회의실을 보여주는 것 같다. → 중간에 어떻게 처리되는지 몰라도, filters만 넣으면 availableRooms가 나오네. → 내부를 열지 않아도 된다. 인지 부하가 줄었다.

결국 중요한 키워드는 '예측 가능성'이었어요. 사람이 한번에 기억할 수 있는 메모리 용량이 적은 만큼 모든 것을 파악하기보다 예측이 가능하도록 짜는 것이에요.

이때 useAvailableRooms을 통해 예측 가능성을 높인 방법은 아래와 같아요.

  1. 네이밍이 계약이다: useAvailableRooms라는 이름은 "사용 가능한 방을 준다"는 명시적 계약이다. return 값을 보기 전에 이미 예측이 끝난다.
  2. return이 예측을 검증한다: { availableRooms }만 return된다. 예측과 결과가 일치한다. 불필요한 내부 상태가 노출되지 않는다.
  3. 경계가 명확하다: filters를 넣으면 rooms가 나온다. input/output이 명확한 hook은 테스트도 쉽고, 교체도 쉽다.

3. 무엇을 감춰야 하는가

"복잡한 내부 구현을 감춘다"는 말은 알겠는데, 그 기준이 뭔지는 명확하지 않았어요. 이번 과제를 통해 제가 내린 기준은 다음과 같아요.

'일반적으로 당연하다고 생각되는 것은 감춰도 된다.'

예시로 예약을 취소하는 useCancelReservationMutation을 보면,

export function useCancelReservationMutation() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (id: string) => cancelReservation(id),
    onSuccess: () => {
      // 예약 취소 → 당연히 목록이 갱신되어야 한다
      queryClient.invalidateQueries({ queryKey: reservationKeys.all() });
      queryClient.invalidateQueries({ queryKey: reservationKeys.myReservations() });
    },
  });
}

이걸 2차 추상화로 wrapping한 근거는 두 가지였어요.

  1. 항상 같은 부수효과가 따라온다: 예약을 취소하면 항상 예약 목록 캐시가 무효화돼야 한다. 이 둘은 분리될 수 없는 하나의 작업이다.
  2. 부수효과가 예측 가능하다: "예약을 취소했으니 목록이 새로고침 되겠지" — 이건 누구나 떠올리는 당연한 생각이다. 당연한 것은 감춰도 된다.

결국 당연하다는 일은 '예측이 가능하다'는 것으로 이어지게 돼요.

[26.03.30 수정]
tkdodo의 Creating Query Abstractions 글을 보고 알게 된 것인데, 단순 쿼리 wrapping은 좋은 방법이 아닐 수 있다. useMutation을 직접 wrapping하기보다 queryOptions로 옵션을 분리하는 것이 더 의미있는 추상화가 될 수 있다.

tkdodo - Creating Query Abstractions


해설 강의 정리

이렇게 과제를 진행하고 해설 강의를 들었는데 인상깊었던 문장들을 정리하면 아래와 같아요.

키워드 문장

  • 먼저 이 화면을 가장 예측하기 쉬운 인터페이스는 뭘까 고민하는 것이 중요하다.
  • 추상화 레벨을 점점 맞춰나간다.
    • <h1><Top>, <Title>
  • 도메인을 많이 가진 컴포넌트는 interface에서 표현력이 떨어진다.
    • MyReservation의 적절한 interface를 생각해보면 MyReservation일때가 가장 완벽하다. props가 없어도 충분히 설명이 가능하다.
    • 도메인 언어를 가진 컴포넌트가 반복된다? → 악취가 나는 포인트
  • 생성되는 곳과 소비되는 곳을 가까이 위치시켜라.
  • 우리는 코드를 읽는 것이 아니라 예측한다.
  • 함수의 인자(컴포넌트의 interface)는 안쪽/내부만 바라봐야 한다.
    • setDate를 props로 넘겨주는 것이 왜 문제인가?: setDate가 아니라 onChange가 되어야한다. (onChangeDate여도 되지만 myReservationList처럼 똑같은 표현을 여러번 반복해서 쓸 필요가 없다.)
  • 예측 가능해야한다 == HTML의 interface를 최대한 따라가는 것이 좋다.

회고 및 느낀점

해설을 보고 나서 느낀 점은, 제가 결론에는 어느정도 도달했지만 시작점이 달랐다는 것이에요.

저는 구현과 분리를 먼저하고 거기서 의미를 찾으려고 했고, 해설에서는 의미(interface)를 먼저 정의하고 구현을 채우게 된 것이죠.

어떻게 보면 당연한 부분이라고 생각하는데 이를 실행에 옮기는 것이 쉽지 않은 것 같아요. 평소에도 설계를 먼저 해야한다는 생각을 가지고는 했는데, 이렇게 처음부터 사고하는 과정을 겪으니 많이 부족하다는 점을 느낀 것 같습니다.그래도 해당 과제를 통해 저는 처음 질문에 대한 저만의 답을 찾은 것 같아요.

저는 추상화는 결국 '예측 가능하도록 만드는 과정'이라고 정의할 수 있을 것 같아요. 아무것도 모르는 사람도 바로 동작을 이해할 수 있는 코드를 말이죠.

또한 이번 모의고사를 통해 리팩터링이라는 큰 범주에서 응집도와 결합도, 유지 보수, 확장성과 재사용성, 인지 부하와 같은 파편화된 개념들이 하나의 예측 가능성이라는 공통의 목적으로 수렴하는 과정이라는 생각을 하게 되었어요.

이제 앞으로는 조금 더 interface-first한 생각을 가지고 개발에 집중해봐야겠다는 생각이 들었습니다.

마지막으로 처음 질문을 다시 드리면서 글을 마무리할게요.

"여러분이 생각하시는 의미있는 추상화는 무엇인가요?"

profile
FE developer - Changemaker

0개의 댓글