일급 함수

[쏙쏙 들어오는 함수형 코딩 Ch10-11]

코드 냄새와 중복을 없애 추상화를 잘할 수 있는 리팩터링 두 가지

암묵적 인자를 드러내기 express implicit argument

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

리팩터링

일급인 것과 일급이 아닌 것을 구별하기

일급 필드를 사용하면 API를 바꾸기 더 어려워지는 게 아닐까?

코드 전체를 바꾸지 않고, 추상화 벽 위에서 기존 필드명을 그대로 사용하고 싶다면, 내부에서 간단히 바꿔주면 된다

const validItemFields = ['price', 'quantity', 'shipping', 'tax', 'number'];
const translations = { quantity: 'number' };

function setFieldByName(cart, name, field, value) {
  // 런타임에도 안전하게 문자열 검사
  if (!validItemFields.includes(field)) throw 'Not a Valid item field';
  // 새로운 필드명으로 바꾸기
  if (translations.hasOwnProperty(field)) field = translations[field];
  const item = cart[name];
  const newItem = objectSet(item, field, value);
  const newCart = objectSet(cart, name, newItem);
  return newCart;
}

데이터 지향 data orientaion

이벤트와 엔티티에 대한 사실을 표현하기 위한 일반 데이터 구조를 사용하는 프로그래밍 형식

함수 본문을 콜백으로 바꾸기 replace body with callback

공통 본문을 고차 함수로 분리

리팩터링

ex.

원래 코드 함수 본문에서 바꿀 부분의 앞부분-뒷부분 확인

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

// 앞부분
try {
  // 본문
  fetchProduct(productId);
  // 뒷부분
} catch (error) {
  logToSnapErrors(error);
}

리팩터링 할 코드를 함수로 빼내기

function withLogging() {
  try {
    // 본문
    fetchProduct(productId);
    // 뒷부분
  } catch (error) {
    logToSnapErrors(error);
  }
}

withLogging();

빼낸 함수의 인자로 넘길 부분을 또 다른 함수로 빼내기

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

// 인자로 넘길 본문을 익명 함수로 빼내기
withLogging(() => saveUserData(user));

배열 copy-on-write 리팩토링

원래 코드

function arraySet(array, idx, value) {
  const copiedArray = array.slice();
  // 바뀌는 본문
  copiedArray[idx] = value;
  return copiedArray;
}

function arrayPush(array, element) {
  const copiedArray = array.slice();
  // 바뀌는 본문
  copiedArray.push(element);
  return copiedArray;
}

function arrayDropLast(array) {
  const copiedArray = array.slice();
  // 바뀌는 본문
  copiedArray.pop();
  return copiedArray;
}

function arrayDropFirst(array) {
  const copiedArray = array.slice();
  // 바뀌는 본문
  copiedArray.shift();
  return copiedArray;
}

함수 빼내기, 콜백 빼내기

function arraySet(array, idx, value) {
  return withArrayCopy(array, (copiedArray) => {
    // 바뀌는 본문을 익명 함수로 빼내기
    copiedArray[idx] = value;
  });
}

// 바뀌지 않는 부분을 함수로 빼내기
function withArrayCopy(array, callback) {
  const copiedArray = array.slice();
  callback(copiedArray);
  return copiedArray;
}

logging 함수 리팩토링

원래 함수

try {
  saveUserData(user);
} catch (error) {
  logToSnapErrors(error);
}

try {
  fetchProduct(productId);
} catch (error) {
  logToSnapErrors(error);
}

이름을 명확하게 바꿈

try {
  saveUserDataNoLogging(user);
} catch (error) {
  logToSnapErrors(error);
}

try {
  fetchProductWithNoLogging(productId);
} catch (error) {
  logToSnapErrors(error);
}

중복 제거; 익명 함수로 만들고, 인자를 일반적인 이름으로 바꾸기

function (arg) {
  try {
    saveUserDataNoLogging(arg);
  } catch (error) {
    logToSnapErrors(error);
  }
}

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

function wrapLogging(f) {
  try {
    f(arg);
  } catch (error) {
    logToSnapErrors(error);
  }
}

const saveUserDataWithLogging = wrapLogging(saveUserDataNoLogging);
const fetchProductaWithLogging = wrapLogging(fetchProductNoLogging);

함수를 리턴하는 함수는 함수 팩토리와 같다

전체 프로그램을 고차 함수로 만들어도 될까?

가능하겠지만, 더 중요한 질문은 “정말 그것이 필요한가”임

고차 함수는 강력하지만, 비용이 따름.

직관적인 방법에 비해, 코드가 더 읽기 쉬운가? 중복 코드를 없앨 수 있는가? 코드가 하는 일이 무엇인지 쉽게 알 수 있는가? 등 질문들을 놓치면 안된다

#develop #fp #first_class #higher-order_function