본문으로 건너뛰기

액션, 계산, 데이터

액션 : 실행 횟수와 시점에 의존한다. 부수효과라 칭한다.

계산 : 입력으로 출력을 계산한다. 순수함수라 칭한다.

이벤트 : 데이터에 대한 사실이다.

함수형 코딩을 적용한 예시

// 쇼핑물 장바구니 프로그램
var shoppingCart = [];
var shoppingCartTotal = 0;

const add_item_to_card = (item, price) => {
shoppingCart.push({ item, price });
shoppingCartTotal += price;
};

const calc_total_price = () => {
shoppingCartTotal = shoppingCart.reduce(
(total, item) => total + item.price,
0
);

// 돔을 업데이트 하는 함수
//set_cart_total_dom();
// 모든 아이콘 업이트를 위해 업데이트 함수 호출
update_shipping_icon();
// 세금 계산
calc_tax();
};

// 만약 구매 버튼에 배송 아이콘을 표시해야한다면?
const update_shipping_icon = () => {
var shoppingCart_buttons = document.getElementsByClassName(
"shoppingCart_button"
);
for (var i = 0; i < shoppingCart_buttons.length; i++) {
var button = shoppingCart_buttons[i];
var item = button.item;

// 만약 아이템의 가격과 장바구니의 총 가격이 100000원 이상이면 배송 아이콘을 표시
if (item.price + shoppingCartTotal >= 100000) {
button.innerHTML = "배송";
} else {
button.innerHTML = "배송 무료";
}
}
};

// 세금 계산
const calc_tax = () => {
var tax = shoppingCartTotal * 0.1;
return tax;
};

Ⅰ. 기존 코드의 주요 문제점

초기 코드는 전역 변수와 DOM에 직접 의존하여 다음과 같은 문제점을 가집니다.

  • 전역 변수 의존성: shoppingCart, shoppingCartTotal 같은 전역 변수를 여러 함수가 직접 읽고 수정합니다. 이러한 암시적(Implicit) 입출력은 코드의 흐름을 예측하기 어렵게 만듭니다.
  • 책임의 혼합: 비즈니스 로직(예: 총액 계산)과 UI 로직(예: DOM 업데이트)이 하나의 함수 안에 섞여 있습니다.
  • 테스트의 어려움: 전역 상태와 DOM에 직접 의존하기 때문에, 독립적인 단위 테스트(Unit Test) 작성이 매우 까다롭습니다.

Ⅱ. 해결 전략: 액션(Action)과 계산(Calculation) 분리

코드를 두 가지 역할로 명확히 나누어 문제를 해결합니다.

계산 (Calculation)

  • 역할: 데이터를 받아(입력) 새로운 데이터를 만들어 반환(출력)하는 순수한 로직.
  • 특징:
    • 오직 인자(arguments)로만 데이터를 받고, return으로만 결과를 내보냅니다. (명시적 입출력)
    • 외부 상태를 변경하지 않습니다. (No Side Effects)
    • 동일한 입력에 대해 항상 동일한 결과를 반환합니다. (순수 함수)
  • 예시: const add = (a, b) => a + b;

액션 (Action) 🎬

  • 역할: '계산' 함수를 사용하여 얻은 데이터를 가지고 실제 세상에 변경을 일으키는 역할.
  • 특징:
    • 호출 시점이나 횟수에 따라 시스템 상태가 달라집니다.
    • 전역 변수 수정, DOM 업데이트, API 요청 등의 작업을 수행합니다.
  • 예시: document.getElementById('total').innerText = total;

Ⅲ. 리팩토링 예시 코드

1. 상품 추가: add_item

문제: 원본 배열을 직접 수정하여 **불변성(Immutability)**을 해칩니다.

// Before: 원본 배열을 직접 수정하는 '액션'
const add_item_to_card = (item, price) => {
shoppingCart.push({ item, price });
};

// After: 새로운 배열을 반환하는 '계산'
const add_item = (cart, item) => {
const new_cart = [...cart]; // 배열 복사로 불변성 유지
new_cart.push(item);
return new_cart;
};
  1. 총액 계산: calc_total 문제: 계산 로직과 다른 액션 호출이 섞여있습니다.
// Before: 계산과 다른 액션 호출이 섞인 '액션'
const calc_total_price = () => {
shoppingCartTotal = shoppingCart.reduce(
(total, item) => total + item.price,
0
);
update_shipping_icon();
calc_tax();
};

// After: 순수하게 총액만 계산하는 '계산'
const calc_total = (cart) => {
return cart.reduce((total, item) => total + item.price, 0);
};
  1. 배송비 및 세금 계산 문제: 배송비 정책 결정, 세금 계산 로직이 DOM 업데이트와 같은 '액션'과 결합되어 있습니다.
// After: 로직을 '계산'으로 분리

// 무료 배송 여부를 결정하는 '계산'
const gets_free_shipping = (total) => {
return total >= 100000;
};

// 세금을 계산하는 '계산'
const calc_tax = (total) => {
return total * 0.1;
};

// 분리된 계산들을 조합해 DOM을 업데이트하는 '액션'
const update_ui = (cart) => {
const total = calc_total(cart);
const isFree = gets_free_shipping(total);
// ... 이 데이터를 사용해 DOM을 변경하는 코드
};