본문으로 건너뛰기

04 액션에서 계산 빼내기

이번 장에서 살펴볼 내용
  • 어떻게 함수로 정보가 들어가고 나오는지 살펴봅니다.
  • 테스트하기 쉽고 재사용성이 좋은 코드를 만들기 위한 함수형 기술에 대해 알아봅니다.
  • 액션에서 계산을 빼내는 방법을 배웁니다.

이 장에서는 테스트하기 쉽고 재사용 하기 좋은 코드를 만들기 위해 리팩터링하는 방법을 살펴보겠습니다. 예제 코드에 기능을 조금 추가한 다음, 액션에서 계산을 빼내는 리팩터링을 하겠습니다.

MegaMart.com에 오신 것을 환영합니다.

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++) {
var item = shopping_cart[i];
shopping_cart_total += item.price;
}

set_cart_total_dom(); // 금액 합계를 반영하기 위해 DOM 업데이트
}

무료 배송비 계산하기

새로운 요구사항

MegaMart는 구매 합계가 20달러 이상이면 무료 배송을 해주려고 합니다. 그래서 장바구니에 넣으면 합계가 20달러가 넘는 제품의 구매 버튼 옆에 무료 배송 아이콘을 표시해 주려고 합니다.

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

구매 버튼에 무료 배송 아이콘을 표시하기 위한 함수를 만듭니다.

function update_shipping_icons() {
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;
if (item.price + shopping_cart_total >= 20) {
button.show_free_shipping_icon();
} else {
button.hide_free_shipping_icon();
}
}
}

합계 금액이 바뀔 때마다 모든 아이콘을 업데이트하기 위해 calc_cart_total() 함수 마지막에 update_shipping_icons() 함수를 호출합니다.

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(); // 무료 배송 아이콘 업데이트
}

세금 계산하기

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

function update_tax_dom() {
set_tax_dom(shopping_cart_total * 0.1);
}

앞에서 한 것처럼 calc_cart_total() 함수 마지막에 update_tax_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(); // 세금 업데이트
}

테스트하기 쉽게 만들기

지금 코드는 비즈니스 규칙을 테스트하기 어렵습니다.

코드가 바뀔 때마다 조지는 아래와 같은 테스트를 만들어야 합니다.

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

테스를 개선을 위한 조지의 제안

테스트를 더 쉽게 하려면 다음 조건이 필요합니다.

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

재사용하기 쉽게 만들기

결제팀과 배송팀이 우리 코드를 사용하려고 합니다.

결제팀과 배송팀이 우리 코드를 사용하려고 합니다. 그러나 다음과 같은 이유로 재사용할 수 없었습니다.

  • 장바구니 정보를 전역변수에서 읽어오고 있지만, 결제팀과 배송팀은 데이터베이스에서 장바구니 정보를 읽어 와야 합니다.
  • 결과를 보여주기 위해 DOM을 직접 바꾸고 있지만, 결제팀은 영수증을, 배송팀은 운송장을 출력해야 합니다.

개발팀 제나의 제안

재사용하려면 아래와 같은 조건이 필요합니다.

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

함수에는 입력과 출력이 있습니다.

입력은 함수가 계산을 하기위한 외부 정보입니다. 출력은 함수 밖으로 나오는 정보나 어떤 동작입니다. 함수를 부르는 이유는 결과가 필요하기 때문입니다.

입력과 출력은 명시적이거나 암묵적일 수 있습니다.

인자는 명시적인 입력입니다. 그리고 리턴값은 명시적인 출력입니다. 하지만 암묵적으로 함수로 들어가거나 나오는 정보도 있습니다.

함수에 암묵적 입력과 출력이 있으면 액션이 됩니다.

함수에서 암묵적 입력과 출력을 없애면 계산이 됩니다. 암묵적 입력은 함수의 인자로 바꾸고, 암묵적 출력은 함수의 리턴값으로 바꾸면 됩니다.

액션에서 계산 빼내기

먼저 계산에 해당하는 코드를 분리합니다. 그리고 입력값은 인자로 출력값은 리턴값으로 바꿉니다.

원래 코드
function calc_cart_total() {
shopping_cart_total = 0;
for (var i = 0; i < shopping_cart.length; i++) {
shopping_cart_total += shopping_cart[i].price;
}

set_cart_total_dom();
update_shipping_icons();
update_tax_dom();
}
바꾼 코드
function calc_cart_total() {
calc_total();
set_cart_total_dom();
update_shipping_icons();
update_tax_dom();
}

