쏙쏙 들어오는 함수형 코딩 - 4

binary·2022년 5월 22일
3
post-thumbnail

Chapter 4 액션에서 계산 빼내기

🌟 MegaMart

MegaMart는 온라인 쇼핑몰이다. 사용자가 쇼핑 중 장바구니에 담겨 있는 제품의 금액 합계를 볼 수 있는 기능이 중요한 기능 중 하나이다.

var shopping_cart = []; // 장바구니 제품과 금액 합계를 담고 있는 전역변수
var shopping_cart_total = 0; // 장바구니 금액 합계를 담고 있는 전역변수

function add_item_to_cart(name, price) {
  shopping_cart.push({
    name: name,
    price: price,
  });
  calc_cart_total();
}

function calc_cart_total() {
  shopping_cart_total = 0;
  for(var i = 0; i < shopping_cart.length; i++) {
    shopping_cart_total += item.price;
  }
  set_cart_total_dom(); // 금액 합계를 반영하기 위해 DOM 업데이트
}

💁‍♀️ 새로운 요구사항

무료 배송비 계산하기

장바구니에 담은 상품들의 합계가 20달러가 넘으면 제품의 구매 버튼 옆에 무료 배송 아이콘을 표시해 주려고 한다.

절차적인 방법으로 구현하기

function update_shipping_icons() {
  var buy_buttons = get_buy_buttons_dom();
  for(var i = 0; i < buy_buttons.length; i++) {
    var button = buy_bottons[i];
    var item = button.item;
    if(item.price + shopping_cart_total >= 20)
      button.show_free_shipping_icon();
    else
      button.hide_free_shipping_icon();
  }
}

function calc_cart_total() {
  shopping_cart_total = 0;
  for(var i = 0; i < shopping_cart.length; i++) {
    shopping_cart_total += item.price;
  }
  set_cart_total_dom(); // 금액 합계를 반영하기 위해 DOM 업데이트
  update_shipping_icons(); // 아이콘을 업데이트하는 코드 추가
}

합계 금액이 바뀔 때마다 아이콘을 업데이트 하기 위해 calc_cart_total() 함수 안에 update_shipping_icons() 호출해주었다.

위 코드는 문제없이 동작하고, 잘 배포했다.

💁‍♀️ 또 새로운 요구사항

세금 계산하기

장바구니의 금액 합계가 바뀔 때마다 세금을 다시 계산해야 한다.

구현

function update_tax_dom() {
  set_tax_dom(shopping_cart_total + 0.10); // 세금 계산 후 DOM을 바로 업데이트
}

function calc_cart_total() {
  shopping_cart_total = 0;
  for(var i = 0; i < shopping_cart.length; i++) {
    var item = shopping_cart[i];
    shopping_cart_total += item.price;
  }
  set_cart_total_dom(); // 금액 합계를 반영하기 위해 DOM 업데이트
  update_shipping_icons(); // 아이콘을 업데이트하는 코드 추가
  update_tax_dom(); // 페이지에 세금을 업데이트하기 위한 코드 추가
}

또 위 코드는 문제없이 동작한다.

🤷‍♀️ 테스트하기 쉽게 만들기

위 코드는 동작하긴 하지만, 정확하게 동작하고 있는지 테스트를 하고 싶어졌다. 그 중 비즈니스 규칙인 세금을 잘 계산하고 있는지를 테스트하고 싶은데 테스트하기가 꽤 어렵다.

  1. 브라우저 설정
  2. 페이지 로드
  3. 장바구니에 제품 담기 버튼 클릭
  4. DOM이 업데이트될 때까지 기다리기
  5. DOM에서 값 가져오기
  6. 가져온 문자열 값을 숫자로 바꾸기
  7. 예상하는 값과 비교하기

모든 코드가 바로 DOM으로 그려버리는 액션 함수로 이루어져 있어서 코드를 테스트하기 위해서는 브라우저에 들어가 이런 식으로 일일이 확인하는 방법밖에 없다.

🛠 테스트 개선을 위한 제안

  • DOM 업데이트와 비즈니스 규칙은 분리되어야 한다.
  • 전역변수가 없어야 한다.

🤷‍♀️ 재사용하기 쉽게 만들기

결제팀과 배송팀에서도 위에 작성한 코드를 사용하고 싶어한다. 그런데 사용할 수 없었다.

  1. 장바구니 정보를 전역변수에서 읽어오고 있는데, 결제팀과 배송팀은 데이터베이스에서 장바구니 정보를 읽어온다.
  2. 결과를 보여주기 위해 DOM을 직접 바꾸고 있지만, 결제팀은 영수증을, 배송팀을 운송장을 출력해야 한다.
function update_shipping_icons() {
  var buy_buttons = get_buy_buttons_dom();
  for(var i = 0; i < buy_buttons.length; i++) {
    var button = buy_bottons[i];
    var item = button.item;
    if(item.price + shopping_cart_total >= 20)
      button.show_free_shipping_icon();
    else
      button.hide_free_shipping_icon();
  }
}

결제팀과 배송팀에서는 item.price + shopping_cart_total >= 20 라는 비즈니스 규칙을 사용하려고 한다. 그런데 이 함수는 전역변수 shopping_cart_total 값이 있어야 실행할 수 있다.

