종합 — 안티패턴 진단과 통합 케이스 스터디 · 퀴즈

6 문항 · Bloom: Analyze:1, Apply:1, Evaluate:4

Q1 Analyze mcq_single

동료 PR에 다음 코드가 들어왔다. ```jsx let timeoutId; // 모듈 전역 function SearchBox() { return <input onChange={e => { clearTimeout(timeoutId); timeoutId = setTimeout(() => fetchResults(e.target.value), 300); }} />; } ``` 진단 클리닉 프로토콜(어떤 패턴 → 영향도 × 수정 난이도 → 어느 섹션 도구)에 따라 가장 정확한 분석은?

정답: B
timeoutId가 모듈 전역이라 같은 컴포넌트 두 인스턴스가 서로의 타이머를 덮어쓴다. 이는 비-렌더 데이터를 인스턴스별로 격리해야 하는 useRef의 정확한 사용처(S1.3, S1.5)이며, 한 줄~몇 줄 변경으로 끝나므로 영향도 높음/난이도 낮음 → 매트릭스 우상단 '오늘 바로'.
오답 해설:
  • A. derived state 문제가 아니다. 전역 변수로 비-렌더 데이터를 보관한 것이 핵심.
  • C. 객체 prop 참조 문제가 아니라 모듈 전역 mutation이 원인.
  • D. cleanup 누락도 부차적 이슈지만 근본 원인은 인스턴스 격리 실패. 타이머에 cleanup만 붙여도 전역이면 여전히 충돌.
Q2 Evaluate mcq_multi

S9.C1 매트릭스에 다음 6개 케이스를 배치했다고 하자. - 케이스1: 전역 timeout (S1) - 케이스2: 렌더 중 ref.current 읽어 표시 (S1/S2) - 케이스3: derived state를 Effect로 (S4) - 케이스4: Effect chain으로 카운터 (S4) - 케이스5: 객체 prop으로 무한 재연결 (S7) - 케이스6: useMount lifecycle wrapper (S8) '리팩터링 우선순위는 영향도 우선, 난이도는 동률일 때만'이라는 원칙을 따를 때, **이번 스프린트에서 먼저 손대야 하는** 케이스 2개를 모두 고르시오. (정답 2개)

정답: A, D
영향도 우선 원칙에 따라 '영향도 높음' 케이스(1, 4)가 먼저다. 케이스1은 우상단(영향도↑·난이도↓)이라 즉시 처리, 케이스4는 영향도↑·난이도 중간이라 같은 스프린트 내 두 번째 우선순위. 케이스3은 난이도가 낮아 손대기 쉽지만 '쉬운 것부터'의 함정이며 영향도가 낮으므로 자투리 시간에.
오답 해설:
  • B. 영향도 낮음이라 우선순위에서 밀린다. 백로그.
  • C. 난이도가 낮다고 먼저 하는 것은 '쉬운 것부터' 함정. 영향도가 낮음.
  • E. 영향도 낮고 호출처 모두 바꿔야 해서 난이도도 만만치 않음. 백로그.
Q3 Apply mcq_single

검색 가능한 채팅방에서 검색 결과를 가져오는 코드를 작성한다. 다음 중 결정 트리(외부 시스템? 렌더 중 계산 가능? 인터랙션 결과?)와 race condition 방어를 모두 만족하는 구현은?

정답: C
query는 '표시 중 동기화'(URL 진입·뒤로가기에서도 fetch가 필요하므로 인터랙션 결과 아님) → Effect가 정답. 빠른 타이핑 시 이전 fetch가 늦게 도착해 새 결과를 덮어쓰는 race를 ignore flag cleanup으로 방어한다(S3.3, S4.5).
오답 해설:
  • A. useMemo는 동기 계산용. Promise를 반환하는 비동기 작업에 적합하지 않다.
  • B. onChange에 fetch를 묶으면 URL 직접 진입·뒤로가기 시에는 fetch가 안 일어남. 인터랙션 결과로 잘못 분류.
  • D. 결정 트리는 맞췄지만 ignore flag가 없어 race 발생 가능 — 빠른 타이핑 시 stale 결과로 덮어쓰임.
Q4 Evaluate mcq_single

통합 케이스의 '새 메시지 자동 스크롤' 블록에서 다음 두 가지가 동시에 등장한다 — `listRef` 와 `flushSync`. 한 동료가 PR에서 묻는다: "왜 그냥 `setMessages` 후에 `listRef.current.scrollTo(...)` 하면 안 돼? 왜 둘 다 필요해?" 가장 정확한 review 답변은?

