[쏙쏙 들어오는 함수형 코딩 Ch10-11]
코드 냄새와 중복을 없애 추상화를 잘할 수 있는 리팩터링 두 가지
암묵적 인자를 드러내기 express implicit argument
코드의 냄새: 함수 이름에 있는 암묵적 인자
- 함수 구현이 거의 똑같음
- 함수 이름이 구현의 차이를 만듦
- ex.
cart = setPriceByName(cart, 'shoe', 13); cart = setQuantityByName(cart, 'shoe', 13); cart = setShippingByName(cart, 'shoe', 0); cart = setTaxByName(cart, 'shoe', 2.34);
리팩터링
- 함수 이름에 있는 암묵적 인자를 확인
- 명시적인 인자를 추가
- 일급 값으로 만드는 것이 중요
- 함수 본문에 하드 코딩된 값을 새로운 인자로 바꾸기
- 함수 호출부 고치기
- ex.
cart = setFieldByName(cart, 'shoe', 'price', 13); cart = setFieldByName(cart, 'shoe', 'quantity', 13); cart = setFieldByName(cart, 'shoe', 'shipping', 0); cart = setFieldByName(cart, 'shoe', 'tax', 2.34);
일급인 것과 일급이 아닌 것을 구별하기
- JS에서 일급이 아닌 것: 수식 연산자, 반복문, 조건문,
try
/catch
블록 - 일급으로 할 수 있는 것
- 변수에 할당
- 함수의 인자로 넘기기
- 함수의 리턴값으로 받기
- 배열이나 객체에 담기
- 일급이 아닌 것을 찾아 일급으로 바꾸는 기술이 FP에서 중요
- ex.
+
연산자는 변수에 할당할 수 없지만function plus(a, b)
와 같은 함수를 만들 수 있음
- ex.
일급 필드를 사용하면 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