Skip to content

더 좋은 액션 만들기

액션을 더 잘 다루기 위해서는 추상화 수준을 올바르게 선택하고
암묵적인 입력과 출력을 줄이며, 액션과 계산을 분리하는 습관이 필요합니다.

스터디 회차: 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;
}

문제점

  • 무료 배송 여부를 확인할 때, 장바구니가 아닌 totalitem_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_shippingcart만을 입력받아 무료배송 여부를 판단합니다.
    • 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는 계산이지만 많은 역할을 수행하고 있습니다.
    1. 배열을 복사하기
    2. item 객체 만들기
    3. 복사본에 item 추가하기
    4. 복사본을 리턴하기

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는 어떤 배열이나 항목에도 쓸수 있는 재사용 가능한 계산 함수가 되었습니다.

정리하기

  • 추상화 단계를 올바르게 선택하여 요구사항에 맞는 인자를 받고 중복을 제거해야 합니다.
  • 암묵적인 입력과 출력을 줄이면 더 명확하고 테스트 가능한 코드가 됩니다.
  • 계산을 분리하면 재사용성과 유지보수성이 올라갑니다.