Search

3. CTA 실습 가이드

Part 1: CTA 기반 인지모델 비교 (1시간)

Phase 1: Individual Timeline 작성 (15분)

<화면 구성>
┌─────────────────────┬─────────────────────┐ │ 선정된 코드 │ CTA 타임라인 │ │ (read-only) │ (각자 작성) │ └─────────────────────┴─────────────────────┘
Plain Text
복사
코치:
"15분 동안 이 코드를 리팩토링하면서,
'무엇을 보고, 어떻게 판단하는지' 실시간으로 기록해주세요.
완벽하게 리팩토링할 필요 없어요. 사고 과정이 중요합니다."

Phase 2: Compare - 인지모델 비교 (20분)

<비교 화면>
┌──────────────────────┬──────────────────────┐ │ 코치 타임라인 │ 학습자 타임라인 │ └──────────────────────┴──────────────────────┘
Plain Text
복사
비교 포인트:
1.
순서 차이 - 무엇을 먼저 봤나?
2.
판단 시점 차이 - 언제 "이상하다"고 느꼈나?
3.
Cue 차이 - 어떤 신호를 봤나?

Phase 3: Progressive Deepening (20분)

6단계 질문으로 깊이 파고듭니다.

Phase 4: Think-Aloud Demonstration (5분)

코치가 실제로 타이핑하면서 사고과정을 실시간으로 언어화합니다.

실제 사례: 종택과 지영의 CTA 세션

이제 실제로 어떻게 진행되는지 볼게요. 종택(코치)과 지영(학습자)의 세션입니다.
제출된 코드: QuestionPage 컴포넌트
설문 퍼널 페이지
useEffect로 초기 질문 설정
useAnswerStore 전역 상태 사용
약 50줄

Phase 1: 각자 15분 분석

세션이 시작됐어요. 화면 왼쪽엔 QuestionPage 코드가 떠 있고, 오른쪽엔 빈 타임라인 템플릿이 있어요. 종택과 지영, 그리고 다른 학습자들이 동시에 15분 타이머를 시작합니다.
조용합니다. 각자 코드를 보면서 타임라인을 적어가요.
15분 후, 타이머가 울립니다.
종택의 타임라인
시간
행동
판단
Cue
목표
0:00
요구사항 확인
-
-
이해하기
0:15
UI 계층 체크
매칭 OK
네비-점수-퀴즈-버튼
검증
0:30
useEffect 발견
더럽다!
setState 포함
우선순위
1:00
의도 파악 시도
불명확
questions 의존
이해하기
2:00
useAnswerStore 확인
예측 불가
main/subId 뒤섞임
설계 검토
3:00
요구사항 재확인
초기값 설정
1:1 매칭
의도 파악
지영의 타임라인
시간
행동
판단
Cue
목표
0:00
요구사항 읽음
-
-
이해하기
0:30
이상적 형태 상상
-
-
설계
1:00
answerStore 확인
score 제거
파생값
고치기
1:30
id 분리 결정
path param
렌더링 로직
고치기
2:00
Selector 수정 계획
onSelect 추가
암묵적 의존
고치기

Phase 2: Compare - 인지모델 비교 (20분)

