함수형 코딩에서는 4개의 계층형 설계 패턴을 소개한다. 직접 구현, 추상화 벽, 작은 인터페이스, 편리한 계층 중 이번 글에서는 직접 구현에 대해 다루려고 한다.
직접 구현 패턴이 뭘까?
앞서 간단히 소개한대로, 직접 구현은 계층형 설계 패턴의 한 종류이다. 하지만 챕터8을 읽으며 "왜 직접 구현이라고 했을까?"라는 의문이 계속 들었다. 전체적으로 읽으며 정리해본 결론은 다음과 같다.
- 함수가 수행하는 작업을 코드에서 직접적으로 확인할 수 있다.
- 추상화가 따로 없기 때문에 코드의 의도가 명확하게 보인다.
- 함수 내부에서 모든 로직을 직접 제어하는 것이 가능하다.
호출 그래프를 통한 계층 구분
호출 그래프를 통해 함수의 계층들을 시각화함으로써, 직접 구현 패턴이 잘 지켜지고 있는지를 점검할 수 있다. 코드를 구성하는 함수들이 일관된 구체화 단계를 가지고 있는지를 확인하고 이를 개선할 수 있게 하는 역할을 한다. 계층형으로 설계를 함으로써 특정 구체화 단계에 집중할 수 있게 된다. 비즈니스 로직에 비즈니스 규칙 외에 for문과 같이 언어 내장 함수가 들어가있다면 불필요하게 코드가 구체화되는 것처럼, 각 함수에서 몰라도 되는 부분들을 잘 나눌 수 있게 하는 설계라고 보면 된다.
// 언어 내장 함수 계층 (가장 하위)
// 이 계층은 언어에서 제공하는 map, filter, reduce 등의 함수를 사용합니다.
// 유틸리티 함수 계층
function deepCopy(obj) {
return JSON.parse(JSON.stringify(obj));
}
function formatPrice(price) {
return `₩${price.toFixed(2)}`;
}
function isValidItem(item) {
return item && item.id && item.price > 0;
}
function calculateTax(amount, taxRate = 0.1) {
return amount * taxRate;
}
// 데이터 변환 계층
function findItemById(items, id) {
return items.find(item => item.id === id);
}
function updateQuantity(cart, itemId, newQuantity) {
return cart.map(item =>
item.id === itemId
? {...item, quantity: newQuantity}
: item
);
}
function applyDiscount(items, discountRate) {
return items.map(item => ({
...item,
price: item.price * (1 - discountRate)
}));
}
function sumPrices(items) {
return items.reduce((total, item) =>
total + (item.price * item.quantity), 0);
}
// 비즈니스 규칙 계층
function addItemToCart(cart, inventory, itemId, quantity = 1) {
const item = findItemById(inventory, itemId);
if (!isValidItem(item)) {
throw new Error("유효하지 않은 상품입니다.");
}
const existingItem = findItemById(cart, itemId);
if (existingItem) {
return updateQuantity(cart, itemId, existingItem.quantity + quantity);
} else {
return [...cart, {...deepCopy(item), quantity}];
}
}
function calculateTotal(cart, discountCode) {
let discountRate = 0;
if (discountCode === "SAVE20") {
discountRate = 0.2;
} else if (discountCode === "SAVE10") {
discountRate = 0.1;
}
const discountedItems = discountRate > 0
? applyDiscount(cart, discountRate)
: cart;
const subtotal = sumPrices(discountedItems);
const tax = calculateTax(subtotal);
return {
items: discountedItems,
subtotal: subtotal,
tax: tax,
total: subtotal + tax
};
}
// 고수준 비즈니스 규칙 계층 (가장 상위)
function updateShoppingCart(state, action) {
switch (action.type) {
case "ADD_ITEM":
const newCart = addItemToCart(
state.cart,
state.inventory,
action.itemId,
action.quantity
);
return {
...state,
cart: newCart,
totals: calculateTotal(newCart, state.discountCode)
};
// 다른 액션 처리...
default:
return state;
}
}
위 코드는 아래처럼 호출 그래프를 그릴 수 있다. 호출 그래프의 계층은 주관적인 부분이 들어갈 수 있기 때문에 계층을 나누는 기준은 사람마다 차이가 있을 수 있다. 말하고자 하는 것은, 호출 그래프를 통해 현재 코드의 계층을 시각화 함으로써 여러 계층을 넘나드는 코드가 있는지를 점검해볼 수 있다는 것이다. 만약 addItemToCart에서 제일 아래 계층에 있는 함수를 호출하거나 여러 계층의 코드를 모두 사용하는 등 계층의 일관성이 떨어질수록 직접 구현 패턴에 맞지 않는 코드가 된다.

함수 줌 레벨을 통한 직접 구현 패턴 점검 방법
하지만 처음부터 전체를 다 그리는 것은 어떻게 보면 불가능할 수 있다고 생각한다. 때문에 실무에 도입을 고민하게 된다면 함수 줌 레벨부터 접근할 수 있다고 생각한다. 함수 줌 레벨이란, 함수 하나와 바로 아래 연결된 함수들을 볼 수 있는 단계이다. 전체 코드에 대한 계층을 확인하는 것이 아니라 하나의 함수에 대해서만 점검하기 때문에, 만약 함수 안에서의 계층 단계가 복잡하게 얽혀있다고 하면 리팩터링을 할 수 있을 것이다. 이렇게 부분적으로 시작했을 때, 보다 점진적인 개선이 가능하다고 생각한다.
결론
직접 구현 파트를 읽으며 나만의 일관된 기준을 세우는 방법에 대한 고민이 들었다. 책에서는 각자의 관점으로 사용하면서 습득한 부분이기 때문에, 만약 내가 설계한 계층이 도움이 되지 않는다면 수정하고, 또 다른 사람과도 바꿔서 보라고 얘기한다. 하지만 예시를 보고 직접 계층을 나눠보면서도 아직까지는 확신이 서지 않는 부분이 있다. 이러한 부분은 뒤에 여러 설계 방법도 확인해보고 계층화하는 연습을 여러번 해야 습득할 수 있을 것 같다는 생각이 든다.
'개발' 카테고리의 다른 글
[함수형 코딩] 데이터 불변성을 위한 카피온라이트, 방어적 복사 (3) | 2025.03.26 |
---|---|
[시나브로 자바스크립트] History API와 SPA 라우팅 (3) | 2025.03.21 |
[함수형 코딩] 더 나은 액션 만들기 (2) | 2025.03.19 |
[시나브로 자바스크립트] Monorepo란? (5) | 2025.03.14 |
[함수형 코딩] 액션, 계산, 데이터란? (5) | 2025.03.12 |