지식조각모음
Chapter 10. 일급 함수 1 본문
💡 이번 장에서 살펴볼 내용
- 왜 일급 값이 좋은지 알아봅니다.
- 문법을 일급 함수로 만드는 방법에 대해 알아봅니다.
- 고차 함수로 문법을 감싸는 방법을 알아봅니다.
- 일급 함수와 고차 함수를 사용한 리팩터링 두 개를 살펴봅니다.
들어가기 전에
앞으로 배울 코드의 냄새와 중복을 없는 두 가지 리팩터링 방법입니다.
코드의 냄새
❓ 함수 이름에 있는 암묵적 인자
👉 함수 본문에서 사용하는 어떤 값이 함수 이름에 나타나는 경우 이런 암묵적 인자로 코드의 냄새를 알아볼 수 있다.
리팩터링 방법
❓ 암묵적 인자 드러내기
👉 암묵적 인자가 일급 값이 되도록 함수에 인자를 추가한다.
❓ 함수 본문을 콜백으로 바꾸기
👉 비슷한 함수에 있는 서로 다른 부분을 콜백으로 바꾼다. 서로 다른 동작 부분은 일급 함수로 전달한다.
문제 상황
비슷한 요구 사항이 계속 된다면? 개발을 하다보면 요구사항을 설정하는 부분만 다르고 그 내용이 대동소이 할 때가 있다. 혹은 동작하는 방식은 비슷한데 어느 한 부분만 달라서 비슷한 함수를 여러개 만들어야 하는 경우도 있다. 이런 경우 어떻게 리팩터링 할까?
코드의 냄새: 함수 이름에 있는 암묵적 인자
암묵적 인자란?
function setPriceByName(cart, name, price) {
var item = cart[name];
var newItem = ObjectSet(item, 'price', price);
var newCart = ObjectSet(cart, name, newItem);
return newCart;
}
function setQuantityByName(cart, name, quant) {
var item = cart[name];
var newItem = ObjectSet(item, 'quantity', quant);
var newCart = ObjectSet(cart, name, newItem);
return newCart;
}
function setShppingByName(cart, name, ship) {
var item = cart[name];
var newItem = ObjectSet(item, 'shipping', ship);
var newCart = ObjectSet(cart, name, newItem);
return newCart;
}
위 함수들은 모두 비슷비슷하게 생긴 함수들이다. 이 함수들은 다음과 같은 특징이 있다.
- 두 번째 줄에서만 내용이 조금 다르고 그 외 모든 내용은 동일하다.
- 변경되는 문자열이 함수 이름에 있다.
이런 냄새를 함수 이름에 있는 암묵적 인자라고 부른다. 그리고 함수 이름에서 서로 다른 부분이 암묵적 인자가 된다.
이런 냄새는 다음과 같은 두 가지 특징을 가진다.
- 함수 이름에 있는 암묵적 인자 냄새의 특징
- 함수 구현이 거의 똑같다.
- 함수 이름이 구현의 차이를 만든다.
리팩터링: 암묵적 인자 드러내기
암묵적 인자를 없애기 위해서는 암묵적 인자 드러내기라는 리팩터링 방법을 사용해야 한다. 암묵적 인자를 명시적인 인자로 바꾸는 것이다.
리팩터링 하기
- 명시적 인자를 추가한다.
- 함수 본문에 하드 코딩된 값을 추가된 인자로 바꾼다.
- 알맞게 수정한다.
예를 들어
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;
}
위의 코드의 경우 다음과 같은 단계로 수정하여 일반적인 하나의 함수로 바꿀 수 있다.
- 암묵적 인자(price)를 추가한다.
- 하드코딩된 값을 추가된 인자(field)로 바꾼다.
- 기존에 변경하고 싶었던 값(price)는 이제 value라는 이름으로 변경한다.
일급 값
일급 값의 정의
기존의 필드 명은 함수 이름에만 있었다. 하지만 이제 인자로 넘길 수 있는 값(문자열)이 되었다. 이로써 필드명이 일급 값으로 바뀐 것이다.
그렇다면 일급 값은 뭘까? 위 예제에서 나온 것처럼 문자열이나 그냥 숫자를 생각해보자. 숫자는 함수에 인자로 넘길 수 있고 리턴 값으로 받을수도 있다. 변수나 배열, 객체에도 넣을 수 있다. 이렇게 사용할 수 있는 값이 일급 값이다.
더 자세히 예를 들어보면, 이전에는 price라는 필드를 변경하고 싶으면 price를 변경하는 함수가 반드시 있어야 했다. 그리고 필드 명은 함수명에 포함되어 있었다. 이런 필드 명은 그냥 값처럼 사용할 수 없다. 위에 정의한대로 함수 명은 일급 값이 아니다.
반면 일급 값이 아닌 것에는 무엇이 있을까? 대표적인 것이 수식 연산자(+, -, * 등)이다. 이런 연산자들은 변수에 넣을 수도 없고 인자로 넘길 수도 없다. 또 반복문이나 조건문 등도 일급이 될 수 없다.
일급 값으로 바꾸기
위 예제말고 일급이 아닌 것들도 일급으로 바꿀 수 있을까? 일급 값을 설명할 때 수식 연산자는 일급이 아니라고 했다. 그렇다면 이런 것도 일급으로 바꿀 수 있을까?
자바스크립트에서 함수는 일급 값이다. 그래서 수식 연산자를 함수로 바꾸면 일급으로 바꿀 수 있다.
function plus(a, b) {
return a + b;
}
꼼수 같아 보이지만 이렇게 일급 값으로 바꾸면 활용할 수 있는 방법이 많아진다.
일급함수 vs. 고차함수
일급 함수
그럼 일급 함수는 무엇일까? 일급 함수는 일급 값의 특성을 가진 함수라고 생각하면 된다. 그리고 언어나 프레임워크가 함수를 일급 객체(데이터)로 다루는 성질을 가지고 있을 때, 그 언어나 프레임워크에서 함수는 "일급 함수"로 간주한다. 즉 언어에서 함수를 일급 객체로 다루면 그 언어에서 모든 함수는 일급 함수이다. JavaScript, Python, Ruby 같은 언어에서 일급 함수를 지원한다.
예를 들어 이런 일반적인 함수 역시 자바스크립트에서는 일급 함수이다. 이런 함수는 변수에 할당하거나, 다른 변수의 매개변수로 사용할 수 있다.
function plus(a, b) {
return a + b;
}
고차함수
고차함수란 인자를 함수로 받거나 리턴 값으로 함수를 리턴할 수 있는 함수를 말한다.
// 함수를 변수에 할당
const add = plus;
// 함수를 매개변수로 전달
function calculate(func, x, y) {
return func(x, y);
}
console.log(calculate(add, 5, 3)); // 출력 결과: 8
예를 들어 위에서 예를 든 plus 함수는 일급 함수이기 때문에 변수에 할당할 수 있다. 그리고 calculate함수의 경우 함수를 매개변수로 받고 있기 때문에 고차함수이다.
이런 고차 함수의 장점은 코드를 추상화할 수 있다는 점이다. 만약 뺄셈을 하는 함수가 필요하다면 새로운 calculate 함수를 만들지 않아도 된다. 그냥 새로운 뺄셈 함수를 변수에 할당해서 매개변수로 전달하면 된다.
콜백함수
고차함수에서 사용되는 함수를 콜백 함수라고 부른다. 다른 함수의 인자로 전달되어 나중에 실행되는 함수이다. 주로 비동기 작업이나 이벤트 처리에 사용한다. 이벤트 핸들러 함수 같은 함수가 콜백함수라고 볼 수 있다.
function performAsyncTask(callback) {
setTimeout(function() {
console.log("비동기 작업 완료");
callback();
}, 1000);
}
function myCallback() {
console.log("콜백 함수 호출");
}
performAsyncTask(myCallback);
위의 예제에서 myCallback 함수가 performAsyncTask 함수의 콜백 함수로 사용되고, 비동기 작업 완료 후에 호출된다.
리팩터링: 함수 본문을 콜백으로 바꾸기
콜백을 사용해서 리펙터링을 하는 방법에 대해 알아보려고 한다. 이런 방법을 사용하면 함수 내용이 일부만 중복되는 경우 이를 추상화할 수 있다는 장점이 있다.
단계
- 본문과 본문의 앞부분과 뒷부분을 구분한다.
- 전체를 함수로 빼낸다.
- 본문 부분을 빼낸 함수의 인자로 전달한 함수로 바꾼다.
예제
다음과 같은 비슷한 try/catch 구문이 있다고 해보자.
try {
saveUserData(user);
} catch (error) {
logToSnapErrors(error);
}
try {
fetchProduct(productId);
} catch (error) {
logToSnapErrors(error);
}
사실 try 문에서 동작하는 부분을 제외하고는 완전히 동일하다. 이런 try/catch 구문이 수십, 수백개가 있다면 어떨까? 그런데 동일하게 catch문만 바꾼다면? 일단 매번 try/catch문을 작성하는 것도 번거롭고 수정하기도 어렵다. 이 코드를 단계 별로 리팩터링 해보자.
먼저 첫 번째 단계는 본문을 구분하는 것이다. 본문은 saveUserData(user);와 fetchProduct(productId); 이다. 그리고 본문의 앞은 try, 본문의 뒷부분은 cathc문이다.
두 번째로 전체를 함수로 빼낸다.
function withLogging() {
try {
saveUserData(user);
} catch (error) {
logToSnapErrors(error);
}
}
withLogging();
마지막으로 본문 부분을 함수의 인자로 전달한다. 여기서는 saveUserData(user)를 전달한다.
function withLogging(f) {
try {
f(); // 본문이 있던 자리. 매개 변수로 받은 함수를 호출한다.
} catch (error) {
logToSnapErrors(error);
}
}
withLogging(function() {
saveUserData(user); // 익명 함수의 형태로 본문으로 전달
});
데이터 지향
👉 이벤트와 엔티티에 대한 사실을 표현하기 위해 일반 데이터 구조를 사용하는 프로그래밍 형식
일급 값은 변수나 일반 객체처럼 사용할 수 있다. 일종의 값처럼 사용할 수 있는 것이다. 이렇게 사용할 때의 장점은 일반적인 데이터 구조를 그대로 사용할 수 있다는 점이다.
예제에서 계속 등장하는 장바구니(cart)는 아주 일반적인 엔티티이다. 범용성이 높고 호출 그래프 상에서 가장 아래에 위치해 있다. 만약 이런 값을 특수한 인터페이스로 감싼다면 정해진 방법에 맞게 데이터를 다뤄야 한다. 이런 경우가 잦다면 매우 피곤할 것이다. 하지만 이렇게 일급 값으로 바꾸어 사용하면 범용적인 데이터 구조인 객체나 배열에 사용할 수 있게 된다.
또한 데이터를 여러가지 방법으로 해석할 수 있다. 값이 어떻게 사용될지 모르기 때문에 그때 그때 맞는 방법으로 사용할 수 있도록 하는 것이 중요하다.
'책 > 쏙쏙 들어오는 함수형 코딩' 카테고리의 다른 글
| chapter 1 (0) | 2023.10.25 |
|---|---|
| 스터디 시작 (0) | 2023.10.25 |