1. 한 문장
추상화란, 번잡한 세부사항(How)에 가려진 본질(What)을 드러내는 행위예요.
•
본질은 다른 것으로 대체할 수 없는 것이에요.
•
세부사항은 다른 것으로 대체해도 목적이 달성되는 것이고요.
알람시계에서 "특정 시간에 알려준다"는 대체할 게 없어요. 그게 본질이에요. 소리 대신 진동, 진동 대신 빛으로 바꿔도 알람은 알람이에요. 그게 세부사항이에요.
2. 추상화, 왜 필요한가
초보자 작업기억: [변수명][함수길이][중복][책임분리][에러처리][포맷][남 눈치] (7/7)
→ 꽉 참, 고차원 사고 불가
전문가 작업기억: [코드스멜][남 눈치][][][][][][] (2/7)
→ 여유 있음, "이 설계가 비즈니스에 맞나?" 생각 가능
Plain Text
복사
단기기억의 용량 제약 때문이에요. 인간이 한번에 저글링할 수 있는 개념의 개수는 겨우 5개 남짓이에요. 기왕 저글링을 할 거라면 작은 공보다는 큰 공을 저글링하는 게 효율적이에요. 추상화를 통해서만 인간은 더 고도화된 사고가 가능해지고, 어려운 문제에 도전할 수 있어요.
추상화는 다른 능력에 함께 작용하며 기존 역량들의 상방을 높여주는 메타 능력이기도 해요. 추상화 능력이 낮은 사람과 높은 사람이 "함수를 작성한다"는 똑같은 행위를 해도 질적으로 다른 결과가 나와요. 전자는 구현 세부사항에 매몰되고, 후자는 좀 더 깊은 구조 뒤에 숨은 근본적인 의미를 끌어내어 정의해요. 행위는 같은데 그 행위가 조직되는 깊이와 해상도가 달라요.
3. 추상화 멘탈 모델 소개
앞에서 추상화가 뭔지, 본질이 뭔지, 왜 필요한지 알았어요. 이제 실제로 어떻게 하는지 볼게요.
여기서부터는 전문가가 추상화할 때 머릿속에서 돌아가는 모델을 옮겨왔어요. 크게 세 부분이에요:
•
입력값: 추상화의 재료
•
연산: 어떤 단계를 거치는가 (3단계)
•
출력값: 추상화의 결과
그리고 연산 중에 만질 수 있는 세 가지 파라미터가 있어요: 이름, 경계, 축척.
입력값: 추상화의 재료
추상화를 시작할 때 손에 들고 있는 것들이에요. 코드, 회의 안건, 커리어 선택지 등 뭐든 대상이 될 수 있어요. 그리고 그 대상에는 보통 이미 누군가 그어놓은 추상화 경계가 있어요. 파일이 나뉘어 있거나, 함수가 쪼개져 있거나, "이건 A팀 일이고 저건 B팀 일"처럼 구분이 되어 있거나요.
프론트엔드에서는 코드만 보면 안 돼요. 요구사항(기획서)과 디자인(Figma 등)을 함께 봐야 해요. 요구사항과 디자인이 코드보다 먼저 존재하고, 코드는 이것들을 구현한 결과예요. 기획서가 SSOT(Single Source of Truth)예요. 변경의 단위도 기획서에서 정해져요.
파라미터: 추상화 과정에서 조작할 수 있는 변수
세 가지를 만질 수 있어요. 그 중 이름이 가장 중요해요.
•
이름: 추상화는 언어 능력이에요. 핵심 행위가 '이름 붙이기'이기 때문이에요. 세계는 무한에 가깝게 복잡하고 인간은 유한해서, 세계를 '있는 그대로' 다룰 수 없어요. 대신 다룰 수 있는 '손잡이'가 필요하고, 그게 이름이에요.
◦
이름을 붙이는 순간 의미가 창발해요. item.price * 0.9는 그냥 연산이에요. 여기에 applyDiscount라는 이름을 붙이는 순간 "할인을 적용한다"는 의미가 생겨나요. 이전에는 없던 의미예요. 이름이 만들어낸 거예요.
◦
좋은 추상화는 결국 좋은 이름을 통해 달성돼요. 이름이 요구사항의 의도를 정확히 전달하고, 약속을 철저하게 지킬 때요.
•
경계: 이미 그어진 추상화 경계를 지울 수도 있고, 새로 그을 수도 있어요. 파일이나 함수로 나뉘어 있던 걸 다시 합치는 게 경계를 지우는 거고, 코드 덩어리에 이름을 붙여서 분리하는 게 경계를 새로 긋는 거예요.
•
축척: 같은 대상을 얼마나 높이서 보느냐예요. 서울을 위성에서 보면 점 하나고, 지하철 노선도로 보면 역들의 연결이고, 골목길 지도로 보면 건물 하나하나가 보여요. 코드도 "이 파일은 뭘 하는 거지?" 수준에서 볼 수도 있고, "이 줄은 왜 있는 거지?" 수준에서 볼 수도 있어요.
연산: 어떤 단계를 거치는가
세 단계예요.
1단계 해체. 이미 그어진 추상화 경계를 일단 지워요. 파일 나눈 거 다시 합치고, 함수 쪼갠 거 인라인해요. 기존 구조를 의심하고 펼쳐놓는 단계예요.
2단계 모음. 펼쳐놓은 것들을 실제로 쓰이는 곳 근처로 옮겨요. 멀리 떨어져 있으면 맥락이 안 보여서 본질을 발견하기 어려워요.
3단계 발견 (오작교 방법론). 두 방향에서 동시에 움직여요.
•
위에서 내려오기: 요구사항에서 출발해요. 자연어로 "이렇게 쓰고 싶다"를 먼저 적어요. 코드가 아니라 요구사항 기반이어야 해요. 코드에서 출발하면 잘못된 추상화 경계를 만들기 쉬워요. (이때 요구사항 자체가 제대로 정제되지 않아서 본질이나 고객에게 전달하고자 하는 가치가 잘 드러나지 않는다면 요구사항 쪽을 먼저 다듬어야 해요. 'Garbage In, Garbage Out' 원칙에 따라 요구사항이 정제되지 않으면 코드도 이상하게 나올 확률이 높아요.)
// 요구사항: "전자제품은 10% 할인하여 총액을 계산한다"
// 자연어로 먼저 적음
장바구니에서
전자제품만 골라서
10% 할인을 적용하고
합산한다
Plain Text
복사
•
아래에서 올라가기: 세부사항에서 출발해요. 가장 안쪽, 가장 작은 단위부터 세부사항이 끼어들 여지, 인터페이스 구멍을 막으며 올라와요. "이건 뭐 때문에 있는 거지?"를 물으면서 이름을 붙여요. 이름을 붙일 때 요구사항 언어를 써요. 이름이 붙는 순간 세부사항이 끼어들 구멍이 막혀요.
// 시작: 구멍 투성이
for (let i = 0; i < cart.items.length; i++) {
if (cart.items[i].category === 'electronics') {
total += cart.items[i].price * 0.9
}
}
// 구멍 막기: category === 'electronics'가 뭐야?
// → 요구사항 언어로: "전자제품인지"
const isElectronics = (item) => item.category === 'electronics'
// 구멍 막기: price * 0.9가 뭐야?
// → 요구사항 언어로: "할인을 적용한다"
const applyDiscount = (item, { rate }) => item.price * (1 - rate)
// 구멍 막기: += 누적이 뭐야?
const sum = (a, b) => a + b
TypeScript
복사
•
만남: 자연어로 적었던 게 실제 코드로 전부 대체되면 끝이에요.
const electronicsTotal = cart.items
.filter(isElectronics)
.map(item => applyDiscount(item, { rate: 0.1 }))
.reduce(sum, 0)
TypeScript
복사
"장바구니 아이템 중 전자제품만 골라서, 각각에 10% 할인 적용하고, 합산해서 전자제품 총액을 구한다"
그 자리에서 바로 읽혀요. 시점 이동 없음.
매 결합부마다 점검할 것:
•
글처럼 자연스럽게 읽히는가? (구현이 아닌 의미 위주의 읽기, 세부사항을 읽느라 숨차지 않을 것)
•
이름이 요구사항을 정확히 표현하는가? (지적인 정직함)
•
뻔해서 "뭔소리야?"가 안 나오는가? (놀람 최소화의 원칙)
•
같은 층에 있는 것들이 비슷한 추상화 수준으로 말하고 있는가? (같은 축척 내 추상화 수준 맞추기)
프론트엔드에서 발견의 기준은 화면이에요. UI와 코드의 인지적 덩어리가 1:1이 되어야 해요. 화면에서 하나로 보이는 게 코드에서도 하나여야 해요. 기획자가 "이 부분 바꿔주세요"라고 하면 해당 코드를 바로 찾을 수 있어야 해요.
프론트엔드에서는 이미 합의된 이름들이 있어요. Form, List, Input, Dialog, Toggle 같은 것들이요. 새로 발명하지 말고 기존 이름에 매칭해요. "이건 결국 Form이네"라고 알아채는 순간 이름이 바로 붙어요.
출력값: 추상화의 결과
본질이 드러난 상태예요. 머릿속에서 대상이 두 파티션으로 나뉘어 있는 상태:
•
본질(What) 파티션: 이게 뭔지 알려주는 이름이 붙어 있고, 안정적이에요
•
세부사항(How) 파티션: 변경이 오면 여기서 처리해요. 추상화가 잘 되었다면 세부사항이 바뀌어도 본질이 흔들리지 않아야 해요
이 상태가 되면 대상을 다루기가 쉬워져요. 이름만 보고도 뭔지 예측할 수 있고, 변경이 와도 어느 파티션 일인지가 바로 보여요.
프론트엔드에서 출력이 잘 됐는지 확인하는 기준은 놀람 최소화예요. 코드를 읽는 사람이 "어? 이게 왜 여기 있지?"라고 느끼면 안 돼요. <Button onClick={...}>을 보면 뭘 하는지 바로 알잖아요. 그 수준으로 뻔해야 해요.
점검 렌즈
파라미터 세 가지(이름, 경계, 축척)가 잘 조작됐는지 확인하는 렌즈가 있어요.
이름 점검 — 언어적 계약
이름은 요구사항에 대한 약속이에요. 인터페이스라는 개념도 결국 핵심은 언어적 계약이에요. "이 함수를 호출하면 이런 일이 일어난다"는 약속이요. 구현이 그 약속을 지키는지 확인해요.
// 약속보다 적게 함: 이름이 과장
const getOrderSummary = (order) => order.items.length
// summary라면서 개수만 반환함
// 약속보다 많이 함: 이름에 없는 일을 함
const formatPrice = (price) => {
trackPriceView(price) // 이름에 없는 분석 호출
return `${price.toLocaleString()}원`
}
// 약속대로 함: 놀람 없음
const formatPrice = (price) => `${price.toLocaleString()}원`
TypeScript
복사
경계 점검 — 응집/결합
추상화 경계를 긋는다는 건 "이 경계 바깥에서는 안쪽을 몰라도 된다"고 결정하는 거예요.
// 잘못된 경계: 바깥에서 안쪽 구조를 알아야 함
function CartPage() {
const { data: items } = useQuery(cartItemsQuery())
const electronicsTotal = items
.filter(item => item.category === 'electronics')
.reduce((sum, item) => sum + item.price * 0.9, 0)
return <div>{electronicsTotal}</div>
}
// 좋은 경계: 그 자리에서 읽힘
function CartPage() {
const { data: items } = useQuery(cartItemsQuery())
const electronicsTotal = items
.filter(isElectronics)
.map(item => applyDiscount(item, { rate: 0.1 }))
.reduce(sum, 0)
return <div>{electronicsTotal}</div>
}
TypeScript
복사
경계가 잘 그어졌으면 안쪽은 응집이 높고(할인 계산 로직이 한 곳에 모여 있음), 바깥과의 결합은 낮아요(함수 이름만 알면 됨). 경계가 잘못 그어졌으면 안쪽을 건드리려면 바깥도 고쳐야 하고, 바깥을 건드리면 안쪽이 터져요.
축척 점검 — 같은 층의 이웃들이 비슷하게 말하는가
같은 함수 안에서 호출되는 것들이 비슷한 추상화 수준인지 봐요.
// 안 맞음: 혼자 수다스러움
function processOrder(order) {
validateOrder(order)
const total = order.items.reduce((sum, item) => sum + item.price * 0.9, 0)
submitOrder(order, { total })
}
// 맞음: 다 비슷하게 과묵함
function processOrder(order) {
validateOrder(order)
const total = order.items
.map(item => applyDiscount(item, { rate: 0.1 }))
.reduce(sum, 0)
submitOrder(order, { total })
}
TypeScript
복사
수다스러움 신호:
•
사칙연산, 하드코드된 값, 배열 인덱스
•
구체적 조건 (=== 'electronics')
•
비교 연산자, 속성 체이닝
•
내부를 봐야 의도를 아는 메서드 (reduce, split, slice)
•
인라인 콜백 안에 위 신호가 있으면 세부사항이 파고든 것
선언적 인터페이스는 인터페이스 구멍이 작아요. for (let i = 0; i < items.length; i++)는 인덱스 초기화, 조건, 증감, 배열 접근까지 전부 구멍이에요. 수다스러워지기 쉬워요. for (const item of items)는 item 변수 하나뿐이라 구멍이 적어요. 마찬가지로 items.map(item => ...)은 화살표 안에 뭐든 넣을 수 있어서 인터페이스 구멍이 크고, items.map(applyDiscount)처럼 함수 이름만 넘기면 구멍이 막혀 있어서 인터페이스 구멍이 커요.
4. 트리거: 언제 이 모델을 꺼내는가
"이게 뭔소리야?" 또는 "아 머리아파" — 이 감각이 신호예요.
초보자와 전문가 모두 같은 신호를 느껴요. 차이는 타이밍과 단위예요.
초보자는 이미 복잡해진 코드 앞에서 느껴요.
"뭔소리야... 일단 돌아가니까 넘어가자." 나중에 추상화를 시도할 때는 큰 단위로 한번에 잘하려고 해요. 여러 가지가 뒤섞이고, 실패할 이유가 많아져요. 요리 초보가 번잡한 주방에서 요리 내내 고통받고, 설거지도 마지막에 한번에 다 하려다 지치는 것처럼요.
전문가는 복잡해지기 직전에 신호를 느껴요.
"지금은 맥락이 있어서 읽히는데, 다음에 돌아오면 못 읽겠다." 작은 비용으로 당장 시작해서 금방 끝내요. 실패할 수 없는 작은 단위로 하고, 기회는 여러 번 있다고 생각해요. <냉장고를 부탁해> 같은 방송에서 볼 수 있듯 훈련 받은 셰프가 요리 중간중간 작은 정리와 청소를 습관적으로 해서 계속 깔끔한 환경을 유지하고, 마지막 설거지 양도 적은 것처럼요.
신호는 같아요. 반응하는 단위와 타이밍이 달라요.
5. 모델의 오작동을 방지하려면
추상화가 아닌 것
이 모델을 받은 사람이 흔히 빠지는 함정들이 있어요. 공통점은 하나예요. 본질을 발견하는 과정 없이 추상화의 결과만 얻으려는 것.
•
분리: 경계를 긋는다고 본질이 드러나지 않아요. 파일을 나누고 함수를 쪼개도 "이건 뭐 때문에 있는 거지?"라는 질문 없이 했다면 추상화는 일어나지 않은 거예요. 분리는 발견의 결과일 수 있지만, 분리 자체가 발견은 아니에요.
•
은닉: 감추는 건 치워놓는 것이지 해결이 아니에요. 복잡한 걸 다른 파일로 옮겨서 눈앞에서 안 보이게 했다고 본질이 드러난 게 아니에요. 나중에 그 파일을 열면 똑같이 복잡해요.
•
일반화: 반복에서 패턴을 찾는 건 발견의 한 방법이지만, 반복이 없으면 추상화를 안 해도 된다는 뜻은 아니에요. 단일 대상에서도 "이건 뭐 때문에 있는 거지?"를 물을 수 있어요.
•
지식 나열: SOLID, 응집도, 결합도... 이런 원칙들을 안다고 추상화를 할 수 있는 게 아니에요. 이 원칙들은 추상화가 잘 된 코드의 상태를 설명하는 도구일 뿐, 그 상태에 어떻게 도달하는지는 알려주지 않아요.
•
경험 누적: 시간이 지나면 자연히 늘 거라는 생각이요. 80살 노인이 평생 칫솔질을 했다고 치과의사보다 칫솔질을 잘하지 않아요. 의도적인 연습 없이 반복만 해서는 안 늘어요.
•
거대한 설계: 처음부터 완벽한 구조를 미리 정하는 건 추상화가 아니에요. 추상화는 만들면서 발견해나가는 과정이에요. 코드가 없는 상태에서 구조를 그리는 건 추측이지 발견이 아니에요.
•
시간 핑계: "시간이 없어서 추상화를 잘 못했다"는 생각이 들면, "시간을 무한대로 주면 잘할 수 있을까?" 자문해봐요. 그때는 지금보다 얼마나 더 잘하게 될까요? 그건 왜 잘한 추상화일까요? 시간이 문제가 아닐 수 있어요.
코드 밖에서 먼저 연습해야 하는 이유
이 모델은 코드에만 적용되는 게 아니에요. 일상에서 먼저 익숙해지면 코드에서도 자연스럽게 발휘돼요.
•
회의 안건을 넣으면: 흩어진 논점들을 모으고, "이 회의는 결국 뭘 결정하려는 거지?"를 묻고, 핵심 의제에 이름을 붙여요.
•
커리어 선택지를 넣으면: 기자 → 부캠 멘토 → 프론트엔드 개발자 → 코치. 표면만 보면 커리어가 마구 점프한 것 같아요. 근데 "뭐가 반복되고 뭐가 달라지지?"를 물으면 다르게 보여요. 반복되는 건 "나와 내가 관계하는 사람들이 직장에서 주체적으로 생존하도록 돕는다"는 목표예요. 달라지는 건 직종이에요. 본질이 유지되면 세부사항은 얼마든지 갈아끼울 수 있어요. 직종 전환이 두렵지 않은 이유가 여기 있어요.
코드로 연습하면 안 되는 이유
첫째, 연습 안 할 핑계가 많아져요. 회의가 많을 때는 회의가 많아서 못하고, 팀 매니징 업무를 맡고 있으면 코드가 없어서 못하고, 코드가 있어도 건드리면 안 되거나 난이도가 높아서 못해요.
둘째, 코드 추상화는 [코드[[추상]화]]예요. 세 층위가 복합적으로 얽힌 행위예요:
1.
추상이 뭔지에 대한 이해
2.
추상화를 수행하는 단계에 대한 이해
3.
그것을 코드 맥락에 적용하는 것
훈련할 때는 한 번에 하나씩 집중해야 해요. 세 층위를 뒤섞어서 접근하면 훈련 난이도가 높아지고, 실패했을 때 뭐가 문제인지 파악하기 어려워요.







