더 좋은 액션 만들기
액션을 더 잘 다루기 위해서는 추상화 수준을 올바르게 선택하고
암묵적인 입력과 출력을 줄이며, 액션과 계산을 분리하는 습관이 필요합니다.
스터디 회차: 4회차 (2025년 8월 20일)
1. 더 나은 추상화 단계 선택하기
예제: 무료배송 여부를 확인하여 아이콘을 표시하는 함수
tsx
function update_shipping_icons() {
var buttons = get_buy_buttons_dom();
for (var i = 0; i < buttons.length; i++) {
var button = buttons[i];
var item = button.item;
if (gets_free_shipping(shopping_cart_total, item.price))
button.show_free_shipping_icon();
else button.hide_free_shipping_icon();
}
}
function gets_free_shipping(total, item_price) {
return item_price + total >= 20;
}
function calc_total(cart) {
var total = 0;
for (var i = 0; i < cart.length; i++) {
var item = cart[i];
total += item.price;
}
return total;
}
문제점
- 무료 배송 여부를 확인할 때, 장바구니가 아닌
total
와item_price
으로 확인하고 있습니다. 이는 비즈니스 요구사항과 맞지 않는 인자입니다. total + item_price
을 수행하는 로직이 중복됩니다.
개선하기
- 장바구니 데이터를 기반으로 무료 배송 여부를 판단합니다.
- 계산은
calc_total
를 통해서 수행합니다.
tsx
function update_shipping_icons() {
var buttons = get_buy_buttons_dom();
for (var i = 0; i < buttons.length; i++) {
var button = buttons[i];
var item = button.item;
var new_cart = add_item(shopping_cart, item.name, item.price);
if (gets_free_shipping(new_cart)) button.show_free_shipping_icon();
else button.hide_free_shipping_icon();
}
}
function gets_free_shipping(cart) {
return calc_total(cart) >= 20;
}
- 이제
gets_free_shipping
는cart
만을 입력받아 무료배송 여부를 판단합니다.cart
를 입력받기 위해add_item
를 호출하여new_cart
를 만들어 전달합니다.- 가격 합계는
calc_total
를 이용하여 중복 로직을 제거합니다.
add_item
는 계산함수이므로 다른 액션과 독립적으로 사용할 수 있습니다.
INFO
함수의 동작을 바꾸었기 때문에 리팩토링에 해당하지 않습니다.
2. 암묵적 입력과 출력 줄이기
예제: 장바구니에 아이템을 추가하고 계산하기
tsx
function add_item_to_cart(name, price) {
shopping_cart = add_item(shopping_cart, name, price);
calc_cart_total();
}
function calc_cart_total() {
shopping_cart_total = calc_total(shopping_cart);
update_shipping_icons(shopping_cart);
update_tax_dom();
}
function update_tax_dom() {
set_tax_dom(calc_tax(shopping_cart_total));
}
문제점
shopping_cart
,shopping_cart_total
와 같은 전역 변수를 직접 사용하기 때문에 테스트하기가 어렵습니다.- 함수의 입력과 출력이 암묵적으로 연결되어 있습니다.
암묵적 입력과 출력을 줄여야하는 이유
암묵적 입력과 출력은 다른 컴포넌트와 강하게 연결된 컴포넌트로, 다른곳에서 사용할 수 없기 때문에 모듈이 아닙니다. 또한 아무때나 실행할 수 없기 때문에 테스트하기 어렵게 만듭니다.
개선하기
- 모든 계산에 필요한 값을 인자로 전달합니다.
- 전역 변수는 읽는 곳이 없다면 제거합니다.
tsx
function add_item_to_cart(name, price) {
shopping_cart = add_item(shopping_cart, name, price);
calc_cart_total(shopping_cart);
}
function calc_cart_total(cart) {
var total = calc_total(cart);
update_shipping_icons(cart);
update_tax_dom(total);
shopping_cart_total = total; // 전역변수에 할당했지만 읽는 곳이 없습니다.
}
function update_tax_dom(total) {
set_tax_dom(calc_tax(total));
}
- 함수들은 외부 전역 변수가 아니라 명시적인 입력값에 의존합니다.
shopping_cart_total
에 대입하는 코드는 읽히지 않으므로 제거할 수 있습니다.
WARNING
책에서는 shopping_cart_total
에 대입하는 코드가 없어서 제거해도된다고 적혀있지만,
전역변수이므로 어디서 어떻게 다시 참조될지 모르기 때문에, 할당까지 해야한다고 생각합니다.
암묵적 출력을 줄이기 위해서 명시적으로 리턴하고 반영하는 코드를 적용하면 아래와 같습니다.
tsx
function add_item_to_cart(name, price) {
shopping_cart = add_item(shopping_cart, name, price);
shopping_cart_total = calc_cart_total(shopping_cart);
}
function calc_cart_total(cart) {
var total = calc_total(cart);
update_shipping_icons(cart);
update_tax_dom(total);
return total;
}
function update_tax_dom(total) {
set_tax_dom(calc_tax(total));
}
3. 계산의 책임 분리하기
예제: 장바구니에 아이템 추가하기
tsx
function add_item(cart, name, price) {
var new_cart = cart.slice();
new_cart.push({
name: name,
price: price,
});
return new_cart;
}
문제점
- add_item는 계산이지만 많은 역할을 수행하고 있습니다.
- 배열을 복사하기
- item 객체 만들기
- 복사본에 item 추가하기
- 복사본을 리턴하기
1,3,4는 값을 바꿀때 복사하는 카피-온-라이트를 구현한 부분이기 때문에 함께 두는 것이 좋습니다.
cart와 item 구조를 모두 알고 있어 책임을 분리해야합니다.
개선하기1 : item 생성과 cart에 추가하는 동작 분리
tsx
function make_cart_item(name, price) {
return {
name: name,
price: price,
};
}
function add_item(cart, item) {
var new_cart = cart.slice();
new_cart.push(item);
return new_cart;
}
- item의 생성과 cart에 추가하는 동작을 분리합니다.
- 변경된 구조에서는 cart와 item이 독립적으로 수행됩니다.
개선하기2 : 더 일반적인 이름으로 바꾸기
tsx
function make_cart_item(name, price) {
return {
name: name,
price: price,
};
}
// 함수 이름을 add_item -> add_element_last 변경
function add_element_last(array, elem) {
var new_array = array.slice();
new_array.push(elem);
return new_array;
}
function add_item(cart, item) {
return add_element_last(cart, item);
}
add_item
는 더이상 cart와 item에 특화된 함수가 아니므로 더 일반적인 이름으로 바꿉니다.add_element_last
는 어떤 배열이나 항목에도 쓸수 있는 재사용 가능한 계산 함수가 되었습니다.
정리하기
- 추상화 단계를 올바르게 선택하여 요구사항에 맞는 인자를 받고 중복을 제거해야 합니다.
- 암묵적인 입력과 출력을 줄이면 더 명확하고 테스트 가능한 코드가 됩니다.
- 계산을 분리하면 재사용성과 유지보수성이 올라갑니다.