암묵적 인자란?
이전에 '[함수형 코딩] 더 나은 액션 만들기' 글을 통해 암묵적 입출력에 대해 다룬 적이 있다. 이번에는 '암묵적 인자'라는 말이 나왔는데, 어떤 의미일까?
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,
}));
결론
개발을 할 때 은근 전체적인 형태가 비슷한데 묘하게 합치긴 애매한 상황들이 생겼던 기억이 있다. 만약 실무에서 비슷한 케이스가 또 발생하게 된다면 이 때는 암묵적 인자를 제거하던 과정을 떠올리며 좀 더 잘 개선해 볼 수 있지 않을까 하는 기대감이 있다. 대신 이렇게 했을 때, 리팩터링 이전인 기존 함수를 완벽하게 바꾸지 않는다면 에러가 발생할 수 있으니, 프로젝트의 복잡도나 중요도에 따라 적절한 테스트 전략이 같이 고민될 필요가 있다고 생각한다.
'개발' 카테고리의 다른 글
내가 겪은 CSS 스타일링 전략의 변화와 고민들 🤔 (5) | 2025.04.06 |
---|---|
[함수형 코딩] 계층형 설계 - 직접 구현 (1) | 2025.04.02 |
[함수형 코딩] 데이터 불변성을 위한 카피온라이트, 방어적 복사 (3) | 2025.03.26 |
[시나브로 자바스크립트] History API와 SPA 라우팅 (3) | 2025.03.21 |
[함수형 코딩] 더 나은 액션 만들기 (2) | 2025.03.19 |