Search
🍪

5. 더 좋은 액션 만들기

내용 요약 (1 ~ 4장)

SUMMARY - 액션실행 시점과 횟수의존적임 - 액션에서 암묵적입력 (전역변수)과 출력제거 → 계산으로 바꿀 수 있음 - 계산테스트 코드 적용코드 재사용성에 유리 (+ 부수효과 없음)
SUMMARY - 액션실행 시점과 횟수의존적임 - 액션에서 암묵적입력 (전역변수)과 출력제거 → 계산으로 바꿀 수 있음 - 계산테스트 코드 적용코드 재사용성에 유리 (+ 부수효과 없음)

내용 요약 (5장)

SUMMARY - 액션에서도 암묵적 입력출력줄여 개선할 수 있음 - 설계엉켜 있는 것을 푸는 것 (작은 함수분리) → 각 함수가 하나의 일을 하면 쉽게 재구성 가능

 원칙: 암묵적 입력, 출력은 적을 수록 좋음

암묵적 입력, 출력이 있다면 다른 컴포넌트와 강하게 연결 됨 → 독립적인 모듈이 아님 (다른 곳에서 사용할 수 없음)
암묵적 입력이 있을 시 → 다른 곳에서 값을 조정하지 않는지 확인해야 함
암묵적 출력이 있을 시 → 결과 값만 받고 싶을 때에도 다른 곳에 영향을 미침 (DOM 조작 등)
암묵적 입력과 출력을 줄이면 테스트 하기 쉽고, 재사용하기 좋음

 비지니스 요구 사항과 설계를 맞추기

장바구니가 아닌 제품 가격과 합계무료배송 여부확인비지니즈 요구 사항과 맞지 않음
중복된 코드 존재 → 합계에 제품 가격을 더하는 코드가 두 군데
  BAD
// BAD 비지니스 요구 사항과 맞지 않는 인자 // 장바구니 대신 제품의 가격 합계와 가격으로 무료 배송 여부 판별 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]; // BAD 장바구니 합계를 구하는 코드 중복 됨 total += item.price; } return total; } 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(); } }
TypeScript
복사
  GOOD
// GOOD cart를 인자로 전달 -> 비지니스 요구사항과 잘 맞음 function gets_free_shipping(cart) { // GOOD 계산함수(calc_total)를 사용해 코드의 중복을 줄임 return calc_total(cart) >= 20; }
TypeScript
복사

 비지니스 요구 사항과 함수를 맞추기

장바구니는 전자상거래에서 많이 사용하는 엔티티 타입이기에 비지니스 요구사항에 잘 맞음
앞선 수정에 따라 함수 시그니처도 바뀌었기에 사용 부분도 수정
엔티티: 사물의 구조, 상태, 동작 등을 모델로 표현하는 경우, 그 모델의 구성요소
함수 시그니처: 함수의 원형에 명시되는 매개변수 리스트
액션(update_shipping_icons)에서 암묵적 입력(shopping_cart_total)을 줄여 개선
  BAD
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; // BAD 함수 시그니처 변경에 따라 argument 수정 필요 if(gets_free_shipping(shopping_cart_total, item.price)) button.show_free_shipping_icon(); else button.hide_free_shipping_icon(); } }
TypeScript
복사
  GOOD
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; // GOOD 추가할 상품이 들어갈 장바구니 생성 후 argument 전달 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(); } }
TypeScript
복사

 Q & A

Q. 코드 라인 수가 늘었습니다. 그래도 좋은 코드 인가요?
Q. add_item() 호출 시 cart 배열 복사

 암묵적 입력과 출력 줄이기

액션(calc_cart_total, update_shipping_icons)에서 암묵적 입력(shopping_cart_total)을 줄여 개선
  BAD
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; // BAD 암묵적 입력(전역변수)을 사용하고 있음 (shopping_cart) 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 calc_cart_total() { shopping_cart_total = calc_total(shopping_cart); set_cart_total_dom(); // BAD 별도의 인자 전달 없음 update_shipping_icons(); update_tax_dom(); }
TypeScript
복사
  GOOD
