일급 함수

일급 값(first-class value)

변수에 할당할 수 있고 함수의 인자로 넘기거나, 반환값으로 사용할 수 있다.

즉, 코드로 다룰 수 있는 값을 의미

자바스크립트에서 일급 값

자바스크립트에서는 일급 값과 일급이 아닌 값이 섞여있다.

일급 값이 아닌 것

  • 조건 문

  • 반복 문

  • 수식 연산자

  • try/catch 블록

일급 값으로 할 수 있는 것

  • 변수에 할당

  • 배열이나 객체에 담기

  • 함수의 입력 값, 출력 값으로 넘기기

일급 값이 아닌 것들을 찾고 일급 값으로 바꾸는 기술은 함수형 프로그래밍에서 중요한 기술로 문제를 해결하는 새로운 능력을 얻게된다.

어떤 문법이든 일급 함수로 바꿀 수 있다.

자바스크립트에서 함수는 일급 값이므로 일급 값이 아닌 것들을 함수로 만들어 일급 값으로 바꿀 수 있다.

// +,- 일급 값이 아닌 수식연산자를 함수로 만들어 일급 값으로 변환
const plus = (a: number, b:number) => a + b;

일급 함수

일급 함수란 함수를 일급 값처럼 쓰이는 함수를 의미

고차 함수(High-Order Function)

일급 함수를 함수의 인자로 받거나 반환하는 함수로 일급 함수를 활용한 함수를 의미한다.

  • 고차함수로 패턴이나 원칙을 코드로 만들 수 있음

  • 고차함수를 사용하지 않으면 일일히 수작업으로 해줘야하 하는 부분이 생김

  • 고차함수는 한번 정의하고 필요한 곳에서 여러번 재사용 가능

  • 고차함수로 함수를 반환하는 함수를 만들 수 있다. 반환 받은 함수는 변수에 할당해서 이름이 있는 일반적인 함수처럼 사용이 가능하다.

  • 고차함수는 많은 중복 코드를 제거할 수 있지만, 가독성을 해칠 수 있다.

  • 코드에 반복되는 부분을 줄이기 위해 고차 함수를 사용하는 것이 중요

함수를 인자로 받는 고차함수반복적이지만 조금씩 다른 작업을 하는 코드에서 공통적인 부분을 함수로 하나의 함수로 추상화시키고 다른 작업을 하는 부분을 함수의 인자로 분리할 수 있기 때문에 재사용성이 높아짐

만약 고차 함수로 만든 좋은 방법을 찾았다면 직관적인 방법과 항상 비교해보자 코드가 더 읽기 쉬운지, 이해하기 쉬운지, 중복을 얼마나 제거했는지를 판단하여 적용하자

일급 함수를 활용한 리팩토링

리팩토링을 통해 얻는 것

  1. 표준화된 원칙

  2. 새로운 동작에 원칙을 적용할 수 있음

  3. 여러 개를 변경할 때 최적화

코드의 냄새를 맡는 방법

  1. 함수 구현부가 거의 똑같음

  2. 함수의 이름이구현의 차이를 만듦 → 함수의 이름에서 서로 다른 부분이 암묵적 인자이다.

코드의 냄새(Code Smell)는 더 큰 문제를 가져 올 수 있는 나쁜 코드를 의미한다.

코드 냄새: 함수 이름의 암묵적 인자

함수 본문에서 사용하는 어떤 값이 함수 이름에 나타난다면 함수 이름에 있는 암묵적 인자는 코드의 냄새가 된다.

  • 거의 똑같이 구현된 함수에서 함수의 이름이 구현부에 있는 다른 부분을 가리킨다.

  • 함수 이름의 암묵적 인자를 일급 값으로 바꾸면 표현력이 좋아짐

리팩토링: 암묵적 인자 들어내기

암묵적 인자가 일급 값이 되도록 함수 인자를 추가하여 잠재적인 중복을 없앨 수 있고, 코드의 목적을 더 잘 표현할 수 있게 됨

  1. 함수 이름에 있는 암묵적인 인자를 확인

  2. 명시적인 인자를 추가

  3. 함수 본문에 하드코딩된 값을 새로운 인자로 바꾼다.

  4. 함수 호출부를 리팩토링된 코드에 맞춰 수정한다.

Before

원래 가격만 설정하는 setPriceByName() 함수

// 함수 이름에 있는 price가 암묵적 인자가 된다.
const setPriceByName = (cart, name, price) => {  
  const item = cart[name];
  const newItem = objectSet(item, 'price', price);
  const newCart = objectSet(cart, name, newItem);
  
  return newCart;
}

