# Chapter05. 더 좋은 액션 만들기


# 1. 암묵적 입력과 출력은 적을수록 좋다

  • 인자가 아닌 모든 입력은 암묵적 입력이고 리턴값이 아닌 모든 출력은 암묵적 출력이다.
  • 계산 뿐만 아니라 액션에서도 암묵적 입력과 출력을 줄이면 좋다.
  • 어떤 함수에 암묵적 입력과 출력이 있다면 다른 컴포넌트와 강하게 연결된 컴포넌트라고 할 수 있다.
    • 다른 곳에서 사용할 수 없기 때문에 모듈이 아니다.
    • 이런 함수의 동작은 연결된 부분의 동작에 의존한다.
  • 암묵적 입력과 출력을 명시적으로 바꿔 모듈화된 컴포넌트로 만들 수 있다.
  • 암묵적 입력과 출력이 있느 함수는 아무 때나 실행할 수 없기 때문에 테스트하기 어렵다.
    • 모든 입력값을 설정하고 테스트를 돌린 후에 모든 출력값을 확인해야 한다.
    • 입력과 출력이 많을수록 테스트는 더 어려워진다.
  • 계산은 암묵적 입력과 출력이 없어 테스트하기 쉽다.
  • 모든 암묵적 입력과 출력을 없애지 못해 액션을 계산으로 바꾸지 못해도 암묵적 입력과 출력을 줄이면 테스트하기 쉽고 재사용하기 좋다.

# 2. 의미 있는 계층으로 나누기

  • 분류 기준
    • C: cart에 대한 동작
    • I: item에 대한 동작
    • B: 비즈니스 규칙
    • A: 유틸리티 함수
  • 현재 코드
var shopping_cart = [];

function add_item_to_cart(name, price) {
  shopping_cart = add_item(shopping_cart, name, price);
  var total = calc_total(shopping_cart);
  set_cart_total_dom(total);
  update_shipping_icons(shopping_cart);
  update_tax_dom(total);
}

function add_item(cart, name, price) { // C, I
  var new_cart = cart.slice();
  new_cart.push({
    name, price
  });
  return new_cart;
}

function calc_total(cart) { // C, I, B
  var total = 0;
  for (var i = 0; i < cart.length; i += 1) {
    var item = cart[i];
    total += item.price;
  }
  return total;
}

function update_shipping_icons(cart) {
  var buttons = get_buy_buttons_dom();
    
  for (var i = 0; i < buttons.length; i += 1) {
    var button = buttons[i];
    var item = buttom.item;
    var new_cart = add_item(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) { // B
  return calc_total(cart) >= 20;
}

function update_tax_dom(total) {
  set_tax_dom(calc_tax(total));
}

function calc_tax(amount) { // B
  return amount * 0.10;
}

# 3. 함수가 잘 분리되어있을 때 좋은 점

  • 재사용하기 쉽다.
    • 함수는 작으면 작을수록 재사용하기 쉽다.
    • 하는 일도 적고 쓸 때 가정을 많이 하지 않아도 된다.
  • 유지보수하기 쉽다.
    • 작은 함수는 쉽게 이해할 수 있고 유지보수하기 쉽다.
    • 코드가 작기 때문에 올바른지 아닌지 명확하게 알 수 있다.
  • 테스트하기 쉽다.
    • 작은 함수는 테스트하기 좋다.
    • 한 가지 일만 하기 때문에 한 가지만 테스트하면 된다.
    • 함수에 특별한 문제가 없어도 꺼낼 것이 있다면 분리하는 것이 좋다.
    • 그렇게 하면 더 좋은 설계가 된다.

# 4. 리팩토링

# (1) add_item()

  • 기존 add_item 함수는 cartitem 구조를 모두 알고 있어 item에 관한 코드를 별도의 함수로 분리한다.
// item 구조만 알고 있음
function make_cart_item(name, price) {
  return {
    name,
    price,
  };
}

// cart 구조만 알고 있음
// 이 함수 구현체의 경우 카피-온-라이트를 구현한 부분이므로 함께 두는 것이 좋다.
function add_item(cart, item) {
  var new_cart = cart.slice();
  new_cart.push(item);
  return new_cart;
}

// 호출하는 부분 수정
function add_item_to_cart(name, price) {
  shopping_cart = add_item(shopping_cart, make_cart_item(name, price));
  // 생략
}
  • 위와 같이 수정하면 cartitem을 독립적으로 확장할 수 있다.
  • 예를 들어 배열인 cart를 해시 맽 같은 자료 구조로 바꾼다고 할 때 변경해야 할 부분이 적다.

# (2) 카피-온-라이트 패턴 빼내기

  • 유틸리티 함수의 성격을 띠는 함수의 경우 함수 이름과 인자 이름을 더 일반적인 이름으로 바꾼다.
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);
}

# (3) 개선된 코드에서 계산 다시 분류해보기

function add_element_last(array, elem) { // A
  var new_array = array.slice();
  new_array.push(elem);
  return new_array;
}

function add_item(cart, item) { // C
  return add_element_last(cart, item);
}

function make_cart_item(name, price) { // I
  return {
    name,
    price,
  };
}

function calc_total(cart) { // C, I, B
  var total = 0;
  for (var i = 0; i < cart.length; i += 1) {
    var item = cart[i];
    total += item.price;
  }
  return total;
}

function gets_free_shipping(cart) { // B
  return calc_total(cart) >= 20;
}

function calc_tax(amount) { // B
  return amount * 0.10;
}


# ❔ 비즈니스 규칙과 장바구니 기능의 차이

  • 장바구니는 대부분의 전자상거래 서비스에서 사용하는 일반적인 개념이다.
    • 그리고 장바구니가 동작하는 방식도 모두 비슷하다.
  • 하지만 비즈니스 규칙은 다르다.
    • 이 책에 나와 있는 예시를 보면 MegaMart에서 운영하는 특별한 규칙이라고 할 수 있다.
  • 예를 들어 다른 전자상거래 서비스에도 장바구니 기능이 있을 것이라고 기대하지만, MegaMart와 똑같은 무료 배송 규칙이 있을 것이라고 기대하지는 않는다.
  • 그리고 지금 시점에서는 비즈니스 규칙과 장바구니에 대한 동작이 모두 속한 경우도 있지만 이 부분은 사실 코드에서 나는 냄새다.
  • 비즈니스 규칙은 장바구니 구조와 같은 하위 계층보다 빠르게 바뀐다.
  • 설계를 진행하면서 이 부분은 분리해야 한다. 추후 뒤에서 더 자세히 살펴보자.


# 5. (p.104) 연습 문제

function update_shipping_icons(cart) {
  var buy_buttons = get_buy_buttons_dom();
    
  for (var i = 0; i < buy_buttons.length; i += 1) {
    var button = buy_buttons[i];
    var item = buttom.item;
    var is_available_free_shipping = check_available_free_shipping(cart, item);
    set_show_free_shipping_icon(is_available_free_shipping);
  }
}

function check_available_free_shipping(cart, item) {
  var new_cart = add_item(cart, item);
  return get_free_shipping(new_cart);
}

function set_show_free_shipping_icon(is_free_shippping) {
  if (is_free_shippping) {
    button.show_free_shipping_icon();
  } else {
    button.hide_free_shipping_icon();
  }
}

# ❗️ 정리

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