0장. 왜 추상화인가
개발자로 일하다 보면 한 번쯤 이런 생각이 들어요.
“코드가 복잡해서 함수도 쪼개고, 파일도 나눴는데… 나는 추상화 한거 같은데 왜 코드가 여전히 복잡하지?”
혹은 이런 말을 하거나 들어본 적 있을 거예요.
“시간이 없어서 추상화를 못 해.”
사실 이건 거꾸로예요. “추상화를 안 해서 시간이 없는” 거예요. 같은 패턴을 여러 번 만들고, 비슷한 버그를 반복해서 고치고, 작은 수정인데 여러 파일을 뒤져야 하는 시간. 그 시간이 쌓여서 “시간이 없다”가 돼요. 추상화는 거창한 설계가 아니에요. 반복되는 고통을 줄이는 일상적인 기술이에요.
처음에는 코드를 작게 만들면 될 줄 알았어요. 함수가 길면 나누고, 파일이 커지면 분리하고. 그런데 막상 해보면 오히려 더 복잡해지는 경험을 해요. 작게 나눈 조각들이 서로 얽히고, 한 곳을 고치면 다른 곳이 깨지고, 전체 흐름을 파악하기가 더 어려워져요.
혹시 코드 리뷰에서 이런 피드백을 받아본 적 있나요?
“이 부분 추상화가 부족한 것 같아요. 좀 더 정리해볼 수 있을까요?”
무슨 말인지는 알 것 같은데, 구체적으로 뭘 어떻게 해야 할지 막막해요. 클래스를 더 만들라는 걸까요? 인터페이스를 뽑으라는 걸까요? 아니면 함수를 더 일반적으로 만들라는 걸까요?
이 책은 바로 그 막막함에서 시작해요.
역량 성장의 병목
개발 역량은 단계적으로 성장해요. 처음에는 문법을 익히고, 그다음에는 라이브러리 사용법을 배우고, 점점 더 큰 시스템을 다루게 돼요. 그런데 어느 순간, 성장이 멈춘 것 같은 느낌이 들어요.
코드를 더 많이 짜도, 새로운 기술을 배워도, 뭔가 근본적인 부분이 나아지지 않는 것 같아요. 이 병목의 정체가 무엇일까요?
많은 경우, 그 병목은 ‘무엇을 어떻게 나눌 것인가’에 대한 사고방식이에요.
코드를 작성하는 건 어렵지 않아요. 요구사항대로 동작하게 만드는 것도 시간을 들이면 돼요. 하지만 그 코드가 “좋은 구조”를 가지려면, 단순히 동작하는 것 이상의 무언가가 필요해요. 그 무언가가 바로 추상화예요.
추상화가 잘 안되어 있으면 어떤 일이 벌어질까요?
•
반복적인 수정: 비슷한 기능을 추가할 때마다 여러 곳을 고쳐야 해요.
•
예측 불가능한 버그: 한 곳을 고쳤는데 다른 곳이 깨져요.
•
설명하기 어려운 코드: “이게 뭐 하는 코드야?”라는 질문에 한 문장으로 답하기 어려워요.
•
확장의 어려움: 새로운 요구사항이 들어오면 기존 코드를 크게 뜯어고쳐야 해요.
이 모든 문제의 공통점이 있어요. 코드가 ’지금 당장 필요한 것’에만 맞춰져 있다는 점이에요.
당장 필요한 것만 보면, 그때그때 동작하는 코드는 만들 수 있어요. 하지만 그 코드는 “지금”에 갇혀 있어요. 조금만 요구사항이 바뀌면 처음부터 다시 작성해야 하는 코드가 돼요. 반면 추상화가 잘 된 코드는 달라요. 지금 필요한 것을 해결하면서도, 미래의 변화를 수용할 여지를 남겨둬요. 이것이 가능한 이유는 ’지금’과 ’본질’을 분리했기 때문이에요.
프론트엔드의 특수성
이 책은 프론트엔드 개발자를 위해 쓰여졌어요. 프론트엔드에는 추상화를 더 어렵게 만드는 특수한 상황들이 있어요.
빠르게 변하는 요구사항
프론트엔드는 사용자와 직접 맞닿아 있어요. 사용자 피드백이 바로 요구사항 변경으로 이어지고, A/B 테스트 결과에 따라 UI가 수시로 바뀌어요. 어제 만든 컴포넌트가 오늘 완전히 다른 모습이 되기도 해요.
이런 환경에서 “변하지 않는 본질”을 찾아내는 것은 쉽지 않아요. 하지만 바로 그렇기 때문에 더 중요해요. 변화가 잦을수록, 변하는 것과 변하지 않는 것을 분리하는 능력이 필요해요.
다양한 상태의 조합
프론트엔드 코드는 수많은 상태를 다뤄요. 로딩 중인지, 에러가 났는지, 데이터가 있는지, 사용자가 로그인했는지, 권한이 있는지… 이 상태들이 조합되면 경우의 수가 폭발적으로 늘어나요.
// 상태가 늘어날수록 조건문이 복잡해져요
if (isLoading) return <Spinner />;
if (error) return <ErrorMessage error={error} />;
if (!data) return <EmptyState />;
if (!user) return <LoginPrompt />;
if (!user.hasPermission) return <PermissionDenied />;
return <Content data={data} />;
TypeScript
복사
이런 코드는 상태가 하나만 추가되어도 모든 조합을 다시 검토해야 해요.
UI와 로직의 얽힘
프론트엔드에서는 “보이는 것”과 “동작하는 것”이 한 곳에 섞이기 쉬워요.
function ProductCard({ product }) {
const [quantity, setQuantity] = useState(1);
const handleAddToCart = () => {
// 비즈니스 로직
if (product.stock < quantity) {
alert("재고가 부족합니다");
return;
}
const price = product.price * quantity;
const discountedPrice = user.isVIP ? price * 0.9 : price;
// API 호출
addToCart({ productId: product.id, quantity, price: discountedPrice });
// UI 업데이트
setQuantity(1);
showToast("장바구니에 담았습니다");
};
return <div className="card">{/* UI 코드 */}</div>;
}
TypeScript
복사
어디까지가 “이 컴포넌트의 책임”인지 불분명해요. 가격 계산 로직이 바뀌면? 재고 체크 방식이 바뀌면? 모두 이 컴포넌트를 수정해야 해요.
그래서 추상화가 더 필요해요
프론트엔드의 이런 특수성 때문에, 오히려 추상화 역량이 더 중요해요.
•
변화가 잦으니 → 변하지 않는 것을 찾아야 해요
•
상태가 많으니 → 상태를 다루는 패턴이 필요해요
•
관심사가 섞이기 쉬우니 → 명확하게 분리하는 기준이 필요해요
이 책에서 다루는 추상화는 프론트엔드에 특화된 것은 아니에요. 하지만 모든 예제는 프론트엔드 맥락에서 진행돼요.
왜 잘 안 되는가
그런데 코드 추상화가 잘 안 되는 이유가 있어요. 코드에서 바로 시작하면 안 되기 때문이에요.
“코드 추상화”라는 단어를 보면, 자연스럽게 코드에 집중하게 돼요. 함수를 어떻게 나눌지, 클래스를 어떻게 설계할지, 어떤 패턴을 적용할지. 하지만 이건 표면만 보는 거예요.
“코드 추상화”의 구조를 뜯어보면 이래요:
[코드 [[추상] 화]]
코드 추상화
└── 코드 + 추상화
└── 추상 + 화(化)
Plain Text
복사
코드는 가장 바깥 레이어예요. 우리가 실제로 작성하는 것이에요. 추상화는 그 안에 있어요. 추상을 만드는 과정이에요. 추상은 가장 안쪽에 있어요. 인간이 가진 인지 능력이에요.
문제는 대부분 바깥(코드)만 보고 안쪽(추상)은 보지 않는다는 거예요.
함수를 쪼개고, 파일을 나누고, 패턴을 적용해도 여전히 복잡한 이유가 여기 있어요. 형식만 바꿨을 뿐, 안쪽의 “추상”이라는 인지 능력을 제대로 발휘하지 않았기 때문이에요.
전문가와 초보자의 가장 큰 차이는 “지식의 양”이 아니에요. 지식의 구조예요.
초보자는 개별 사실들을 따로따로 기억해요. “이 상황에서는 이렇게, 저 상황에서는 저렇게.” 전문가는 개별 사실들 뒤의 패턴을 봐요. “결국 이건 다 같은 원리야.”
코드 추상화를 직접 배우면, 개별 패턴(함수 추출, 인터페이스, 전략 패턴…)을 따로따로 익히게 돼요. 하지만 “추상”이라는 원리를 먼저 이해하면, 모든 패턴이 같은 원리의 변형임을 알게 돼요.
원리를 알면 응용할 수 있어요. 원리를 모르면 외워야(!) 해요. 외우고 훈련하면 원리를 이해하고, 누구든 응용할 수 있게 돼요.
이 책의 접근법
이 책은 안쪽(추상)부터 다뤄요.
장 | 제목 | 내용 |
1장 | 추상이란 | 가장 안쪽. What과 How가 분리된 상태. |
2장 | 추상화란 | 중간. 추상을 만드는 과정. 환원과 재구성. |
3장 | 일상에서 연습하기 | 코드 없이 추상/추상화 수련. |
4장 | 코드 추상화 | 추상을 코드에 적용. 구조(레이어, 협력, 축척). |
5장 | 사례와 함정 | 실무 패턴과 피해야 할 것. |
6장 | 심화 사례 | 언어 초월 추상화, 플랫폼 흡수. |
왜 이 순서일까요? 코드 없이도 추상/추상화를 연습할 수 있기 때문이에요.
추상은 코드에만 쓰이는 게 아니에요. 알람 시계를 볼 때, 레스토랑에서 주문할 때, 길을 설명할 때 — 우리는 이미 추상을 사용하고 있어요. 다만 의식하지 않을 뿐이에요.
이 책에서는 먼저 추상이라는 상태(What과 How의 분리)가 무엇인지 정의해요. 그리고 그 상태를 만들어내는 과정(추상화)을 배워요. 일상에서 연습한 다음, 마지막에 코드라는 도메인에 적용해요.
추상화는 인간 본질적인 능력이자 지혜예요. 1637년 데카르트는 이렇게 썼어요. “문제를 풀고 싶다면, 먼저 그것이 풀렸다고 가정하라.” 고대 그리스 수학자 파푸스로부터 영향 받은 이 사고방식은 수학자 폴리아의 ’문제 해결 휴리스틱’으로, 다시 SICP의 ’Wishful Thinking’으로 이어졌어요. 우리가 배우게 될 코드 추상화는 단지 인간에게 내재된 오래된 지혜를 코드에 적용하는 것일 뿐이에요.
이 책의 목적
이 책의 목적은 명확해요.
“추상화해”라는 피드백을 받았을 때, 구체적으로 무엇을 해야 하는지 알게 되는 것.
이를 위해 이 책은 세 가지를 제공해요.
첫째, 추상화가 무엇인지 이해하기. 추상화는 막연한 개념이 아니에요. “복잡한 걸 단순하게”도 아니고, “공통점을 뽑아내는 것”도 완전한 설명이 아니에요. 이 책에서는 추상을 ‘What과 How의 분리’로 정의해요. 이 정의가 왜 유용한지, 그리고 실제 코드에서 어떻게 적용되는지 차근차근 설명해요.
둘째, 추상화하는 방법 익히기. 이해하는 것과 할 수 있는 것은 달라요. 이 책은 추상화를 연습 가능한 절차로 만들어요. 클린(환원 → 재구성 → 검증)과 저크(구조 전이) 방식을 다뤄요. 처음에는 이 절차를 의식적으로 따라가야 해요. 마치 운전을 처음 배울 때 순서를 외우듯이. 하지만 반복하면 자연스러워져요.
셋째, 추상화를 체화하기. 궁극적인 목표는 “추상화를 의식하지 않고도 자연스럽게 하는 것”이에요. 숙련된 개발자가 코드를 볼 때 “이건 분리해야겠다”는 감각이 바로 드는 것처럼, 추상화도 감각이 될 수 있어요. 이 책은 그 과정을 세 단계로 설명해요: 의식적 절차 → 패턴 인식 → 자동화된 지각.
이 책을 읽고 나면
이 책을 다 읽고 나면, 적어도 이런 변화가 있을 거예요.
•
“추상화 해주세요”라는 피드백에 구체적인 다음 단계를 떠올릴 수 있어요.
•
코드가 복잡하게 느껴질 때, “세부사항”과 “본질”을 분리해볼 수 있어요.
•
새로운 패턴을 만났을 때, 같은 원리의 변형임을 알아차릴 수 있어요.
추상화는 재능이 아니에요. 연습으로 익힐 수 있는 기술이에요.
선행 지식
이 책을 읽기 전에 알아야 할 것:
•
함수를 작성할 수 있다
•
클래스/객체 개념을 안다
•
리팩토링이 뭔지 안다
이 정도면 충분해요. 특정 언어나 프레임워크 경험은 필요하지 않아요.
이제 시작해요.
1장에서는 추상이 무엇인지 정의해요.
