우석
일급함수 1
리팩터링 기법 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 setShippingByName(cart, name, ship) {
var item = cart[name];
var newItem = objectSet(item, 'shipping', ship);
var newCart = objectSet(cart, name, newItem);
return newCart;
}
- 책에서는
암묵적 인자 드러내기
라는 리팩터링 기법을 소개한다. - 해당 기법을 적용할 수 있는 곳에서는 아래와 같은 특징이 있다.
- 함수 구현의 거의 똑같다.
- 함수 이름이 구현의 차이를 만든다.
- 위 3가지 메서드를 보면 Price, Quantity, Shipping이라는 메서드 내의 단어가 구현의 차이를 만든다.
- 이 단어들이 암묵적인 인자를 뜻한다.
function setFieldByName(cart, name, field, value) {
var item = cart[name];
var newItem = objectSet(item, field, value);
var newCart = objectSet(cart, name, newItem);
return newCart;
}
- 인묵적 인자를 메서드 명에서 드러내고 메서드 파라미터로 바꾸면 3개의 개별적인 메서드는 하나의 메서드로 리팩터링이 가능하다.
- 이는 메서드 명에 존재하던 필드명이라는 인자를
일급값
으로 만든 것.- 일급값: 함수에 인자로 넘길 수 있고, 함수 리턴값으로 사용할 수 있고, 변수에 넣을 수 있는 값을 뜻한다.
- 단, field 값의 정확성을 위해서는 컴파일 타임 혹은 런타임에 검사하는 로직을 추가하면 좋다.
- 컴파일 타임: 정적 타입 시스템에서 Java의 Enum과 같은 타입을 이용하면 된다.
- 런타임: Set 구조에 가능한 field를 담아두고 매 요청마다 검사를 진행한다.
리팩터링 기법 2: 본문을 콜백으로 바꾸기
function cookAndEatFoods() {
for (var i = 0; i < foods.length; i++) {
var food = foods[i];
cook(food);
eat(food);
}
}
function cleanDishes() {
for (var i = 0; i < dishes.length; i++) {
var dish = dishes[i];
wash(dish);
dry(dish);
putAway(dish);
}
}
- 2개의 메서드는 반복문을 포함한 비슷한 로직을 수행한다.
- 우선 1차적으로 리팩터링 기법 1을 적용해 암묵적 인자를 드러내보자
function cookAndEatArray(array) {
for (var i = 0; i < array.length; i++) {
var item = array[i];
cook(item);
eat(item);
}
}
function cleanArray(array) {
for (var i = 0; i < array.length; i++) {
var item = array[i];
wash(item);
dry(item);
putAway(item);
}
}
- 암묵적 인자 드러내기를 통해 Foods와 Dishes를 일급값(array)로 변경했다.
- 하지만 메서드 명에는 여전히 암묵적 인자가 존재해 보인다.
- 이번 인자는 특정 데이터가 아닌 동작을 뜻하기 때문에 해당 동작을 드러내 변수로 변경해보자
function forEach(array, f) {
for (var i = 0; i < array.length; i++) {
var item = array[i];
f(item);
}
}
function cookAndEat(food) {
cook(food);
eat(food);
}
function clean(dish) {
wash(dish);
dry(dish);
putAway(dish);
}
- 이렇게 리팩터링이 마무리 됐다.
- 결과적으로 forEach()는 고차 함수로 정의되었다.
- 고차 함수: 인자로 함수를 받거나 함수를 리턴하는 것
- 지금까지 진행된 본문을 콜백으로 바꾸기 리팩터링은 아래와 같은 단계를 거친다.
- 본문과 본문의 앞/뒤를 구분한다.
- 본문을 함수로 빼낸다.
- 빼낸 본문을 함수 인자로 전달한다.
- 이번 장에서는 2가지 리팩터링 방법과 일급 값, 일급 함수, 고차 함수라는 개념을 배웠다.
- 앞으로 이 개념들을 활용하는 것의 강점에 대해 설명해줄듯
만혁
일급 함수 1
코드의 냄새: 함수 이름에 있는 암묵적 인자
- 특징
- 거의 똑같이 구현된 함수가 있다.
- 함수 이름이 구현에 있는 다른 부분을 가리킨다.
리팩터링: 암묵적 인자를 드러내기
- 단계
- 함수 이름에 있는 암묵적 인자를 확인한다.
- 명시적인 인자를 추가한다.
- 함수 본문에 하드 코딩된 값을 새로운 인자로 바꾼다.
- 함수를 호출하는 곳을 고친다.
리팩터링: 함수 본문을 콜백으로 바꾸기
- 단계
- 함수 본문에서 바꿀 부분의 앞부분과 뒷부분을 확인한다.
- 리팩터링 할 코드를 함수로 빼낸다.
- 빼낸 함수의 인자로 넘길 부분을 또 다른 함수로 빼낸다.
암묵적 인자 드러내기
// as-is
function setPriceByName(cart, name, price) {
const item = cart[name];
const newItem = objectSet(item, 'price', price);
const newCart = objectSet(cart, name, newItem);
return newCart;
}
function setQuantityByName(cart, name, quantity) {
const item = cart[name];
const newItem = objectSet(item, 'quantity', quantity);
const newCart = objectSet(cart, name, newItem);
return newCart;
}
cart = setPriceByName(cart, "shoe", 13);
cart = setQuantityByName(cart, "shoe", 3);
cart = setShippingByName(cart, "shoe", 0);
cart = setTaxByName(cart, "shoe", 2.34);
// to-be
function setFieldByName(cart, name, field, value) {
const item = cart[name];
const newItem = objectSet(item, field, value);
const newCart = objectSet(cart, name, newItem);
return newCart;
}
cart = setFieldByName(cart, "shoe", "price", 13);
cart = setFieldByName(cart, "shoe", "quantity", 3);
cart = setFieldByName(cart, "shoe", "shipping", 0);
cart = setFieldByName(cart, "shoe", "tax", 2.34);
연습 문제
함수 이름에 있는 암묵적 인자 드러내기
// as-is
function multiplyByFour(emplyoees) {
return x * 4;
}
function multiplyByPir(emplyoees) {
return x * 4;
}
// to-be
function multiply(x, y) {
return x * y;
}
// as-is
function incrementQuantityByName(cart, name) {
const item = cart[name];
const quantty = item['quantity'];
const newQuantity = quantity + 1;
const newItem = objectSet(item, 'quantity', newQuantity);
const newCart = objectSet(cart, name, newItem);
return newCart;
}
function incrementSizeByName(cart, name) {
const item = cart[name];
const quantty = item['size'];
const newSize = size + 1;
const newItem = objectSet(item, 'size', newSize);
const newCart = objectSet(cart, name, newItem);
return newCart;
}
// to-be
function incrementFieldByName(cart, name, field) {
const item = cart[name];
const value = item[field];
const newValue = value + 1;
const newItem = objectSet(item, field, newValue);
const newCart = objectSet(cart, name, newItem);
return newCart;
}
// as-is
function incrementFieldByName(cart, name, field) {
const item = cart[name];
const value = item[field];
const newValue = value + 1;
const newItem = objectSet(item, field, newValue);
const newCart = objectSet(cart, name, newItem);
return newCart;
}
// to-be
function incrementFieldByName(cart, name, field) {
const supports = ['size', 'quantity'];
if (!supports.includes(field)) {
throw "not supported field"
}
const item = cart[name];
const value = item[field];
const newValue = value + 1;
const newItem = objectSet(item, field, newValue);
const newCart = objectSet(cart, name, newItem);
return newCart;
}
함수 본문을 콜백으로 바꾸기
// as-is
try { // 앞부분
saveUserData(user); // 본문
} catch (error) { // 뒷부분
logToSnapErrors(error); // 뒷부분
} // 뒷부분
try { // 앞부분
fetchProduct(id); // 본문
} catch (error) { // 뒷부분
logToSnapErrors(error); // 뒷부분
} // 뒷부분
// to-be
// 1. 함수로 빼내기
function withLogging() {
try {
saveUserData(user);
} catch (error) {
logToSnapErrors(error);
}
}
// 2. 콜백으로 빼내기
function withLogging(f) {
try {
f();
} catch (error) {
logToSnapErrors(error);
}
}
withLogging(function() { saveUserData(user) }) // 인라인으로 함수 선언
withLogging(() => saveUserData(user)) // 이것도 되겠지~
회독
- 코드를 일단 작성하고 책에서 말하는 순서대로 리팩토링하는게
- 최대한 안전하게 변경하는 방법인거 같다
- 로깅 함수의 경우 활용도가 꽤 높아보임ㅎㅎ