
소프트웨어 설계: 코드를 만들고 테스트하고, 유지보수하기 쉬운 프로그래밍 방법을 선택하기 위해 미적 감각을 사용하는 것
추상적 계층: 구체적인 코드에서 멀어지고 무엇을 할지만 보여주는 상위 레벨의 설계 단위이다.
소프트웨어 설계를 위한 미적 감각을 키우기 위해서는 계층형 설계라는 것을 사용하면 좋다고 책에서 제안하고 있다.
계층형 설계(stratified design)란?
책에서는 소프르웨어를 계층으로 구성하는 기술로,
각 계층에 있는 함수는 바로 아래 계층에 있는 함수를 이용해 정의하는 방법 이라고 설명하고 있다.
이렇게만 읽으면 물음표 같으니 쉽게 설명하면,
"복잡한 문제를 처리할 때는 한번에 처리 하지 않고, 의미 단위로 계층을 나눠서 설계하자"는 것이다.
🔹 그래서 계층은 어떻게 나누는데?
Low-Level | 아주 구체적인 동작 단위의 함수, 날짜 포맷, 숫자 계산, 필터 조건 함수 등 |
Mid-Level | 저수준 함수를 조함해 하나의 기능적인 흐름, 데이터 필터링, 매핑, 정렬 등 리스트 가공 로직 |
High-Level | 서비스 전체 흐름 (유저 조회 → 필터링 → 통계 생성 → 리턴) |
대부분의 실무에서는 위 처럼 3단계로 구분하고,
계층 구조는 높아질수록 점점 추상적인 계층으로 올라가면서 나누어진다.
책에서 제시한 계층은 4단계인데, 함수형 설계 원칙을 실제 코드에 자연스럽게 반영한 계층 구분이다.
: 언어 수준 → 불변성 전략 → 기능 단위 → 정책 흐름
각 계층은 정확하게 구분하긴 어렵기 때문에 상황에 맞춰 구분을 하는 것이 좋다고 한다.
계층형 설계의 패턴 4가지
이번 챕터에서는 4가지 중 직접 구현에 대한 부분만 작성할 예정이다.
1️⃣ 직접 구현 (Direct Implementation)
하위 함수들을 상위 함수 안에서 직접 조합해 사용하는 방식
- 중간 계층 없이, 작은 함수들을 상위 흐름 안에 직접 배치
- 단순한 흐름에서 매우 유용
- 구조는 단순하지만 흐름이 명확
arr.filter(isEven).map(square).reduce(sum);
2️⃣ 추상화 벽 (Abstraction Barrier)
하위 계층의 세부 구현을 숨기고, 상위 계층에서는 그 의미만 사용하도록 추상화
- 하위 로직이 변경되어도 상위 계층엔 영향 없음
- 계층 간 의존성 명확화 + 변경에 강한 구조
const getActiveUsers = (users) => users.filter(isActive);
// 상위 계층에서는 getActiveUsers만 사용
3️⃣ 작은 인터페이스 (Narrow Interface)
함수나 계층이 사용하는 데이터와 기능 범위를 최소화
- 필요 이상으로 많은 데이터를 넘기지 않음
- 의존성을 줄이고, 테스트와 재사용이 쉬움
// ❌ 나쁜 예 - 전체 user 전달
updateUser(user);
// ✅ 좋은 예 - 필요한 값만 전달
updateUserName(user.id, user.name);
4️⃣ 편리한 계층 (Convenient Layer)
자주 쓰이는 조합을 하나의 함수로 묶어서 다른 계층에서 더 쉽게 쓸 수 있게 만든 계층
- 반복되는 로직을 한 계층으로 모아 재사용성과 가독성 증가
- 일종의 “헬퍼 계층” 역할
const prepareCartData = (cart) => {
return {
totalPrice: calcTotal(cart),
itemCount: cart.length,
hasDiscount: checkCoupon(cart),
};
};
직접 구현 패턴(direct implementation)
작은 단위의 함수들을 상위 로직 안에서 직접 조합하여 흐름을 구성하는 방식
📌 예시
// 넥타이 하나를 사명 무료로 넥타이 클립 하나를 주는 코드
function freeTieClip(cart){
const hasTie = false
const hasTieClip = false;
for(let i = 0; i < cart.length; i++) {
const item = cart[i];
if (item.name === "tie")
hasTie = true;
if (item.name === "tie clip")
hasTieClip = true;
}
if (hasTie && !hasTie Clip) {
const tieclip = make_item("tie clip", 0);
return add item(cart, tieClip);
}
return cart;
}
위 코드는 설계를 하지 않은채 그냥 기능을 추가한 코드이다.
이런식의 코드는 유지보수가 어렵다.
📌 수정된 예시
function freeTieClip(cart){
const hasTie = isInCart(cart, "tie"),
const hasTieClip = isInCart(cart, "tie clip"),
if (hasTie && !hasTie Clip) {
const tieclip = make_item("tie clip", 0);
return add item(cart, tieClip);
}
return cart;
}
// 장바구니에서 제품을 확인하는 반복문을 하나의 함수로 만들어 사용
function isInCart(cart, name) {
for(let i = 0; i < cart.length; i++) {
if (cart[i].name === name)
return true;
}
return false;
}
🔹 호출 그래프를 만들어 호출 시각화
함수들이 서로 어떻게 호출하고 연결되어 있는지를 시각적으로 보여주는 구조도로,
계층형 설계가 잘 되어있는지 확인하는 도구로 활용된다

처음 계층에 대한 설계 없이 만들어진 freeTieClip 함수의 호출 그래프이다.
언어기능 레벨과 함수 호출 레벨이 동시에 그려진걸 볼 수 있다 그래서 수정하면

각 기능에 맞춰 그래프가 수정되었다.
하지만 문제가 있는데, 한 함수에서 서로 다른 추상화 단계를 사용하면 코드가 명확하지 않아 읽기 어렵다.
🔹 즉 함수 간의 계층이 섞이면 코드가 쉽게 꼬이고, 유지보수가 어려워지기 때문이다.
특히 직접 구현에서는 서로 다른 추상화 단계에 있는 기능을 사용하지 말아야 된다고 설명하고 있는데
우리가 위에서 수정했던 코드가 모두 이 규칙을 지키기 위해서 였다.

새로 수정된 예시에 대한 호출 그래프는 더 이상 다른 추상화 단계에 있는 기능을 사용하지 않게 되었다.
💡
코드가 돌아가는 것 만큼,
어떤 계층에서 어떤 역할을 담당하는지 설계하는 것도 중요한 것 같다.
결국은 유지보수를 위해 고민한 설계의 흔적이 코드를 오래도록 살아남게 만드는 것 같다.
'FE > BOOK' 카테고리의 다른 글
[함수형코딩]WEEK5 계층형 설계- 추상화의 벽, 작은 인터페이스, 편리한 계층 (0) | 2025.04.08 |
---|---|
[함수형코딩]WEEK3 불변성 이야기 (2) | 2025.03.29 |
[함수형코딩]WEEK2 함수형 사고: 더 좋은 액션 만들기 (1) | 2025.03.19 |
[함수형 코딩] WEEK1 액션, 계산, 데이터 (2) | 2025.03.12 |
[HTTP 완벽가이드] 18장 웹 호스팅 (0) | 2025.02.10 |