내용 요약 (1 ~ 4장)
SUMMARY
- 액션은 실행 시점과 횟수에 의존적임
- 액션에서 암묵적인 입력 (전역변수)과 출력을 제거 → 계산으로 바꿀 수 있음
- 계산은 테스트 코드 적용과 코드 재사용성에 유리 (+ 부수효과 없음)
SUMMARY
- 액션은 실행 시점과 횟수에 의존적임
- 액션에서 암묵적인 입력 (전역변수)과 출력을 제거 → 계산으로 바꿀 수 있음
- 계산은 테스트 코드 적용과 코드 재사용성에 유리 (+ 부수효과 없음)
내용 요약 (5장)
SUMMARY
- 액션에서도 암묵적 입력과 출력을 줄여 개선할 수 있음
- 설계는 엉켜 있는 것을 푸는 것 (작은 함수로 분리) → 각 함수가 하나의 일을 하면 쉽게 재구성 가능
원칙: 암묵적 입력, 출력은 적을 수록 좋음
•
암묵적 입력, 출력이 있다면 다른 컴포넌트와 강하게 연결 됨 → 독립적인 모듈이 아님 (다른 곳에서 사용할 수 없음)
•
암묵적 입력이 있을 시 → 다른 곳에서 값을 조정하지 않는지 확인해야 함
•
암묵적 출력이 있을 시 → 결과 값만 받고 싶을 때에도 다른 곳에 영향을 미침 (DOM 조작 등)
•
암묵적 입력과 출력을 줄이면 테스트 하기 쉽고, 재사용하기 좋음
비지니스 요구 사항과 설계를 맞추기
•
장바구니가 아닌 제품 가격과 합계로 무료배송 여부를 확인 → 비지니즈 요구 사항과 맞지 않음
•
중복된 코드 존재 → 합계에 제품 가격을 더하는 코드가 두 군데
// 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 cart를 인자로 전달 -> 비지니스 요구사항과 잘 맞음
function gets_free_shipping(cart) {
// GOOD 계산함수(calc_total)를 사용해 코드의 중복을 줄임
return calc_total(cart) >= 20;
}
TypeScript
복사
비지니스 요구 사항과 함수를 맞추기
•
장바구니는 전자상거래에서 많이 사용하는 엔티티 타입이기에 비지니스 요구사항에 잘 맞음
•
앞선 수정에 따라 함수 시그니처도 바뀌었기에 사용 부분도 수정
•
엔티티: 사물의 구조, 상태, 동작 등을 모델로 표현하는 경우, 그 모델의 구성요소
•
함수 시그니처: 함수의 원형에 명시되는 매개변수 리스트
•
액션(update_shipping_icons)에서 암묵적 입력(shopping_cart_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;
// BAD 함수 시그니처 변경에 따라 argument 수정 필요
if(gets_free_shipping(shopping_cart_total, item.price))
button.show_free_shipping_icon();
else
button.hide_free_shipping_icon();
}
}
TypeScript
복사
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)을 줄여 개선함
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
복사
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
복사
코드 다시 살펴보기
•
과하게 분리 된 코드와 읽는 곳이 없는 전역 변수 삭제
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
복사
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 부분을 독립적으로 확장 가능
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
복사
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)
Cart (cart에 대한 구조를 알아야 함)
// BAD 구현은 어느 배열에나 쓸 수 있으나 일반적 이름이 아님
function add_item(cart, item) {
var new_cart = cart.slice();
new_cart.push(item);
return new_cart;
}
TypeScript
복사
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() 사용하기
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
복사
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. 비지니스 규칙과 장바구니에 모두 속하는 함수도 있을 수 있는지?