function calc_total() {
shopping_cart_total = 0;
for (var i = 0; i < shopping_cart.length; i++) {
shopping_cart_total += shopping_cart[i].price;
}
}

원래 코드에서 빼낸 코드로 새로운 함수로 만들었습니다. 하지만 새 함수는 아직 액션입니다.

방금 한 리팩터링은 서브루틴 추출하기라고 할 수 있습니다. 기존 코드에서 동작은 바뀌지 않았습니다.

노트

앞에서 코드를 고치면서 동작은 그대로 유지했습니다. 이런 방법을 리팩터링이라고 합니다.

새로 만든 함수는 아직 액션이기 때문에 계산으로 바꿔야 합니다. 여기에 있는 입력과 출력이 모두 암묵적이기 때문에 명시적인 입력과 출력으로 바꿔야 계산이 됩니다.

현재 코드
function calc_cart_total() {
calc_total();
set_cart_total_dom();
update_shipping_icons();
update_tax_dom();
}

function calc_total() {
shopping_cart_total = 0;
for(var i = 0; i < shopping_cart.length; i++) {
shopping_cart_total += shopping_cart[i].price;
}
}
암묵적 출력을 없앤 코드
function calc_cart_total() {
shopping_cart_total = calc_total();
set_cart_total_dom();
update_shipping_icons();
update_tax_dom();
}

function calc_total() {
var_total = 0;
for(var i = 0; i < shopping_cart.length; i++) {
item = shopping_cart[i];
total += item.price;
}
return total;
}

이제 남은 일은 암묵적 입력을 함수 인자로 바꾸는 일입니다.

현재 코드
function calc_cart_total() {
shopping_cart_total = calc_total();
set_cart_total_dom();
update_shipping_icons();
update_tax_dom();
}

function calc_total() {
var_total = 0;
for(var i = 0; i < shopping_cart.length; i++) {
item = shopping_cart[i];
total += item.price;
}
return total;
}
암묵적 입력을 없앤 코드
function calc_cart_total() {
shopping_cart_total = calc_total(shopping_cart);
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++) {
item = cart[i];
total += item.price;
}
return total;
}

액션에서 또 다른 계산 빼내기

add_item_to_cart() 함수에서도 계산을 빼낼 수 있습니다. 앞에서 한 것처럼 빼낼 부분을 찾고 함수로 빼서 입력과 출력을 명시적으로 바꿉니다.

원래 코드
function add_item_to_cart(name, price) {
shopping_cart.push({
name: name,
price: price
});

calc_cart_total();
}
바꾼 코드
function add_item_to_cart(name, price) {
add_item(name, price);
calc_cart_total();
}

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

shopping_cart를 사용하는 부분이 암묵적 입력과 출력입니다. 인자와 리턴값으로 바꿔봅시다.

원래 코드
function add_item_to_cart(name, price) {
add_item(name, price);
calc_cart_total();
}

function add_item(name, price) {
shopping_cart.push({
name: name,
price: price
});
}
바꾼 코드
function add_item_to_cart(name, price) {
shopping_cart = add_item(shopping_cart, name, price);
calc_cart_total();
}

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. 새 함수에 암묵적 입력과 출력을 찾습니다.

새 함수에 암묵적인 입력과 출력을 찾습니다. 압묵적 입력은 함수를 부르는 동안 결과에 영향을 줄 수 있는 것을 말합니다. 암묵적 출력은 함수 호출의 결과로 영향을 받는 것을 말합니다.

3. 암묵적 입력은 인자로 암묵적 출력은 리턴값으로 바꿉니다.

한 번에 하나씩 입력은 인자로 출력은 리턴값으로 바꿉니다. 새로운 리턴값이 생겼다면 호출하는 코드에서 함수의 결과를 변수에 할당해야 할 수도 있습니다.

여기서 인자와 리턴값은 바뀌지 않는 불변값이라는 것이 중요합니다. 리턴값이 나중에 바뀐다면 암묵적 출력입니다. 또 인자로 받은 값이 바뀔 수 있다면 암묵적 입력입니다.

요점 정리

  • 액션은 암묵적인 입력 또는 출력을 가지고 있습니다.
  • 계산의 정의에 따르면 계산은 암묵적인 입력이나 출력이 없어야 합니다.
  • 공유 변수는 일반적으로 암묵적 입력 또는 출력이 됩니다.
  • 암묵적 입력은 인자로 바꿀 수 있습니다.
  • 암묵적 출력은 리턴값으로 바꿀 수 있습니다.
  • 함수형 원칙을 적용하면 액션은 줄어들고 계산은 늘어납니다.