목차10개 항목
출처와 참고
테오(teo)의 「잘하는 개발자가 되는 그라데이션 사고법 (추상화 계층과 데이터 흐름 이해하기)」(2023.06)을 바탕으로 재구성한 노트다.
구현: 요구사항을 데이터로 본다
문법을 다 알아도 테트리스를 못 만드는 단계가 있다. 막히는 이유는 요구사항을 데이터로 표현하는 법을 모르기 때문이다. 판을 2차원 배열로 보면 블록은 0과 1의 작은 배열, 회전은 미리 돌려 둔 배열의 집합, 줄 지우기는 "한 행의 모든 값이 0보다 큰가"가 된다. "불을 켠다"는 isLight = true다. 구현은 요구사항을 데이터와 그 변화로 기술하는 일이다.
그다음 단계의 어려움은 조립이다. 각 기능은 구현했는데 어디에 두고 어떻게 연결할지 몰라 코드가 스파게티가 된다. 이것을 푸는 두 개념이 추상화 계층과 데이터 흐름이다.
추상화: 구현의 반대 방향
추상화(abstraction)는 복잡한 세부를 숨기고 핵심만 남겨 단순하게 만드는 일이다. 반대말인 구현에서 출발하면 쉽다. 구현이 요구사항 → 데이터 방향이라면, 추상화는 데이터 → 사람의 언어 방향이다. 출발점은 구현한 코드에 알맞은 이름을 붙이는 것이다. isLight = !isLight에 toggleLight()라는 이름을 붙이면 다음과 같은 코드가 된다.
- 명세에 가깝고
- 행동을 기술하며
- 선언적이고
- 사람의 개념에 가깝다
| 종류 | 무엇에 이름을 붙이나 | 예 |
|---|---|---|
| 데이터 추상화 | 값 (변수명) | currentDate, firstDate |
| 프로세스 추상화 | 값이 바뀌는 과정 (함수명) | currentDate → firstDate를 startOfMonth()로 |
추상화된 코드는 잘 변하지 않는다. 구현은 바뀌어도 turnOnLight()라는 이름은 요구사항이 바뀌기 전까지 그대로다. 잘 변하지 않는 코드로 조립해야 조립부가 흔들리지 않는다. 이 잘 변하지 않는 경계가 인터페이스이고, 인터페이스가 쌓이면 추상화 계층(abstraction layer) 이 된다.
계층이 생기면 그 아래를 생각하지 않고 더 높은 수준에서 사고한다. 화면은 실제로 픽셀의 2차원 배열이지만 프론트엔드 개발자는 HTML·CSS로, 다시 그 위의 React·Svelte로 생각한다. 좋은 추상화 계층은 더 적은 코드로 더 높은 수준의 사고를 하게 한다.
좋은 추상화의 조건
1. 딱 맞는 이름
추상화는 구현을 숨긴다. 단, 만든 사람에게는 숨겨지지 않는다. 맥락을 모르는 사람은 이름만 보고 안을 추측해야 한다. 협업 상대에는 미래의 나도 포함된다. 그래서 이름만으로 숨은 구현을 예측할 수 있어야 한다. 간결하면서 설명적이고, 계속 지어야 하니 일관돼야 한다. 이름 짓기가 개발에서 가장 어려운 일로 꼽히는 이유다(가독성을 높이는 전략).
2. 알맞은 수준
이름보다 중요한 것이 수준(level) 이다. 한 번에 크게 감싸면 이름에서 안을 예측하기 어렵다. 여러 단계로 나눠 조립한다.
// 구현 그대로: 무엇을 하는지 읽어 내야 한다
const first = new Date(now.getFullYear(), now.getMonth(), 1);
first.setDate(first.getDate() - first.getDay());
// 알맞은 수준으로 나눠 이름 붙이기: 달력 첫 칸 = 이번 달 시작 주의 시작
const calendarStart = startOfWeek(startOfMonth(now));startOf 같은 이름은 재사용되고, 날짜 계산을 더 높은 수준의 개념으로 생각하게 만든다. date-fns·Day.js 같은 라이브러리가 이런 계층이다(날짜 라이브러리: date-fns와 Day.js).
| 특징 | 뜻 |
|---|---|
| 안정성 | 잘 변하지 않아 예측 가능하다 |
| 단순성 | 이해하고 쓰기 쉽다 |
| 목적에 맞는 수준 | 너무 추상적이면 특수한 경우에만 맞고, 덜 추상적이면 여전히 복잡하다 |
| 재사용성 | 비슷한 도메인에 확장해 쓸 수 있다 |
| 가독성·일관성 | 이름만으로 의도와 숨은 구현이 보인다 |
추상화는 점진적으로 만든다. 너무 잘게 쪼개도, 너무 많이 겹쳐도 의미를 잃는다.
데이터 흐름
가장 높은 수준까지 추상화한 계층 구성이 아키텍처다. 소프트웨어의 데이터는 사용자에서 출발해 다시 사용자로 돌아오는 순환을 그린다.
flowchart LR u["사용자"] --> ie["인터페이스(이벤트)"] ie --> bl1["비즈니스 로직"] bl1 --> da1["데이터 접근"] da1 --> data["데이터"] data --> da2["데이터 접근"] da2 --> bl2["비즈니스 로직"] bl2 --> is["인터페이스(화면)"] is --> u
프론트엔드로 옮기면
flowchart LR u["사용자"] --> h["이벤트 핸들러"] h --> s["상태 변경·API 요청"] s --> srv["서버·스토어"] srv --> st["상태"] st --> jsx["JSX 렌더"] jsx --> scr["화면"]
계층만 계층이 아니다. 계층 사이를 잇는 과정도 하나의 계층이다. 이벤트에서 상태 변경으로 가는 구간은 이벤트 핸들러 계층, 상태에서 화면으로 가는 구간은 JSX 계층이다.
그라데이션 사고
추상화는 데이터(저수준)에서 요구사항(고수준)으로 한 방향이고, 데이터 흐름은 순환한다. 이 두 축이 만든 지도 위에서 코드의 자리가 정해진다. 이 관점을 요구사항이 코드로 서서히 바뀌어 가는 그라데이션에 빗대 그라데이션 사고라고 부른다.
1. 생태계를 지도에 놓는다
라이브러리와 프레임워크는 이 지도의 어느 구간, 어느 수준을 차지한다. fetch → axios → TanStack Query는 같은 서버 통신 구간에서 점점 높아지는 추상화 수준이다. 새 도구가 나와도 어느 구간·수준인지 알면 빨리 이해하고 적용한다. 도구가 채우지 않은 빈 구간이 내 코드가 채울 곳이다.
2. 요구사항을 구간으로 쪼갠다
"블록을 회전한다"는 키 입력 이벤트 → 회전 로직 → 데이터 처리 → 상태 변경 → 렌더로 나뉜다. 코드가 커지기 전에 요구사항을 미리 이 구간들로 쪼개 두면 복잡한 문제가 작은 문제로 나뉘고, 연결도 자연스러워진다. 연습은 말로 된 요구사항을 이 틀에 맞는 문장으로 적어 보고, 그 문장과 닮은 코드로 옮기는 것이다.
같은 사고는 코드 밖에도 적용된다. 아이디어를 기획·설계 단계에서 알맞은 수준으로 쪼개 두면 개발에 들어갈 때 이미 구간이 나뉘어 있다.
정리
- 요구사항을 데이터로 표현하면 구현이 된다.
- 구현에 딱 맞는 이름과 알맞은 수준을 주면 추상화가 되고, 잘 변하지 않는 추상화가 쌓여 계층이 된다.
- 데이터는 사용자를 중심으로 계층 사이를 순환하고, 계층을 잇는 과정도 계층이다.
- 추상화 수준과 데이터 흐름의 지도 위에서 코드의 자리가 정해진다. 요구사항을 그 구간들로 쪼개 채운다.
- 알기만 해서는 늘지 않는다. 이름을 지을 때마다, 코드를 둘 때마다 의식적으로 연습한다.
관련 노트: 프론트엔드에서 관심사의 분리, 함수형 프로그래밍, 가독성을 높이는 전략, 프론트엔드에서 의존성 관리하기