5장. 더 좋은 액션 만들기

  • 비즈니스 요구 사항과 설계 맞추기 : 요구 사항에 맞춰 더 나은 추상화 단계 선택하기
  • 비즈니스 요구 사항과 함수 맞추기
  • 암묵적 입력과 출력 줄이기 → 암묵적 입력과 출력이 있는 함수는 아무 때나 실행할 수 없기 때문에 테스트하기 어렵다. ← 모든 입력값을 설정하고 테스트를 돌린 후 모든 출력값을 확인해야 하기 때문

add_item 함수 개선하기

1. 계산을 분리해 더 좋은 설계 만들기

cart에 대한 동작 / item에 대한 동작 / 비즈니스 규칙에 대한 함수인지 표시해보기

C  I
function add_item(cart, name, price) {
    var new_cart = cart.slice(); // 배열을 복사한다.
    new_cart.push( // 복사본에 item을 추가한다.
		{ // item 객체를 만든다.
        name: name,
        price: price
    });
    return new_cart; // 복사본을 리턴한다.
}

📍 설계는 엉켜있는 코드를 푸는 것이다.

  • 재사용하기 쉽다
  • 유지보수하기 쉽다
  • 테스트하기 쉽다 (관심사가 분리된 함수이기 때문)

⇒ cart와 item으로 이루어진 함수에서, item에 관한 코드를 별도의 함수로 분리

function add_item(cart, item) {
  var new_cart = cart.slice();
  new_cart.push(item);
  return new_cart;
}
 
function make_cart_item(name, price) {
  return {
    name: name,
    price: price,
  };
}
 
add_item(shopping_cart, make_cart_item("shoe", 3.45));

2. 카피-온-라이트 패턴을 빼내기

function add_item(cart, item) {
  const new_cart = cart.slice();
  new_cart.push(item);
  return new_cart;
}

기존 계산 코드는 장바구니를 넘기는 상황에서만 쓸 수 있는 것처럼 보임.

⇒ 함수 이름과 인자 이름을 더 일반적인 이름으로 바꾸기

function add_item(cart, item) {
  return add_element_last(cart, item);
}
 
function add_element_last(array, elem) {
  var new_array = array.slice();
  new_array.push(elem);
  return new_array;
}

이렇듯 어떤 배열이나 항목에도 쓸 수 있는, 재사용할 수 있는 함수를 유틸리티 함수라고 부름

update_shipping_icons 함수 개선하기

function update_shipping_icons(cart) {
  var buy_buttons = get_buy_buttons_dom();
  for (var i = 0; i < buy_buttons.length; i++) {
    var button = buy_buttons[i];
    var item = button.item;
    const new_cart = add_item(cart, make_cart_item("shoes", 3.45));
    if (gets_free_shipping(new_cart)) button.show_free_shipping_icon();
    else button.hide_free_shipping_icon();
  }
}

1. 함수가 하는 일 작성 및 분류하기

  • 모든 버튼 가져오기 ← 구매하기 버튼
  • 버튼을 가져오고 반복하기 ← 구매하기 버튼
  • 버튼에 관련된 제품 가져오기 ← 구매하기 버튼
  • 가져온 제품을 가지고 새 장바구니 만들기 ← cart와 item
  • 장바구니가 무료 배송이 필요한지 확인하기 ← cart와 item
  • 아이콘 표시하거나 감추기 ← DOM 관련 동작

2. 관심사에 따라 함수 분리하기

function update_shipping_icons(cart) {
  // 구매하기 버튼 관련 동작
  var buy_buttons = get_buy_buttons_dom();
  for (var i = 0; i < buy_buttons.length; i++) {
    var button = buy_buttons[i];
    var item = button.item;
 
    var hasFreeShipping = gets_free_shipping_with_item(cart, item);
 
    set_free_shipping_icon(button, hasFreeShipping);
  }
}
function gets_free_shipping_with_item(cart, item) {
  // cart와 item 관련 동작
  var new_cart = add_item(cart, item);
  return gets_free_shipping(new_cart);
}
function set_free_shipping_icon(button, isShown) {
  // DOM 관련 동작
  if (isShown) button.show_free_shipping_icon();
  else button.hide_free_shipping_icon();
}
  • 개선된 전체 코드
    /** 액션 */
    // 장바구니 구현하기
    var shopping_cart = [];
    var shopping_cart_total = 0;
     
    function add_item_to_cart(name, price) {
      var item = make_cart_item(name, price);
      shopping_cart = add_item(shopping_cart, item);
      var total = calc_total(shopping_cart);
      set_cart_total_dom(total);
      update_shipping_icons(shopping_cart);
      update_tax_dom(total);
    }
     
    function set_cart_total_dom(total) {
      // ...
      total;
      // ...
    }
     
    // 무료 배송비 계산하기
    function update_shipping_icons(cart) {
      // 구매하기 버튼 관련 동작
      var buy_buttons = get_buy_buttons_dom();
      for (var i = 0; i < buy_buttons.length; i++) {
        var button = buy_buttons[i];
        var item = button.item;
     
        var hasFreeShipping = gets_free_shipping_with_item(cart, item);
     
        set_free_shipping_icon(button, hasFreeShipping);
      }
    }
    function gets_free_shipping_with_item(cart, item) {
      // cart와 item 관련 동작
      var new_cart = add_item(cart, item);
      return gets_free_shipping(new_cart);
    }
    function set_free_shipping_icon(button, isShown) {
      // DOM 관련 동작
      if (isShown) button.show_free_shipping_icon();
      else button.hide_free_shipping_icon();
    }
     
    // 세금 계산하기
    function update_tax_dom(total) {
      set_tax_dom(calc_tax(total));
    }
     
    /** 계산 */
    function add_item(cart, item) {
      return add_element_last(cart, item);
    }
     
    function add_element_last(array, elem) {
      var new_array = array.slice();
      new_array.push(elem);
      return new_array;
    }
     
    function make_cart_item(name, price) {
      return {
        name: name,
        price: price,
      };
    }
     
    function calc_total(cart) {
      var total = 0;
      for (var i = 0; i < cart.length; i++) {
        var item = cart[i];
        total += item.price;
      }
      return total;
    }
     
    function gets_free_shipping(cart) {
      return calc_total(cart) >= 20;
    }
     
    function calc_tax(amount) {
      return amount * 0.1;
    }

keyPoint

  • 일반적으로 암묵적 입력과 출력은 인자와 리턴값으로 바꿔 없애는 것이 좋다.
  • 설계는 엉켜있는 것을 푸는 것이다. 언제든 다시 합칠 수 있다.
  • 엉켜있는 것을 풀어 각 함수가 하나의 일만 하도록 하면, 개념을 중심으로 쉽게 구성할 수 있다.