chp14. 중첩된 데이터에 함수형 도구 사용하기
앞 장에서는 배열을 다루는 함수형 도구를 살펴봤습니다.
이번 장에서는 객체를 다루는 함수형 도구에 대해 알아보고, 특히 중첩된 데이터를 재귀적으로 안전하게 접근하는 방법을 다룹니다.
객체 리팩토링하기
예를 들어, 객체의 quantity 필드를 1 증가시키는 함수는 다음과 같이 작성할 수 있습니다.
jsx
function incrementQuantity(item) {
var quantity = item["quantity"];
var newQuantity = quantity + 1;
var newItem = objectSet(item, "quantity", newQuantity);
return newItem;
}
function incrementSize(item) {
var size = item["size"];
var newSize = size + 1;
var newItem = objectSet(item, "size", newSize);
return newItem;
}문제는 함수 이름에 암묵적 인자(필드 이름)가 숨어 있다는 점입니다.
이 경우 암묵적 인자를 드러내기 리팩토링을 적용할 수 있습니다.
1차 리팩토링
jsx
function incrementField(item, field) {
var value = item[field];
var newValue = value + 1;
var newItem = objectSet(item, field, newValue);
return newItem;
}이제 어떤 필드를 증가시킬지 명시적으로 드러납니다.
하지만 여전히 decrement, double 같은 변형이 생기면 중복이 발생합니다.
2차 리팩토링 (본문을 콜백으로 바꾸기)
jsx
function update(object, key, modify) {
var value = object[key];
var newValue = modify(value);
var newObject = objectSet(object, key, newValue);
return newObject;
}update는 고차 함수로 변환되어 값의 변형 로직을 외부에서 주입합니다.objectSet을 통해 원본을 훼손하지 않는 카피-온-라이트 원칙을 유지합니다.
중첩된 객체 리팩토링하기
객체 안에 또 다른 객체가 있을 때는 update만으로는 부족합니다.
jsx
const shirt = {
options: {
color: "blue",
},
};단순히 update를 여러 번 중첩 호출하면 코드가 장황해지고 유지보수가 어려워집니다.
jsx
function update2(object, key1, key2, modify) {
return update(object, key1, function (value1) {
return update(value1, key2, modify);
});
}따라서 재귀 함수를 활용해 일반화할 수 있습니다.
재귀를 통한 일반화
jsx
function nestedUpdate(object, keys, modify) {
if (keys.length === 0)
// 종료 조건
return modify(object);
var key1 = keys[0];
var restOfKeys = drop_first(keys);
return update(object, key1, function (value1) {
return nestedUpdate(value1, restOfKeys, modify); // 재귀 호출
});
}keys배열에 깊이만큼의 키를 전달keys.length === 0일 때modify실행- 그렇지 않으면 첫 번째 키로 접근 후 나머지 키에 대해 재귀 호출
안전한 재귀 작성 규칙
- 종료 조건: 더 이상 자신을 호출하지 않는 조건이 반드시 필요하다.
- 재귀 호출: 최소 한 번 이상은 자기 자신을 호출해야 한다.
- 종료 조건에 다가가기: 호출을 반복할수록 종료 조건에 가까워져야 한다.
깊이 중첩된 데이터와 추상화 벽
직접 경로를 다 쓰면 코드가 복잡해집니다.
jsx
nestedUpdate(blogCategory, ["posts", "12", "author", "name"], capitalize);이 코드는 내부 구조를 너무 많이 알아야 하고, 기억하기도 어렵습니다.
따라서 추상화 벽(Abstraction Barrier)을 적용합니다.
추상화 벽 적용 후
jsx
function updatePostById(category, id, modifyPost) {
return nestedUpdate(category, ["posts", id], modifyPost);
}
function updateAuthor(post, modifyUser) {
return update(post, "author", modifyUser);
}
function capitalizeName(user) {
return update(user, "name", capitalize);
}
// 사용 예시
updatePostById(blogCategory, "12", function (post) {
return updateAuthor(post, capitalizeUserName);
});- 내부 구조를 몰라도 사용할 수 있습니다. → 기억 부담 감소
- 함수 이름이 동작을 드러내므로 이해하기 쉬워집니다. → 가독성 증가
정리하며
이번 장에서는 객체를 다루는 함수형 도구를 살펴보고, 특히 중첩된 데이터를 재귀적으로 안전하게 접근하는 방법을 다뤘습니다.
또한 깊은 경로를 직접 나열하는 대신 추상화 벽을 세우면 내부 구조를 알 필요 없이 함수를 사용할 수 있습니다. 그리고 함수 이름만으로 의도가 드러나 복잡성을 줄일 수 있습니다.