Q5-1
인원 선택 BottomSheet에서 9명 초과·유아 단독을 막는 검증을 어디에 두셨나요? 왜 거기인가요?
핵심 포인트
tosslib의NumericSpinner는minNumber로 0 미만은 자체 차단하지만, 상한은 합계가 9명이라 spinner 단독으로 표현 불가.CountBottomSheet.tsx의handleChange에서 증가 동작만 가로채서 검증 → 통과 시에만setCount.- 두 가지 룰: (1) 합계 ≥ 9면 "최대 9명까지" 토스트, (2)
key === 'newborn' && adults === 0이면 "유아는 보호자와 함께" 토스트. - 감소는 검증 없이 통과 — UX상 줄이는 행위는 항상 안전.
- 실패 케이스를 토스트로 알려주고
setCount는 호출하지 않음 — 사용자에게 "왜 안 됐는지" 피드백.
모범 답안먼저 답해보고 펼치기
검증을 어디에 둘지가 인원 BottomSheet에서 가장 고민한 부분이에요.
tosslib의 NumericSpinner는 minNumber 같은 단순 상·하한을 prop으로 표현할 수 있지만, "어른+어린이+유아 합계가 9명"은 다른 spinner 상태에 의존하는 룰이라 spinner 단독으로 표현이 안 돼요. 그래서 검증을 한 단계 위 — CountBottomSheet의 handleChange — 에서 가로챘습니다.
handleChange에서 가장 먼저 한 일은 "이 동작이 증가인가 감소인가"를 판별하는 거예요. next > count[key]이면 증가. 증가일 때만 두 가지 룰을 검사합니다 — 합계가 이미 9명 이상이면 토스트 띄우고 setCount 호출하지 않고 early return, 또 key === 'newborn'이고 count.adults === 0이면 "유아는 보호자와 함께" 토스트. 룰을 통과해야만 setCount({...count, [key]: next})가 호출됩니다.
감소는 검증 없이 통과시켰어요. UX 관점에서 "줄이는 동작이 막히는 경우"는 거의 없고, 막더라도 사용자가 헷갈리기만 합니다. 일단 줄이게 하고, 다음 증가 시점에 다시 검증하는 게 마음이 편합니다.
토스트로 피드백을 주는 건 토스 가이드라인의 "사용자 경험 개선 지점" 원칙과 맞닿아 있어요. 그냥 막아만 두면 사용자는 "왜 안 되지?"가 궁금해집니다. 토스트로 짧게 이유를 알려주면 그 자리에서 결정 — "어른을 한 명 더 추가해야겠다" — 이 가능합니다.
이 답변, 어땠나요?
꼬리 질문
-
Q5-1-a. 감소도 검증해야 하는 경우가 있나요? 예를 들면 어른이 0명이 되는데 유아가 남아있는 경우. → 그런 케이스도 룰 자체로는 위반인데, 의도적으로 감소 시점에는 막지 않았어요. 사용자가 어른 수를 임시로 0으로 줄였다가 다른 인원을 조정한 뒤 다시 어른을 늘리는 흐름이 자연스러울 수 있거든요. 대신 "확인" 버튼을 눌러 commit하는 시점에 다시 검증하는 게 답이라고 봤는데, 6시간 안에서는 증가 시점 검증만으로도 큰 문제가 생기지 않아 우선 미뤘습니다. README to-do 후보 중 하나입니다.
-
Q5-1-b. 검증을 atom 레벨로 끌어올릴 수도 있을 텐데요?
setPassengerCountAtom을 write-only atom으로 만들고 거기서 검증하는 식으로. → 가능합니다. 다만 검증 실패 시 "토스트를 띄운다"는 UI side effect가 atom으로 가면 atom이 React/overlay-kit에 의존하게 돼서 테스트·재사용이 까다로워져요. 그래서 "도메인 룰은 atom, UI 피드백은 컴포넌트"로 책임을 나눴습니다. 사실passengerCountAtom은 BottomSheet 안에서useState임시 카운트로 받았다가 "확인" 시 한 번 commit하는 구조라, atom 레벨 검증보다 컴포넌트 레벨 검증이 자연스러웠어요. -
Q5-1-c. 합계 검증은
totalPassengers(count) >= MAX_PASSENGER_TOTAL인데,> 9가 아니라>= 9로 한 이유가 뭔가요? → 증가 직전 시점에 검사하는 거라 그렇습니다. 현재 합계가 이미 9면 한 번 더 증가시키는 순간 10명이 되니까, 증가 자체를 막아야 해요.> MAX_PASSENGER_TOTAL로 두면 9명 → 10명으로 잠깐 통과한 뒤에 막혀서 한 박자 늦은 피드백이 됩니다.
CS · 이론
- Boundary validation: 도메인 룰은 가능한 한 데이터가 변경되는 boundary에서 검증해야 합니다. spinner 컴포넌트 내부가 아니라, "spinner가 주는 next 값을
setCount로 commit하기 직전"이 그 boundary였습니다. - Pessimistic vs Optimistic feedback: 증가를 막고 토스트로 알리는 건 pessimistic 접근 — 잘못된 상태가 일순간이라도 화면에 보이지 않습니다. 금융 도메인에서는 pessimistic이 기본값으로 안전합니다.
- 부정 피드백(Negative feedback)의 비용: 막혔는데 이유가 없으면 사용자가 답답해합니다. 토스 디자인의 결을 따라 짧고 명확한 토스트로 이유를 함께 전달했습니다.