정답: B
S2.3의 핵심 — state setter 직후 새 DOM을 즉시 읽어야 하는 시나리오에서 batching이 깨지지 않으면 scrollHeight가 stale. flushSync로 동기 업데이트 강제 → 그 직후 ref로 명령형 scrollTo. 두 도구는 역할이 다르다(ref=명령형 DOM 접근, flushSync=타이밍 강제).
오답 해설:
  • A. flushSync는 batching 회피용이지 Strict Mode와 무관. 빼면 stale scrollHeight 버그 재발.
  • C. React가 관리하는 노드를 querySelector로 잡아 직접 조작하면 동작은 하더라도 ref가 표준이며, 본질은 ref 유무가 아니라 batching 타이밍이라는 진단을 놓침.
  • D. useEffect 패턴은 한 박자 늦은 스크롤(이미 다음 렌더의 입력 반영)이 되거나 추가 렌더를 유발. 즉시 동기 측정 시나리오에는 flushSync가 정답.
Q5 Evaluate mcq_multi

다크모드 토글이 채팅 재연결을 일으키는 다음 코드를 진단·처방하라. ```jsx useEffect(() => { const c = createConnection(serverUrl, roomId); c.connect(); showToast(`Connected in ${theme} mode`); return () => c.disconnect(); }, [serverUrl, roomId, theme]); ``` **적절한** 처방을 모두 고르시오. (정답 2개)

정답: B, C
theme은 알림에만 쓰이는 non-reactive 의도지만 코드상으론 reactive — useEffectEvent로 그 부분만 추출하면 deps 오염 없이 최신 theme 사용(S6.2). 실험적 API가 아직 stable 아닐 때의 fallback은 ref 동기화 패턴(보일러플레이트 비용 인정). suppress는 S7.1이 명시적으로 금지하는 안티패턴이며, '재연결 받아들임'은 사용자 경험 손상이 명백한 상황에서 가독성보다 정확성이 이긴다는 트레이드오프 판단을 무시한다.
오답 해설:
  • A. linter suppress는 'deps가 아니라 코드를 바꾼다'(S7.1) 원칙 위반. 금지된 처방.
  • D. 메시지 끊김은 명확한 사용자 경험 손상. 재연결을 받아들이는 것은 정당한 트레이드오프가 아니다.
Q6 Evaluate short_answer

최종 솔루션에서 `useChatRoom({ serverUrl, roomId, onMessage })` 커스텀 훅을 추출했다. 그런데 현재 앱에서 useChatRoom은 단 한 곳에서만 사용된다. 동료가 PR review에서 묻는다: "단일 사용처인데 왜 추출했어? S8.4는 단일 사용처에 Custom Hook 자제하라고 했잖아." 이 결정을 정당화하라. 답변에는 (1) 왜 추출했는지(2가지 이상의 가치), (2) 어떤 다른 선택지가 있었고 왜 탈락했는지, (3) 받아들인 트레이드오프를 모두 포함할 것.

정답: 참조 답안 키워드: (1) 의도 캡슐화('채팅방 연결'이라는 도메인 명명) + 핸들러 최신화 패턴(useEffectEvent wrap, S8.3) 캡슐화 + 미래의 useSyncExternalStore로 내부 교체 가능(S8.5) + 테스트 가능성. (2) 탈락 — 인라인 Effect 유지: 컴포넌트 본문이 동기화 노이즈로 길어지고 의도 흐려짐 / useMount 같은 lifecycle wrapper로 추출: deps linter가 죽고 의도 잃음(S8.4). (3) 트레이드오프: 약간의 간접 지향(파일/호출 한 단계 더), 단일 사용처라 DRY 가치는 약함 — 그러나 의도 표현·핸들러 패턴 캡슐화·교체 가능성이 그 비용을 정당화.
S8.4는 'lifecycle wrapper나 Hook 미호출 함수'를 자제하라는 것이지 모든 단일 사용처를 금지하지 않는다. 핵심 채점 포인트는 LO-S9.3의 본질 — '왜 이 도구인가, 다른 선택지는 무엇이었나, 어떤 트레이드오프를 받아들였나'를 모두 답하는 것. 채점 rubric (4점 만점): - 1점: 추출한 가치를 2개 이상 제시 (의도 캡슐화 / 핸들러 패턴 / 교체 가능성 / 테스트 중 2개) - 1점: 다른 선택지(인라인 유지, lifecycle wrapper 등) 중 최소 1개를 비교하고 탈락 사유 제시 - 1점: 받아들인 트레이드오프(간접 지향, 단일 사용처라 DRY 약함 등)를 명시 - 1점: S8.4의 '단일 사용처 자제' 원칙이 lifecycle wrapper에 대한 것임을 구분하거나, 의도 캡슐화 가치가 DRY를 능가한다는 판단 근거 제시