본문 바로가기
개발

[함수형 코딩] 중복 코드를 없애는 방법: 암묵적 인자 드러내기

by soyooooon 2025. 4. 7.
반응형

쏙쏙 들어오는 함수형 코딩

암묵적 인자란?

이전에 '[함수형 코딩] 더 나은 액션 만들기' 글을 통해 암묵적 입출력에 대해 다룬 적이 있다. 이번에는 '암묵적 인자'라는 말이 나왔는데, 어떤 의미일까?

const getUserName = (user) => user['name'];
const getUserEmail = (user) => user['email'];
const getUserAge = (user) => user['age'];

위와 같은 함수가 있다고 했을 때, 이 함수들은 서로 함수명과 읽어올 key 다를 뿐, 전체적인 형태가 동일하다. 함수 이름을 보면 key를 결정하는 문자열이 함수명에 포함되어 있다는 것을 확인할 수 있는데, 이런 상황을 보고 함수 이름에 암묵적 인자가 있다고 할 수 있다. 구분하고자 하는 값을 인자로 직접 전달하는 것이 아니라, 함수 이름의 일부에 녹아들어 있기 때문이다.

 

위 함수에서의 암묵적 인자를 없애고, 필드명을 인자로 넘길 수 있는 값인 일급 값으로 바꿔 개선한다면 다음과 같이 바꿀 수 있다.

const getUserField = (user, key) => user[key];

// 함수 사용 시
getUserField(user, 'name');
getUserField(user, 'email');
getUserField(user, 'age');

 

또 다른 상황은 없을까? 🤔

위에서 작성한 예제는 굉장히 간단한 형태를 보여주고 있다. 아마 암묵적 인자에 대해 모르고 있었더라도 저런 반환값이 필요하다고 하면 애초에 getUserField(user, 'name'); 이런 형태로 작성했을 가능성이 높다고 생각한다.

 

이번엔 조금 더 실제 있을법한 예제로 작성해 보았다. raw 데이터가 있을 때, createdAt, updatedAt을 추가함과 동시에 각 상황에 맞는 필드를 다듬는 함수가 있다고 가정했다. 각 함수에서 추가되는 key값뿐만 아니라 해당 key의 value를 처리하는 방식도 함수마다 다르다.

// utils/userParser.ts
export const parseUser = (raw: any) => {
  return {
    ...raw,
    createdAt: new Date(raw.createdAt),
    updatedAt: new Date(raw.updatedAt),
    fullName: `${raw.firstName} ${raw.lastName}`,
  };
};

// utils/productParser.ts
export const parseProduct = (raw: any) => {
  return {
    ...raw,
    createdAt: new Date(raw.createdAt),
    updatedAt: new Date(raw.updatedAt),
    priceWithTax: raw.price * 1.1,
  };
};

// utils/postParser.ts
export const parsePost = (raw: any) => {
  return {
    ...raw,
    createdAt: new Date(raw.createdAt),
    updatedAt: new Date(raw.updatedAt),
    contentLength: raw.content.length,
  };
};

 

이런 경우에는 다음 단계들을 통해 리팩터링을 진행할 수 있을 것이다.

Step1. 공통되는 부분을 함수로 추출하기

각 함수는 ...raw와 createdAt, updatedAt을 추가하는 부분이 공통적으로 반복되고 있었다. 이 부분을 withTimestamps라는 별도의 함수로 분리한 다음 이를 각 함수에서 호출했다.

// utils/baseParser.ts
export const withTimestamps = (raw: any) => ({
  ...raw,
  createdAt: new Date(raw.createdAt),
  updatedAt: new Date(raw.updatedAt),
});
// utils/userParser.ts
export const parseUser = (raw: any) => {
  const base = withTimestamps(raw);
  return {
    ...base,
    fullName: `${raw.firstName} ${raw.lastName}`,
  };
};

// utils/productParser.ts
export const parseProduct = (raw: any) => {
  const base = withTimestamps(raw);
  return {
    ...base,
    priceWithTax: raw.price * 1.1,
  };
};

// utils/postParser.ts
export const parsePost = (raw: any) => {
  const base = withTimestamps(raw);
  return {
    ...base,
    contentLength: raw.content.length,
  };
};

Step2. 고차함수 형태

이전 단계에서 세 개 함수가 공통된 형태를 띤 상태에서 마지막 필드만 다른 key, value 값을 가지고 있었다. 이번 단계에서는 공통된 부분을 createParser라는 하나의 함수로 통합했다. 그리고 기존 각 함수에서 다른 형태를 띠던 부분들은 함수의 형태로 인자로 넘겨주어 활용할 수 있도록 개선했다. 이를 통해 암묵적 인자를 제거하고 코드의 중복을 없앨 수 있었다.

// utils/createParser.ts
export const createParser = (enhancer: (raw: any) => object) => {
  return (raw: any) => {
    const base = withTimestamps(raw);
    return {
      ...base,
      ...enhancer(raw),
    };
  };
};
// 사용 예
const parseUser = createParser(raw => ({
  fullName: `${raw.firstName} ${raw.lastName}`,
}));

const parseProduct = createParser(raw => ({
  priceWithTax: raw.price * 1.1,
}));

const parsePost = createParser(raw => ({
  contentLength: raw.content.length,
}));

 

결론

개발을 할 때 은근 전체적인 형태가 비슷한데 묘하게 합치긴 애매한 상황들이 생겼던 기억이 있다. 만약 실무에서 비슷한 케이스가 또 발생하게 된다면 이 때는 암묵적 인자를 제거하던 과정을 떠올리며 좀 더 잘 개선해 볼 수 있지 않을까 하는 기대감이 있다. 대신 이렇게 했을 때, 리팩터링 이전인 기존 함수를 완벽하게 바꾸지 않는다면 에러가 발생할 수 있으니, 프로젝트의 복잡도나 중요도에 따라 적절한 테스트 전략이 같이 고민될 필요가 있다고 생각한다.

반응형