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);
함수를 리턴하는 함수는 함수 팩토리와 같다
전체 프로그램을 고차 함수로 만들어도 될까?
가능하겠지만, 더 중요한 질문은 "정말 그것이 필요한가"임
고차 함수는 강력하지만, 비용이 따름.
직관적인 방법에 비해, 코드가 더 읽기 쉬운가? 중복 코드를 없앨 수 있는가? 코드가 하는 일이 무엇인지 쉽게 알 수 있는가? 등 질문들을 놓치면 안된다