종택이 두 타임라인을 화면에 나란히 띄웁니다.
"자, 이제 비교해볼게요."
차이 1: 비교 단계가 빠졌다
종택: "둘 다 요구사항부터 확인했네요. 좋아요."
화면에 표시합니다:
종택: 요구사항 → 이상적 형태 상상 → 코드 매칭 확인 → 로직 이해 지영: 요구사항 → 이상적 형태 상상 → (비교 생략) → 세부사항
Plain Text
복사
[사고의 타임라인 비교]
종택(Expert): [관찰 ───] [문제 정의 ─────] [해결 ─]
"왜 이게 문제지?"를 고민하는 데 시간의 80%를 씁니다. 해결은 순식간입니다.
지영(Novice): [관찰 ─] [해결 시도 ───────────] (헤맴)
"일단 고치자!" 하며 바로 뛰어듭니다. 그리고 엉뚱한 곳에서 시간을 씁니다.
CTA를 통해 이 '비율의 차이'를 눈으로 확인해야 합니다.
종택: "저도 지영님처럼 했어요. 요구사항 읽고, '설문 퍼널이면 이런 식으로 구성되어야 하겠지' 이상적 형태를 머릿속에 그렸어요. 네비게이션, 점수 표시, 퀴즈, 다음 버튼.
그다음이 중요한데요, 코드랑 1:1로 매칭되는지 체크했어요. '네비게이션 있네, 점수 표시 있네, 퀴즈 있네, 버튼 있네. OK. 대체로 되어 있구나.'
지영님은요?"
지영: "저는... 이상적 형태를 상상하고 나서 바로 '그럼 score를 제거해야겠다', 'id는 path param으로' 이렇게 갔어요."
종택: "바로 그거예요. 비교 단계가 빠진 거예요. 이상적 형태를 상상했으면, 현재 코드가 얼마나 그것과 가까운지 확인해야 해요. 어떤 건 이미 잘 되어 있을 수 있거든요. 어떤 건 이상할 수 있고.
저는 '대체로 잘 되어 있네' 확인했어요. 그럼 이제 남은 건 로직이 제대로 동작하는가를 이해하는 거죠. 그래서 useEffect를 보게 된 거고."
차이 2: 문제 정의가 없었다
종택: "더 중요한 차이가 있어요."
화면을 바꿉니다:
종택: 0:30 useEffect 발견 → "더럽다" 1:00 "왜 필요했지?" ← 문제 정의 2:00 "초기값 설정이구나" ← 의도 파악 3:00 "어떻게 명시적으로?" ← 해결책 지영: 1:00 "score 제거" ← 바로 해결책 1:30 "path param으로" ← 바로 해결책
Plain Text
복사
종택: "지영님, useEffect 보셨죠?"
지영: "네, 봤어요."
종택: "어떻게 생각하셨어요?"
지영: "없애야 할 것 같다고요."
종택: "'왜' 없애야 하죠?"
지영: "음..." (말을 잇지 못함)
종택: "바로 그거예요. 무슨 문제를 풀지 정의를 안 하고 세부사항으로 바로 가신 거예요.
저는 useEffect를 보고 0.5초 만에 '더럽다'고 느꼈지만, 바로 없애지 않았어요. 2분 반 동안 '왜 문제인가?' **'왜 필요했을까?'**를 계속 물었어요.
그래서 '아, 이게 초기값 설정이구나. useEffect로 하면 시간축에 얽혀서 예측 불가능해지는구나. 그럼 어떻게 명시적으로 바꿀까?' 이 순서로 간 거예요.
지영님은 이 과정을 다 건너뛰었어요."
차이 3: 0.5초 vs 1분
종택: "판단 시점도 볼게요."
종택: 0:30 "더럽다!" (감각) 지영: 1:00 "score 제거" (결론)
Plain Text
복사
종택: "제가 30초에 뭔가를 느꼈어요. 지영님은 1분에 결정을 내렸고요. 근데 성질이 달라요.
저는 '느낌'이었어요. '이거 더러운데?' 그다음 2분 반 동안 왜 더러운지, 왜 필요했는지 파고든 거죠.
지영님은 1분에 바로 '결론'이었어요. score 제거, path param. 중간 과정 없이."
차이 4: Cue - 신호를 뭘 봤나
종택: "지영님, useEffect 외에 뭘 더 보셨어요?"
지영: "score가 파생값이라는 거요."
종택: "useEffect에서는 어떤 신호를 보셨어요?"
지영: "음... setState가 있다는 거?"
종택: "제가 본 신호를 보여드릴게요."
화면에 표시합니다:
종택이 0.5초에 본 신호: 1. useEffect 존재 2. setState 포함 3. questions 의존성 → 동시에 세 개 지영이 본 신호: 1. score가 파생값 → 하나
Plain Text
복사
종택: "저는 세 개를 동시에 봤어요. 그래서 '아, 이거 시간축에 얽히겠네. 복잡해지겠어' 바로 느낀 거죠.
지영님은 다른 걸 보셨어요. score가 파생값. 그것도 맞는 관찰이에요. 근데 useEffect의 신호들은 놓치셨어요."
지영: "아... 저는 useEffect를 보긴 봤는데, 왜 문제인지를 구체적으로 생각하지 않았어요. 그냥 '이상하니까 없애야지'만 생각했어요."
종택: "바로 그거예요. 신호를 보는 것왜 문제인지 정의하는 것은 다른 거예요. 신호는 봤지만 문제를 정의하지 않았기 때문에, 해결책으로 바로 점프한 거죠."
종택의 눈에 보인 코드:
// 🚨 신호 1: useEffect가 비즈니스 로직을 통제함 useEffect(() => { // 🚨 신호 2: setState가 렌더링을 또 유발함 setMainId(questions[0].id); }, [questions]); // 🚨 신호 3: 외부 의존성이 변경되면 예측 불가능하게 실행됨
JavaScript
복사
종택의 눈에는 이 3가지 키워드가 마치 빨간색 네온사인처럼 반짝거렸습니다. 여러분도 이 네온사인을 켜는 것이 목표입니다.

Phase 3: Progressive Deepening (20분)

