Search

왜 ‘추상화 교과서’를 읽어야 할까?

1. 코드 분리는 추상화가 아니다

"이 복잡한 로직들을 분리하면 좀 나아지지 않을까?"
많은 개발자들이 이렇게 생각합니다. 그리고 실제로 그렇게 합니다. 다음 컴포넌트를 보겠습니다.
function ProductList() { const [cart, setCart] = useState([]); // ... const handleAddToCart = async (product) => { /* ... */ }; const handleLike = async (productId) => { /* ... */ }; return ( <div> {products.map(product => ( <div key={product.id} className="card"> <img src={product.imageUrl} alt={product.name} /> <h3>{product.name}</h3> <p>{product.price}</p> <div className="actions"> <button onClick={() => handleAddToCart(product)} disabled={loadingProductId === product.id} > {loadingProductId === product.id ? '담는중...' : '장바구니'} </button> <button onClick={() => handleLike(product.id)} disabled={likedProducts.has(product.id)} > ❤️ {likeCounts[product.id] || 0} </button> </div> </div> ))} </div> ); }
JavaScript
복사
장바구니 상태, 로딩 상태, 좋아요 상태, 좋아요 개수가 모두 뒤섞여 있습니다. "정리가 필요하다"는 느낌이 듭니다. 그래서 개발자는 UI 부분을 컴포넌트로 분리합니다.
function ProductCard({ product, onAddToCart, isAddingToCart, onLike, isLiked, likeCount }) { return ( <div className="card"> <img src={product.imageUrl} alt={product.name} /> <h3>{product.name}</h3> <p>{product.price}</p> <div className="actions"> <button onClick={onAddToCart} disabled={isAddingToCart}> {isAddingToCart ? '담는중...' : '장바구니'} </button> <button onClick={onLike} disabled={isLiked}> ❤️ {likeCount} </button> </div> </div> ); }
JavaScript
복사
언뜻 보면 성공한 것 같습니다. 컴포넌트는 작고, props는 명확하며, UI만 담당합니다.

하지만 정말 그럴까요?

이제 "공유하기" 기능을 추가해야 한다고 상상해보겠습니다. onShare props를 추가할까요? "위시리스트" 기능은요? onAddToWishlist, isInWishlist도 추가해야겠죠.
기능이 하나씩 추가될 때마다 ProductCard의 props는 계속 늘어납니다. 이 컴포넌트는 금방 누더기가 될 것입니다.
더 큰 문제는 재사용입니다. "상품 정보만 보여주면 되는데, 왜 장바구니 관련 props를 다 넘겨줘야 하지?" 이 컴포넌트의 인터페이스는 더 이상 "의미 있는 계약"이 아니라 단순한 "데이터 통로"가 되어버렸습니다.

무엇이 잘못되었을까?

코드는 분리되었습니다. 그러나 복잡도는 사라지지 않았습니다. 단지 숨겨졌을 뿐입니다.
ProductCard의 인터페이스를 다시 보세요. 너무 구체적인 사항들이 props에 하드코드되어 있습니다. 이것은 변경에 취약한 설계입니다. 복잡도 감소라는 추상화의 본질적 목표에는 도달하지 못했습니다.
그간 학습자들이 추상화에 관해 보거나 따라했던 것은 결국 "무언가 코드를 분리하는 행위"였습니다. 원리를 이해하지 못한 채 피상적으로 따라 하는 수준에 그쳤고, 추상화에 대한 올바른 멘탈 모델을 형성하는 데 실패한 것입니다.
왜 이런 일이 벌어질까요? 코드를 분리했는데 왜 복잡도는 그대로일까요? 그들에게 없는 것은 무엇일까요?

2. 멘탈모델이 없기 때문이다

대부분의 추상화 교육은 좋은 추상화의 정의나 결과에 대해 다루는 데서 그칩니다.
"추상화란 공통점을 뽑아내는 것이다" "추상화란 불필요한 세부사항을 숨기는 것이다"
이러한 정의는 완성된 추상화가 무엇인지는 설명하지만, 어떻게 추상화를 만드는지는 가르쳐주지 않습니다.
린다 플라워는 『글쓰기의 문제해결전략』에서 이렇게 말합니다.
"나는 작문을 기존의 결과 중심의 수사학에서 과정 중심의 접근방법으로 전환하여 연구하기로 하였다. 잘 조직된 글은 어떠해야 하는지를 정의내리는 대신에 어떻게 글을 구성해 가는지에 대한 과정을 보여주고자 하였다."
추상화 교육도 마찬가지입니다. "좋은 추상화가 무엇인지"보다 "어떻게 추상화 사고를 하는지"를 가르쳐야 합니다.
더 큰 문제는, 많은 교육이 귀납적 방식만 사용한다는 것입니다. 많은 사례를 보여주고, 학습자가 스스로 패턴을 발견하고, 일반 원칙을 도출하길 기대합니다. 하지만 이 방식은 외생적 인지부하가 너무 높습니다. 초보자는 "무엇이 중요한지"조차 모르는 상태에서 수많은 사례를 분석해야 하니까요.
학습자에게 부족한 것은 경험이 아닙니다. 명시적인 멘탈모델입니다. 사고의 프레임워크가 없으면, 아무리 많은 사례를 봐도 패턴을 발견할 수 없습니다.
그렇다면 어떤 멘탈모델이 필요할까요? 추상화를 가능하게 하는 사고의 틀은 무엇일까요?

3. 우리의 멘탈모델: 작동 가능한 프레임워크

우리는 추상화에 대한 명시적 멘탈모델을 직접 전달합니다. 막연한 "공통점 찾기"가 아니라 구체적이고 작동 가능한 프레임워크를 제시하는 것이죠.

핵심: What과 How의 분리

첫 번째 멘탈모델: 추상화는 What과 How의 분리다
코드의 목적(What)과 세부 구현(How)을 분리하여, 사용하는 측에는 필요한 정보만 노출하고, How를 변경하더라도 외부로 변경이 전파되지 않도록 하는 것입니다.
이것만으로도 충분히 구체적이지만, 우리는 "왜"까지 명확히 합니다.
두 번째 멘탈모델: 변경의 마찰을 줄인다 (점착성)
추상화가 왜 중요한가? 우리는 점착성(Viscosity)이라는 개념으로 설명합니다. 점착성이란 코드를 변경할 때 느끼는 "끈적끈적함", 즉 변경이 얼마나 번거롭고 어려운가를 나타냅니다.
목적(What)은 비교적 잘 바뀌지 않고, 세부 구현(How)은 목적에 비해 자주 바뀝니다. What과 How가 명확히 분리되면, How가 자주 바뀌어도 영향 범위가 작고, What이 바뀌더라도 변경 범위가 명확합니다.
점착성이 높으면 마치 갯벌에 발목이 빠진 것처럼 한 걸음을 떼려면 온 몸에 힘을 줘야 하고, 자칫 발버둥 칠 수록 더 깊이 빨려들어갈 수 있게 되어요. 그러니 코드 퀄리티는 "귀찮아서 나중에"가 되기 쉬운거죠. 기술 부채가 쌓이더라도, 점착성이 낮으면 올바른 방법으로 고치는 것이 쉬워 코드 품질이 유지됩니다.

도구: Wishful Thinking - 사고의 순서

멘탈모델을 알았다면, 이제 그것을 어떻게 적용할까요?
세 번째 멘탈모델: Wishful Thinking
우리는 SICP에서 검증된 Wishful Thinking 사고법을 가르칩니다. "이런 게 있으면 좋겠다(What)"를 먼저 상상하고, "어떻게 만들지(How)"는 나중에 생각하는 것입니다.
초보자는 요구사항을 보자마자 "useState 써야지, useEffect로 fetch하고..."라며 구현(How)부터 생각합니다. 전문가는 "제품 목록 보여주는 게 본질이네. 그럼 이런 게 있으면 좋겠다"라며 목적(What)부터 생각합니다.
Wishful Thinking은 이 전문가의 사고 순서를 명시적으로 가르칩니다.

확장: 전이와 계층

네 번째 멘탈모델: 일상에서 프로그래밍으로 (능력의 전이)
추상화는 새로운 능력이 아닙니다. 사람들은 이미 일상에서 추상화를 합니다.
커피를 주문할 때 "아메리카노 주세요"라고 말하지, 원두가 3번 선반에 있는지 머신 온도가 93도인지 말하지 않습니다. 택시를 탈 때 "강남역으로"라고 하지, 경로를 상세히 지시하지 않습니다.
Prat(2020) 연구가 밝혔듯, 프로그래밍 학습은 수학보다 언어 학습에 가깝습니다. 외국어를 배울 때 어휘(단어)를 배우고 문법(조합)을 배우듯, 추상화도 어휘(추상화 패턴: List, Form, Dialog)를 배우고 문법(조합 방법: IoC, Composition)을 배웁니다.
우리의 전략은 학습자가 이미 가진 일반 추론 능력과 언어 능력을 프로그래밍 영역으로 전이시키는 것입니다.
다섯 번째 멘탈모델: What과 How는 계층적이다 (의도의 재귀)
처음 배울 때 학습자들이 가장 헷갈려하는 부분이 있습니다. "요구사항에 적힌 건 다 What인가요?" 아닙니다. 요구사항 자체도 본질(What)과 세부사항(How)이 섞여 있습니다.
예를 들어 아래와 같은 요구사항이 있다고 가정해봅시다.
확인 버튼을 누르면, 입력된 정보를 저장하고 alert를 띄운 뒤, 3초 뒤에 특정 경로로 페이지 이동
표면적으로는 많은 것을 요구하지만, 본질은 "입력된 정보 저장 후 사용자에게 피드백하고 다음 단계로"입니다. "alert", "3초", "특정 경로"는 세부사항입니다.
이 세부사항은 다시 다음 계층의 What이 됩니다. alert를 보여주는 방식도 여러 가지가 있을 테니까요.
상위 계층의 How가 하위 계층의 What이 되는 것, 이것을 우리는 "의도의 재귀"라고 부릅니다. 추상화는 단일 레벨이 아니라 계층적으로, 재귀적으로 작동한다는 것을 이해하는 게 핵심입니다.
이 5가지 멘탈모델이 왜 효과적일까요? 다른 곳에서는 왜 이렇게 하지 않을까요?

4. 왜 효과적인가: 과학적 근거와 선택의 차이

30년 연구가 뒷받침하는 방법

이 방식의 효과성은 30년 이상의 인지과학 연구로 뒷받침됩니다.
Sweller(1988)의 Worked Examples: 원칙을 먼저 주고 완성된 예시를 보여주는 것이 귀납적 발견보다 초보자에게 효과적
Chi(1981)의 전문가 인식 연구: 전문가는 표면이 아닌 깊은 구조(근본 원리)로 문제를 범주화
Wiedenbeck(1985)의 Top-down 사고 연구: 전문가는 What(목적)부터 생각
Prat(2020)의 언어 능력 전이 연구: 프로그래밍 학습은 언어 학습과 유사
이 연구들이 모두 우리의 접근을 지지합니다.

그런데 왜 많은 곳에서 이렇게 하지 않을까요?

이것은 틀림과 옳음의 문제가 아니라 교육 철학과 실행 역량의 차이입니다.
첫째, 교육 철학의 차이
발견 학습(Discovery Learning)을 중시하는 곳이 많습니다. "스스로 깨달아야 진짜 배운다"는 믿음이죠. 이것이 나쁜 건 아닙니다. 다만 초보자가 충분한 스키마 없이 발견하려 하면 외생적 인지부하가 너무 높아진다는 문제가 있을 뿐입니다.
우리는 직접 교수(Direct Instruction)와 연습을 결합합니다. 먼저 명확한 멘탈모델을 제공하고, 그 위에서 다양한 케이스를 경험하게 하는 것이 더 효율적이라고 믿습니다.
둘째, 명확한 교육 프레임워크의 부재
"추상화가 뭐예요?"라고 물었을 때 "음... 공통점을 뽑아내는 거?"라고 답한다면, 그것을 명시적으로 가르치기 어렵습니다. 교수자 본인도 막연하면 체계적으로 전달할 수 없습니다.
우리는 먼저 코치들의 멘탈모델을 명확히 정립합니다. "What/How 분리", "점착성", "의도의 재귀" 같은 명시적 개념들을 코치들이 먼저 완전히 이해합니다. 그래야 학습자에게 명확하게 전달할 수 있고, 학습자의 사고 과정을 정확히 평가하고 피드백할 수 있습니다.
셋째, 이론을 실천으로 구체화하는 역량
인지과학 분야의 수많은 연구가 효과적인 학습 방법을 밝혀냈지만, 그것이 실제 교육 현장까지 전파되는 데는 시간이 걸립니다. 많은 교육자들이 이런 연구를 접할 기회가 없거나, 알더라도 실제 커리큘럼으로 구체화하는 방법을 모릅니다.
우리는 연구를 읽고 끝내지 않습니다. Sweller의 Worked Examples 이론을 "일반해 어휘 사전"으로, Chi의 깊은 구조 인식을 "요구사항 분해"로, Prat의 언어 능력 전이를 "커피 주문 비유"로 구체화합니다. 이론을 실천 가능한 교육 콘텐츠로 바꾸어 전달하는 것이죠.
그렇다면 이 교과서를 통해 여러분은 무엇을 얻게 될까요?

5. 여러분의 변화

교과서를 읽기 전, 여러분의 모습

코드 리뷰에서 이런 대화가 오갑니다.
"이 컴포넌트 너무 복잡한 것 같아요. 분리해볼까요?" "어디를 어떻게요?" "음... 일단 함수로 빼볼까요?"
기획자가 말합니다. "여기에 버튼 하나만 추가해주세요." 여러분은 ‘가슴 철렁함’, ‘또 어디가 깨질지 모른다는 불안감’에 휩싸이며 컴포넌트 파일을 열어봅니다. 코드를 넘나들며 props를 추가하고, 조건문을 추가하고, 여러 사용처를 확인합니다. 간단한 요청이 반나절 작업이 됩니다.
복잡한 코드 앞에서 "뭔가 이상한데"라는 느낌은 있지만, 정확히 무엇이 문제인지 설명할 수 없습니다. "분리하면 나아지지 않을까?"라는 막연한 기대로 함수를 만들고 컴포넌트를 쪼개지만, 복잡도는 사라지지 않고 단지 다른 곳으로 이동했을 뿐입니다.

교과서를 읽은 후, 여러분의 모습

같은 코드 리뷰에서 이렇게 말합니다.
"이 컴포넌트는 단일 책임 원칙을 위반하고 있어요. 상품 표시와 장바구니 로직이 얽혀 있습니다. 제어권을 외부로 넘기면 어떨까요?"
동료가 묻습니다. "제어권을 넘긴다는 게 무슨 뜻이죠?"
여러분은 구체적으로 설명할 수 있게 됩니다.
"ProductCard가 장바구니 버튼을 직접 알지 않게 만드는 겁니다. bottomActions로 슬롯만 제공하고, 무엇을 넣을지는 사용하는 쪽에서 결정하게 하죠. 그러면 새 버튼이 추가되어도 ProductCard는 수정하지 않아도 됩니다."
기획자의 요청에 여러분은 사용처 한 곳만 열어 버튼 컴포넌트를 추가합니다. ProductCard는 건드리지 않습니다. 10분이면 끝납니다.
복잡한 코드를 보면 "여기는 시점 이동이 많네요. 관련된 것들이 멀리 떨어져 있어요"라고 진단합니다. "변경의 단위로 묶어서 인라인하면 얽힘이 보일 거예요"라고 개선 방향을 제시합니다.

이 교과서의 학습 방식

각 챕터는 동일한 구조로 진행됩니다.
먼저 구체적인 문제 상황을 살펴봅니다. 컴포넌트에 props가 계속 추가되는 상황, 필터링 로직이 여기저기 흩어진 상황. 여러분이 실무에서 겪는 바로 그 상황입니다.
다음으로 여러분이 이미 해봤을만한 일반적인 해결 시도들을 살펴봅니다. config 객체로 묶기, 컴포넌트 분리하기, 조건 분기 추가하기. 이것들이 왜 근본적 해결이 아닌지 확인합니다.
원칙을 배웁니다. IoC란 무엇인가, 왜 제어권을 넘기는가, 어떤 문제를 해결하는가. 단순한 정의가 아니라, 실제 코드에서 어떻게 작동하는지 여러 예시를 통해 봅니다.
이제 연습합니다. 같은 코드를 보고 초보자는 "children props를 쓰네"라고 하고, 숙련자는 "제어권을 외부로 넘겼네"라고 말합니다. 이 차이를 인식하는 훈련을 합니다. 코드 스멜을 맡는 연습을 합니다.
마지막으로 실무에 적용합니다. 스스로의 코드에서 "뭔가 답답한 인터페이스"를 찾아 판단 기록 템플릿을 작성합니다. "요약 / 상황 / 판단 / 근거 / 질문"을 정리하고 코치에게 피드백을 받습니다. 이 과정을 반복하며 판단 능력이 형성됩니다.
과학적 근거에 기반한, 유효하고 효과적인 교육. 이것이 토스 프론트엔드 챕터의 교육 철학이자 방향성입니다.
우리는 있으면 좋은 "vitamin"이 아니라, 반드시 문제를 해결하는 필요한 "painkiller"로서의 교육을 제공하고 싶습니다. 막연한 조언 대신 명확한 방법을, 추상적인 원칙 대신 작동 가능한 프레임워크를, 귀납적 발견 대신 체계적 학습을 제공하고자 합니다.