// 사용하는 코드
cart = setPriceByName(cart, 'shoe', 13);
cart = setQuantityByName(cart, 'shoe', 3);
cart = setShippingName(cart, 'shoe', 0);
cart = setTaxByName(cart, 'shoe', 2.34);

Refactoring

어떤 필드값이든 설정할 수 있는 setFieldByName() 함수로 리팩토링 하자

리팩토링을 통해 비슷한 함수를 모두 일반적인 함수 하나로 변경

// 명시적인 인자를 추가하고 원래 인자는 더 일반적인 이름으로 변경
const setFieldByName = (cart, name, field, value) => {
  const item = cart[name];
  const newItem = objectSet(item, field, value);
  const newCart = objectSet(cart, name, newItem);
  
  return newCart;
}

cart = setFieldByName(cart, 'shoe', 'price', 13);
cart = setFieldByName(cart, 'shoe', 'quantity', 3);
cart = setFieldByName(cart, 'shoe', 'shipping', 0);
cart = setFieldByName(cart, 'shoe', 'tax', 2.34);

리팩토링: 함수 본문을 콜백으로 바꾸기

함수 본문에 비슷하지만 서로 다른 부분을 함수의 콜백 인자로 바꾼다.

  • 일급 함수의 특징을 이용하여 어떤 함수에 동작(함수)을 전달할 수 있게됨

  • 원래 코드를 고차함수로 만드는 강력한 방법

  1. 함수 구현부의 앞뒤로 바뀌지 않는 부분을 찾고, 리팩토링할 본문을 찾는다.

  2. 리팩토링할 코드를 함수로 추출한다.

  3. 추출한 함수를 인자로 전달받고 빼낸 함수 본문에 적용한다.

  4. 문법의 중복 및 코드 원칙 같은 다른 형태의 중복을 없앨때 탁월하다.

인자로 전달하는 함수를 콜백(Callback)함수라 부르며, 콜백으로 전달된 함수는 나중에 호출 될 것을 기대한다. 핸들러 함수라고도 불린다.

Before

// 모든 곳에서 같은 형태로 catch 구문을 사용한다.
try { // 앞부분
  saveUserData(user); // 본문 (달라지는 부분)
} catch (error) {  // 뒷부분
  logToSnapErrors(error);
}

// #2
try {
  fetchProduct(productId); // 달라지는 부분
} catch (error) {
  logToSnapErrors(error);
}

Refactoring

const withLogging = (f) => {
  try {
    f();
  } catch (error) {
    logToSnapErrors(error);
  }
}

함수의 인자로 일반 데이터를 전달하지 않고 함수를 전달하는 이유

함수로 전달하는 이유는 전달하는 함수 내부 코드가 특정 문맥 안에서 실행되어야 하기 때문이다.

고차함수를 활용하면 다른 곳에 정의된 문맥에서 코드를 실행시킬 수 있다. 그리고 문맥은 함수이기 때문에 재사용이 가능하다.

리팩토링: 카피온 라이트 패턴 적용

  1. 함수 본문의 앞부분과 뒷부분 확인하기

  2. 함수 빼내기

  3. 콜백 빼내기

Before

본문과 앞부분, 뒷부분을 확인해보면 카피-온-라이트가 적용된 두 함수는 비슷하다.


const arraySet = <T>(array: T[], idx: number, value: T) => {
  const copy = [...array];
  copy[idx] = value;
  return copy;
}

const push = <T>(array: T[], elem: T) => {
  const copy = [...array];
  copy.push(elem);
  return copy;
}

After

// #1 함수 빼내기 (리팩토링 할 코드를 함수로 빼는 단계)
const arraySet = <T>(array: T[], idx: number, value: T) => {
  return withArrayCopy(array);  
}

const withArrayCopy = <T>(array: T[]) => {
  const copy = [...array];
  copy[idx] = value;
  return copy;
}

// #2 콜백 빼내기 (본문을 콜백함수로 빼는 단계)
const arraySet = <T>(array: T[], idx: number, value: T) => {
  return withArrayCopy(array, (copy) => copy[idx] = value);  
}
// 카피-온-라이트 원칙을 따르고 재사용할 수 있는 함수
const withArrayCopy = <T>(array: T[], modfiy: (x: T[]) => void) => {
  const copy = [...array];
  modify(copy);
  return copy;
}

Last updated