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분)
코치가 실제로 타이핑하면서 사고과정을 실시간으로 언어화합니다.
2. 실제 사례: 종택과 지영의 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
복사