function update_shipping_icons(cart) { var buttons = get_buy_buttons_dom(); for(var i = 0; i < buttons.length; i++) { var button = buttons[i]; var item = button.item; // GOOD 명시적 입력(인자)을 받아서 처리 (cart) 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 calc_cart_total() { shopping_cart_total = calc_total(shopping_cart); set_cart_total_dom(); // GOOD 인자 전달에 따라 함수 시그니처가 변경 됨 update_shipping_icons(shopping_cart); update_tax_dom(); }
TypeScript
복사

코드 다시 살펴보기

과하게 분리 된 코드와 읽는 곳이 없는 전역 변수 삭제
  BAD
function add_item_to_cart(name, price) { shopping_cart = add_item(shopping_cart, name, price); calc_cart_total(shopping_cart); } // BAD 따로 분리 되어 있는 것이 과한 함수 (add_item_to_cart 내부가 적절) function calc_cart_total(cart) { var total = calc_total(cart); set_cart_total_dom(total); update_shipping_icons(cart); update_tax_dom(total); // BAD 읽는 곳이 없는 전역 변수 (update_tax_dom 수정으로 인해) shopping_cart_total = total; }
TypeScript
복사
  GOOD
function add_item_to_cart(name, price) { shopping_cart = add_item(shopping_cart, name, price); // GOOD 분리 되어 있는 것이 과한 코드를 함수 내부로 옮김 var total = calc_total(shopping_cart); set_cart_total_dom(total); update_shipping_icons(shopping_cart); update_tax_dom(total); // GOOD 불필요한 코드 제거 (shopping_cart_total = total) }
TypeScript
복사

 원칙: 설계엉켜 있는 코드푸는 것이다

“작은 함수를 사용하면 관심사를 자연스럽게 분리할 수 있다”
재사용쉬움: 함수는 작으면 작을 수록 → 재사용하기 쉬움
유지보수 쉬움: 작은 함수는 쉽게 이해할 수 있음 → 유지보수 하기 쉬움
테스트하기 쉬움: 작은 함수는 테스트 하기 좋음 → 한가지 일만 하기에 한가지만 테스트

계산 분류하기

계산을 3가지 기준에 따라 나눔 (cart, item, business)
각 그룹은 의미 있는 계층을 이룸 (계층형 설계)
Cart (cart에 대한 구조를 알아야 함) Item (item에 대한 구조를 알아햐 함) function add_item(cart, name, price) { var new_cart = cart.slice(); new_cart.push({ name: name, price: price }); return new_cart; } Cart (cart에 대한 구조를 알아야 함) Item (item에 대한 구조를 알아햐 함) Business (비지니스 규칙에 대한 함수) function calc_total(cart) { var total = 0; for(var i = 0; i < cart.length, i++) { var item = cart[i]; total += item.price; } return total; } Business (비지니스 규칙에 대한 함수) function gets_free_shipping(cart) { return calc_total(cart) >= 20; } Business (비지니스 규칙에 대한 함수) function calc_tax(amount) { return amount * 0.10; }
TypeScript
복사

 함수를 분리해 더 좋은 설계 만들기 (add_item())

item 구조만 알고 있는 함수와 cart 구조만 알고 있는 함수로 분리
cart와 item 부분을 독립적으로 확장 가능
  BAD
Cart (cart에 대한 구조를 알아야 함) Item (item에 대한 구조를 알아햐 함) function add_item(cart, name, price) { var new_cart = cart.slice(); // 1. cart 배열 복사 new_cart.push({ // 2. item 객체 생성 name: name, // 3. cart 복사본에 item 객체 추가 price: price }); return new_cart; // 4. cart 복사본 리턴 } add_item(shopping_cart, "shoes", 3.45);
TypeScript
복사
  GOOD
Item (item에 대한 구조를 알아햐 함) function make_cart_item(name, price) { // 2. item 객체 생성 별도 함수 분리 return { name: name, price: price }; } Cart (cart에 대한 구조를 알아야 함) function add_item(cart, item) { var new_cart = cart.slice(); // 1. cart 배열 복사 new_cart.push(make_cart_item(name, price)); // 3. cart 복사본에 item 객체 추가 return new_cart; // 4. cart 복사본 리턴 } add_item(shopping_cart, make_cart_item("shoes", 3.45));
TypeScript
복사

 카피-온-라이트 패턴을 빼내기 (add_item())

add_item은 카피-온-라이트를 사용해 배열에 항목을 추가하는 함수
어떤 배열과 항목에서 쓸 수 있는 이름으로 변경하여 유틸리티 함수로 변경 (add_element_last)
  BAD
Cart (cart에 대한 구조를 알아야 함) // BAD 구현은 어느 배열에나 쓸 수 있으나 일반적 이름이 아님 function add_item(cart, item) { var new_cart = cart.slice(); new_cart.push(item); return new_cart; }
TypeScript
복사
  GOOD
Array Util (배열에 대한 범용적인 유틸 함수) // GOOD 어떤 배열이나 항목에도 쓸 수 있게 일반적 이름으로 변경 function add_element_last(array, elem) { var new_array = array.slice(); new_array.push(elem); return new_array; } Cart (cart에 대한 구조를 알아야 함) function add_item(cart, item) { return add_element_last(cart, item); }
TypeScript
복사

 add_item() 사용하기

  BAD
function add_item_to_cart(name, price) { // BAD add_item에 3개의 인자를 전달 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); }
TypeScript
복사
  GOOD
function add_item_to_cart(name, price) { var item = make_cart_item(name, price); // GOOD 설계 개선에 따라 add_item에 2개의 인자 전달 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); }
TypeScript
복사

Q & A

Q. 계산을 유틸리티, 장바구니, 비지니스 규칙으로 나누는 이유
Q. 비지니스 규칙과 장바구니의 차이는? 전자상거래에서 장바구니는 비지니스 규칙이 아닌지?
Q. 비지니스 규칙과 장바구니에 모두 속하는 함수도 있을 수 있는지?