Skip to content

chp. 10 일급 함수 I

이번 장에서는 일급 함수의 개념을 바탕으로, 코드 속 중복을 제거하고 추상화를 강화하는 리팩토링 기법을 알아봅니다.

1. 함수 이름에 있는 암묵적 인자를 명시적으로 바꾸기

아래 두 함수는 비슷한 패턴을 따릅니다.

jsx
function setPriceByName(cart, name, price) {
  var item = cart[name];
  var newItem = objectSet(item, "price", price);
  var newCart = objectSet(cart, name, newItem);
  return newCart;
}

function setShippingByName(cart, name, ship) {
  var item = cart[name];
  var newItem = objectSet(item, "shipping", ship);
  var newCart = objectSet(cart, name, newItem);
  return newCart;
}

function objectSet(object, key, value) {
  var copy = Object.assign({}, object);
  copy[key] = value;
  return copy;
}

차이점은 오직 필드명(price, shipping)뿐인데, 이 값이 함수 이름 속에 숨어 있습니다.

따라서 필드가 바뀔 때마다 새로운 함수가 필요해지고, 코드가 점점 늘어나는 구조가 됩니다.

1.1 리팩토링: 암묵적 인자 드러내기

리팩토링 절차는 다음과 같습니다.

  1. 함수 이름 속 암묵적 인자를 식별한다.
  2. 해당 값을 명시적인 함수 인자로 추가한다.
  3. 본문에 하드코딩된 값을 인자로 대체한다.

setPriceByName 리팩토링 예시

jsx
// 기존
function setPriceByName(cart, name, price) {
  var item = cart[name];
  var newItem = objectSet(item, "price", price);
  var newCart = objectSet(cart, name, newItem);
  return newCart;
}

// 리팩토링 후
function setFieldByName(cart, name, field, value) {
  var item = cart[name];
  var newItem = objectSet(item, field, value);
  var newCart = objectSet(cart, name, newItem);
  return newCart;
}

field는 함수 이름 속에 숨어 있던 암묵적 인자가 아니라, 명시적으로 다룰 수 있는 값이 됩니다.

1.2. 일급값

이 과정에서 객체의 속성이 단순히 코드 속 하드코딩된 문자열에서, 자유롭게 전달하고 조작할 수 있는 인자로 변경되었습니다.

이처럼 변수에 담고, 함수 인자로 전달하고, 반환할 수 있는 대상일급값이라고 합니다.

TIP

  • 일급값의 특징

    1. 변수에 할당할 수 있다.
    2. 함수 인자로 전달할 수 있다.
    3. 함수 반환값으로 사용할 수 있다.
    4. 배열이나 객체에 담을 수 있다.
  • 일급값이 아닌 것

    • + 연산자
    • if, for, try/catch 같은 제어 구조

2. 함수 본문을 콜백으로 바꾸기

두 번째 리팩토링 방법은 코드의 앞뒤 흐름은 동일하지만, 중간 동작만 달라지는 경우에 유용합니다.

대표적으로 예외 처리, 반복문, 공통 로직이 해당합니다.

2.1. 에러 로깅 예시

프로젝트 전역에서 쓰일 수 있는 에러 로그를 전달하는 코드 예시입니다.

jsx
try {
  // 앞 부분
  saveUserData(user); // 본문
} catch (error) {
  logToSnapErrors(error); // 뒷 부분
}

이 패턴은 시스템이 커질수록 수백 번 반복됩니다.

본문을 콜백으로 바꾸는 리팩토링을 적용해보면,

jsx
function withLogging(f) {
  try {
    f();
  } catch (error) {
    logToSnapErrors(error);
  }
}

withLogging(function () {
  saveUserData(user);
});

앞뒤 제어 흐름은 withLogging이 담당하고, 달라지는 부분만 콜백으로 전달한 것입니다.

2.2. 반복문 예시

반복문에서도 같은 원리를 적용할 수 있습니다.

jsx
// 요리하고 먹기
for (var i = 0; i < foods.length; i++) {
  var food = foods[i];
  cook(food);
  eat(food);
}

// 설거지하기
for (var i = 0; i < dishes.length; i++) {
  var dish = dishes[i];
  wash(dish);
  dry(dish);
  putAway(dish);
}

두 반복문은 구조는 같고, 본문만 다릅니다. 이를 forEach 같은 고차 함수로 바꾸면:

jsx
forEach(foods, function (food) {
  cook(food);
  eat(food);
});

forEach(dishes, function (dish) {
  wash(dish);
  dry(dish);
  putAway(dish);
});

공통적인 반복 제어는 forEach가 담당하고, 실제로 하고 싶은 일만 콜백으로 넘긴 구조입니다.

2.3. 얻는 이점

  • 중복 제거: 제어 흐름은 한 번만 구현
  • 가독성 향상: 함수 이름이 의도를 드러냄 (withLogging, forEach)
  • 재사용성 증가: 새로운 동작을 콜백으로 전달하기만 하면 됨
  • 버그 예방: 인덱스 처리, 예외 처리에서 발생할 실수를 줄임
  • 확장성 확보: map, filter, reduce 같은 함수형 API로 발전 가능

정리하며

일급 함수는 단순히 “함수를 값처럼 쓸 수 있다”는 특성에 그치지 않습니다.

  • 암묵적 인자 드러내기: 함수 이름 속에 숨어 있는 값을 인자로 꺼내, 중복을 줄이고 유연성을 높입니다.
  • 본문을 콜백으로 바꾸기: 공통 흐름을 함수로 일반화하고, 달라지는 부분만 콜백으로 분리해 재사용성과 가독성을 확보합니다.

두 가지 리팩토링은 작은 코드 중복 제거에서 시작해, 점차 함수형 프로그래밍의 강력한 추상화 도구로 확장될 수 있습니다.