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. 비지니스 규칙과 장바구니에 모두 속하는 함수도 있을 수 있는지?