종택: "이제 더 깊이 파고들어볼게요. 6단계 질문을 해볼 거예요."
Level 1: WHAT - 무엇을 봤나
종택: "지영님, 코드를 보고 정확히 무엇을 먼저 하셨어요?"
지영: "요구사항을 읽었어요."
종택: "그다음은요?"
지영: "이상적인 형태를 생각했어요. '설문지를 보여준다', '현재 답변을 저장한다' 이런 식으로요."
종택: "그다음은요?"
지영: "음... answerStore를 봤어요. score가 파생값이니까 제거해야겠다고 생각했고요."
종택: "좋아요. 저는 달랐어요. 요구사항 읽고, 코드 구조를 체크했어요. 요구사항이랑 1:1로 매칭되나 확인한 거죠. '네비게이션 있네, 점수 표시 있네...' 대체로 되어 있더라고요.
그다음 로직을 이해하려고 들어갔는데, useEffect가 보인 거예요."
Level 2: WHY - 왜 그 순서였나
종택: "지영님, 왜 이상적 형태를 먼저 상상하셨어요?"
지영: "음... 어떻게 되어야 하는지 알아야 고칠 수 있잖아요."
종택: "맞아요. 근데 현재 코드가 어떤지 확인하는 단계가 빠졌어요. 이미 잘 되어 있는 부분도 있을 수 있잖아요. 저는 '요구사항과 코드가 1:1로 매칭되어 있는지' 먼저 체크했어요. 대체로 되어 있었어요.
그럼 이제 남은 건 '로직이 제대로 동작하는가'를 이해하는 거예요. 그래서 로직을 보러 들어간 거죠."
Level 3: CUES - 어떤 신호를 봤나
종택: "이제 핵심이에요. 지영님, useEffect를 보셨나요?"
지영: "네, 봤어요."
종택: "어떻게 느끼셨어요?"
지영: "음... 없애야 할 것 같다고요?"
종택: "왜요?"
지영: "음... (잠시 침묵) 잘 모르겠어요. 그냥 이상해 보여서요."
종택: "바로 그거예요. '이상하다'까지만 느끼고, 왜 이상한지를 구체적으로 사고하지 않으신 거예요.
제가 본 신호를 보여드릴게요."
화면에 표시합니다:
신호 1: useEffect 존재 신호 2: 안에 setState 포함 신호 3: questions를 의존성으로 이 세 개를 0.5초에 동시에 봤어요. 왜 문제인가? → 시간축에 얽힘 → questions 변경될 때마다 실행 → 예측 불가능 → 시나리오 이해 방해
Plain Text
복사
종택: "이게 위생의 감각이에요. 못생긴 게 아니라 더러운 거(!!)예요. 기능은 동작하겠죠. 근데 시간이 지나면 복잡해질 거예요. 버그가 생길 거예요. 그게 보이는 거예요.
지영님은 이 신호를 못 보신 거예요. 아니, 봤는데 왜 문제인지를 사고하지 않으신 거죠."
지영: "아... 맞아요. 그냥 '없애야지'만 생각했어요."
Level 4: GOALS - 목표가 뭐였나
종택: "지영님 목표가 1분부터 '고치기'였죠?"
지영: "네."
종택: "저는 3분까지도 '고치기'가 아니었어요. 계속 **'왜 필요했을까?'**를 물었어요.
useEffect가 더럽게 느껴졌지만, 일단은 '왜 이게 여기 있지?' 이해하고 싶었어요. 없애기 전에 의도를 파악해야 하니까요.
1분 동안 코드를 보고, useAnswerStore 인터페이스를 봤어요. main/subId가 뭔지 이해가 안 가더라고요. 2분쯤에 '아, 이게 현재 렌더링하는 질문의 id구나' 이해했어요.
그다음 요구사항을 다시 봤어요. '아, 받아온 퀴즈 중 첫 번째를 기본으로 선택하고 싶었구나. 초기값 설정이네.'
3분 되어서야 '그럼 이걸 어떻게 명시적으로 바꿀까?' 고민을 시작한 거예요."
지영: "아... 저는 그 과정을 다 건너뛰었네요."
Level 5: ALTERNATIVES - 다른 방법은
종택: "지영님, path param 말고 다른 방법도 생각해보셨나요?"
지영: "아니요... 그게 제일 좋은 것 같아서요."
종택: "왜 제일 좋죠?"
지영: "음... 외부에서 관리하니까요?"
종택: "좋아요. 근데 왜 외부에서 관리해야 하죠?"
지영: "음..." (말을 잇지 못함)
종택: "봤죠? 해결책은 있는데, 왜 그게 해결책인지 설명을 못 하는 거예요. 왜냐면 문제 정의가 없었으니까요. 저는 아래처럼 세 가지 대안을 비교하고 선택한 거예요."
문제: useEffect로 초기값 설정 → 왜 문제?: 실행 시점 불명확, 시간축 얽힘 → 의도: 첫 번째 질문을 기본으로 선택 → 대안 1: 상위 컴포넌트에서 prop으로 → 대안 2: path param으로 → 대안 3: 스토어 초기값으로 path param 선택 이유: → URL이 source of truth → 브라우저 히스토리와 싱크 → 명시적
Plain Text
복사
Level 6: EXPERTISE - 전문성은 어떻게
종택: "지영님, 초보 때의 나라면 어땠을 것 같아요?"
지영: "음... 저처럼 바로 고쳤을 것 같아요."
종택: "맞아요. 저도 그랬어요. 근데 언제부터 바뀌었냐면, '잘못 고쳐서 더 복잡해진' 경험을 수십 번 하고 나서예요.
useEffect 없앴더니 다른 버그가 생기고, 그걸 고치려고 또 useEffect 추가하고... 이런 악순환을 겪었어요.
그때 깨달았어요. '아, 의도를 먼저 파악해야겠다. 왜 이 코드가 여기 있는지를.'
지금은 useEffect 보면 자동으로 '왜 필요하지?' 생각이 떠올라요. 습관이 된 지 1년쯤 됐어요. 위생의 감각도 그렇게 만들어진 거예요.”
지영님도 할 수 있어요. 해결책 떠올리기 전에 30초만 먼저 '왜?'를 물어보세요.
'왜 이게 문제지?' '왜 이게 여기 있지?' '왜 이렇게 만들었을까?'
3개월이면 습관이 됩니다."