🛠 재사용성 개선을 위한 제안

  • 전역변수에 의존하지 않아야 한다.
  • DOM을 사용할 수 있는 곳에서 실행된다고 가정하면 안 된다.
  • 함수가 결괏값을 리턴해야 한다.

🌟 액션과 계산, 데이터를 구분하기

함수형 프로그래밍으로 위 문제를 해결하기 위해 액션, 계산, 데이터로 나누어 문제를 생각해보았다.

전역변수가 변경되므로 액션, DOM에서 엘리먼트들을 읽으므로 액션, DOM을 바꾸므로 액션. 위 코드는 계산이나 데이터는 없고 모두 다 액션으로 이루어져 있다.

함수에는 입력과 출력이 있다

모든 함수에는 입력과 출력이 있다. 입력은 함수가 계산을 하기 위한 외부 정보이고, 출력은 함수 밖으로 나오는 정보나 어떤 동작이다.

함수를 호출하는 이유는 결과가 필요하기 때문인데, 입력이 있어야 결과를 얻을 수 있다.

입력과 출력은 명시적이거나 암묵적일 수도 있다. 이때 암묵적 입력과 암묵적 출력이 있으면 액션 함수가 되는 것이다. 반대로 말하면 함수에서 암묵적 입력과 암묵적 출력을 없애면 계산 함수가 된다는 것이다.

  • 명시적 입력
    • 인자
  • 암묵적 입력
    • 인자 외 다른 입력
  • 명시적 출력
    • 리턴값
  • 암묵적 출력
    • 리턴값 외 다른 출력

테스트와 재사용성을 개선하기 위한 제안

  • DOM 업데이트와 비즈니스 규칙 분리하기
    DOM을 업데이트하는 건 리턴값이 없기 때문에 암묵적 출력이라고 할 수 있다.

  • 전역변수에 의존하지 않기
    전역변수를 읽는 것은 암묵적 입력이고 바꾸는 것은 암묵적 출력이다.

  • 함수가 결괏값을 리턴하기
    명시적인 출력과 명시적인 입력을 사용하면 계산이 되기 때문에 액션 함수를 계산함수로 바꿀 수 있다.

🌟 액션에서 계산 빼내기

function calc_cart_total() {
  shopping_cart_total = 0;
  // 계산에 해당되는 코드 시작
  for(var i = 0; i < shopping_cart.length; i++) {
    var item = shopping_cart[i];
    shopping_cart_total += item.price;
  }
  // 계산에 해당되는 코드 끝
  set_cart_total_dom(); 
  update_shipping_icons();
  update_tax_dom();
}

계산에 해당되는 코드를 따로 빼서 함수로 만들어보자!

function calc_total() {
 shopping_cart_total = 0; // 전역 변수
  // shopping_cart 전역 변수
  for(var i = 0; i < shopping_cart.length; i++) {
    var item = shopping_cart[i];
    shopping_cart_total += item.price;
    // 전역변수 변경
  }
}

🤔 이 함수에서 어떤 입력과 출력이 있을까?

암묵적인 입력으로는 전역변숫값을 읽어오고, 암묵적인 출력으로는 전역변숫값을 바꾸고 있다.

따라서 이 함수는 명시적인 입력과 명시적인 출력이 없기 때문에 아직도 액션 함수이다.

🤔 전역변수 대신 지역변수를 사용하여 지역변숫값을 리턴하도록 고치면 되지 않을까?

function calc_cart_total() {
  shopping_cart_total = calc_total(); // 계산한 값 전역변수에 할당

  set_cart_total_dom(); 
  update_shipping_icons();
  update_tax_dom();
}

function calc_total(cart) {
  var total = 0; // 지역변수
  for(var i = 0; i < cart.length; i++) {
    var item = cart[i];
    total += item.price;
  }
  retrun total; // 지역변수 리턴
}

🤔 암묵적 출력을 없앴으니 암묵적 입력을 함수 인자로 바꿔볼까?

function calc_cart_total() {
  shopping_cart_total = calc_total(shopping_cart); // shopping_cart를 인자로 넣은 함수의 계산 결과를 전역변수에 할당

  set_cart_total_dom(); 
  update_shipping_icons();
  update_tax_dom();
}

function calc_total(cart) { // 전역변수 대신 인자를 만들어 사용
  var total = 0; 
  for(var i = 0; i < shopping_cart.length; i++) {
    var item = shopping_cart[i];
    total += item.price;
  }
  retrun total;
}

이런 방식으로 다른 액션 함수에서 계산을 빼내어 새로운 함수로 만들 수 있다.


❗️ 6줄 요약

  • 액션은 암묵적인 입력 또는 암묵적인 출력을 가지고 있다.

  • 계산은 암묵적인 입력 또는 암묵적인 출력이 없어야 한다.

  • 전역 변수, 데이터에 값을 추가하여 배열을 변경하는 등의 코드는 암묵적 입력 또는 암묵적 출력이 된다. (데이터는 복사하여 변경하고 반환하는 방식으로 명시적 입력과 명시적 출력으로 변경할 수 있다)

  • 암묵적 입력은 인자로 바꿀 수 있다.

  • 암묵적 출력은 리턴값으로 바꿀 수 있다.

  • 함수형 원칙을 적용하면 액션은 줄어들고 계산은 늘어난다.


혹시나 잘못된 정보가 있다면 댓글로 알려주세요 ! 저의 성장의 큰 도움이 될 것 같습니다.🌱

0개의 댓글