[Front-end] 개발자 공부

[개발 공부 85일차] 함수형 코딩 | 더 좋은 액션 만들기

MOLLY_ 2024. 8. 5. 11:43
728x90

<목차>
0. TL;DR
1.
[원칙] 암묵적 입력과 출력은 적을수록 좋다.
2. [원칙] 설계: 엉켜있는 코드를 푸는 것
3. [적용] 함수를 분리해 더 좋은 설계 만들기


 

 

 

0. TL;DR

  1. 암묵적 입력과 출력은 인자’와 ‘리턴값’으로 바꿔 없애는 게 좋음
    • 암묵적 입력: 인자가 아닌 모든 입력
    • 암묵적 출력: 리턴값이 아닌 모든 출력
  2. ‘설계’란 엉켜있는 코드를 푸는(분리하는) 것. 풀려있는 건 언제든 다시 합칠 수 있음
  3. 코드를 분리해 각 함수가 하나의 일만 하면, 개념을 중심으로 쉽게 구성 가능

 

 

들어가기 전, 가벼운 Q&A를 살펴보자.

Q1. 코드 라인 수가 적어야 좋은 코드인가?

A. 코드 라인 수만으로 유지보수하기 좋은 코드인지 판별하기는 어렵다.

측정할 수 있는 여러 가지 지표 중 하나는 ‘함수의 크기’다. 작은 함수는 이해가기 쉽고, 재사용하기 쉽다.

 

Q2. 어떤 함수를 부를 때마다 배열을 복사하면 비용이 많이 들지 않을까?

A. 아니다.

최신 프로그래밍 언어의 런타임과 가비지 콜렉터는 불필요한 메모리를 효율적으로 잘 처리해서 신경 쓰지 않고 복사본을 만들 수 있다.

 

 

1. [원칙] 암묵적 입력과 출력은 적을수록 좋다.

  • 암묵적 입력: 인자가 아닌 모든 입력
  • 암묵적 출력: 리턴값이 아닌 모든 출력
  • 암묵적 입력과 출력이 없는 함수 == 계산

암묵적 입력과 출력이 있는 함수는 아무 때나 실행할 수 없기 때문에 테스트하기 어렵다. 모든 입력값을 설정하고 테스트를 돌린 후에 모든 출력값을 확인해야 하기 때문이다.

 

입력과 출력이 많을수록 테스트는 어려워진다.

 

 

2. [원칙] 설계: 엉켜있는 코드를 푸는 것

함수를 사용하면 자연스럽게 분리할 수 있다. 함수는 인자로 넘기는 값과 그 값을 사용하는 방법을 분리한다.

 

엉켜있는 코드를 풀면(분리하면),

  1. 재사용하기 쉽다.
    : 함수는 작으면 작을수록 재사용하기 쉽다.
  2. 유지보수하기 쉽다.
    : 작은 함수는 쉽게 이해하기 쉽고, 유지보수하기 쉽다.
  3. 테스트하기 쉽다.
    : 한 가지 일만 하기 때문에 한 가지만 테스트하면 된다. 문제가 없더라도 분리할 수 있는 코드가 있다면 분리하는 게 좋다.

 

 

3. [적용] 함수를 분리해 더 좋은 설계 만들기

예시 1

// [변경 전] 원래 코드
function addItem(cart, name, price) {
	let newCart = cart.slice(); // (1) 배열 복사
	newCart.push({ // (2) item 객체 생성, (3) 복사복에 item을 추가
		name: name,
		price, price,
	});
	
	return newCart; // (4) 복사본 return
}

addItem(shoppingCart, 'shoes', 3.45);


// [변경 후] 분리한 코드
function makeCartItem(name, price) {
	return { // (2) item 객체 생성
		name: name,
		price: price,
	}
}

// 1, 3, 4는 *카피-온-라이트를 구현한 부분이기 때문에 함께 두는 게 좋음
function addItem(cart, item) {
	let newCart = cart.slice(); // (1) 배열 복사
	newCart.push(item); // (3) 복사본에 item 추가
	
	return newCart; // (4) 복사본 return
}

addItem(shoppingCart, makeCartItem('shoes', 3.45));

 

 

🍯 카피-온-라이트 (copy-on-write): 이전의 값을 복사하고 새로 쓰는 것

(1) 복사본 생성, (2) 복사본 변경, (3) 복사본 return 이렇게 3단계로 되어 있다.

 

 

예시 2

updateSippingIcons 함수는 많은 일을 하고 있다. 하는 일을 나열 및 분류하고, 작은 단위로 분리해 보자.

  • 액션에 속하는 것: 전역변수 선언과 호출, DOM 수정
// [변경 전] 원래 코드
function updateSippingIcons(cart) {
  let buyButtons = getBuyButtonDom(); // (1) 모든 버튼 가져오기

  for (let i = 0; i < buyButtons.length; i++) { // (2) 버튼 가지고 반복하기
    let button = buyButton[i];
    let item = button.item; // (3) 버튼에 관련된 제품 가져오기
    let newCart = addItem(cart, item); // (4) 가져온 제품 가지고 새 장바구니 생성

    if (getFreeShipping(newCart)) { // (5) 장바구니가 무료 배송이 필요한지 확인
      button.showFreeShippingIcon(); // (6) 아이콘을 표시하거나 감추기
    } else {
      button.hideFreeShippingIcon();
    }
  }
}


// [변경 후] 분리한 코드
function updateSippingIcons(cart) {
  let buyButtons = getBuyButtonDom(); // (1) 모든 버튼 가져오기

  for (let i = 0; i < buyButtons.length; i++) { // (2) 버튼 가지고 반복하기
    let button = buyButton[i];
    let item = button.item; // (3) 버튼에 관련된 제품 가져오기
    
    let hasFreeShipping = getFreeShippingWithItem(cart, item);
    setFreeShippingIcon(button, hasFreeShipping);
  }
}

function getFreeShippingWithItem(cart, item) {
	let newCart = addItem(cart, item); // (4) 가져온 제품 가지고 새 장바구니 생성
	return getFreeShipping(newCart); // (5) 장바구니가 무료 배송이 필요한지 확인
}

function setFreeShippingIcon(button, isShown) {
	if (isShown) {
		button.showFreeShippingIcon(); // (6) 아이콘을 표시하거나 감추기 (DOM 수정이라 액션임)
	} else {
		button.hideFreeShippingIcon();
	}
}

// 여기서, cart 혹은 item 동작과 버튼에 관한 동작을 분리하면 더 좋음

 

 

728x90