Phase 4: Think-Aloud 시연 (5분)

종택: "이제 제가 실제로 리팩토링하면서 생각을 말할게요. 제 머릿속을 실시간으로 보여드리는 거예요."
화면에 QuestionPage 코드가 떠 있어요. 종택이 타이핑을 시작합니다. 큰 소리로 말하면서요.
"자, useEffect를 봤어요. (0.5초)
setState가 있네요. questions를 의존하고.
이건 더러워. 못 참겠어.
왜 더러운가? (의식적 사고 시작)
시간축에 얽혀요. questions 변경될 때마다 실행되고.
근데 questions가 언제 변경되지?
API 호출할 때 한 번만? 그럼 이 useEffect는 한 번만 실행되면 되는데...
그럼 이게 뭘 하려는 거지? (의도 파악)
setMainId(questions[0].id)... 아, 첫 번째 질문을 기본으로 선택하는구나.
초기값 설정이네.
근데 왜 useEffect로? (문제 정의)
초기값은 useEffect로 하는 게 아니야. 명시적이지 않거든.
questions[0]이 언제 결정되는지도 불명확하고.
어떻게 명시적으로 바꿀까? (대안 탐색)
외부에서 id를 전달받으면?
path param... /question/:id
그럼 URL이 source of truth가 되고...
useEffect 없애고... id를 prop으로...
좋아, 시작해봅시다..."
지영을 비롯한 학습자들이 놀란 표정으로 봅니다.
지영: "와... 진짜 0.5초 만에 '더럽다'는 게 보이시네요. 그리고 3분 동안 '왜?'만 물으시고..."
종택: "수백 번 봤거든요. 여러분도 의도적으로 연습하면 됩니다.
핵심은요:
1.
신호를 먼저 보세요 (useEffect + setState)
2.
왜 문제인지 물으세요 (시간축 얽힘)
3.
의도를 파악하세요 (초기값 설정)
4.
대안을 비교하세요 (prop vs param vs store)
5.
그다음 고치세요
이 순서만 지키면 됩니다."

지영의 회고

세션이 끝나고 지영이 회고를 씁니다.
오늘의 깨달음 3줄
1.
"점프하는 습관"이 병목
이상적 형태 상상 후 거기서 멈추고 코드와 비교했어야 함.
바로 구현 상세로 점프했어.
useEffect를 "없애야한다"까지만 생각하고 "왜 없애야 하는지" 구체적으로 사고하지 않음.
2.
리팩토링 = 비교 작업
종택님은 요구사항 확인 → 코드 매칭 체크 → 로직 이해.
나는 요구사항 확인 → 이상적 형태 상상 → 해결책.
비교 과정이 빠졌어.
3.
우선순위는 "위생 감각"
종택님은 useEffect를 보고 0.5초 만에 "더럽다" 느낌.
"고치면 되는 것"과 "더러워서 못 참는 것"을 구분함.
나는 우선순위 기준이 없었어.
다음 주 목표:
□ 해결책 떠올리기 전 30초 멈추기 □ "왜 이게 문제지?" 질문하기 □ useEffect 보면 "시간축 얽힘" 체크하기
Plain Text
복사