자바스크립트는 함수형과 객체지향을 모두 지원하는 멀티 패러다임 언어입니다.
과연 나는 평상시에 프론트엔드 개발을 진행하며 함수형 프로그래밍 방식으로 잘 개발하고 있었을까? 라는 의문이 들어
에릭 노먼드의 '쏙쏙 들어오는 함수형 코딩'을 읽게 되었습니다.
이 책은 평상시에 단순히 효율화를 위해 사용하던 자바스크립트의 map, reduce 등의 기본적인 배열 메서드에 대해 왜 이런 방식으로 개발해야 하는지 다시 한 번 생각하게 해주었고,
코드를 어떻게 하면 조금 더 클린 코드로 작성할 수 있을지에 대해 어느 정도 방향성을 알려준 책이였습니다.
책에서 반복적으로 강조하는 개념이 있는데, 액션, 계산, 데이터의 분리입니다.
액션은 부수 효과가 있는 실행 시험과 횟수에 의존하는 함수이고,
계산은 순수 함수로 단순히 계산을 다루고,
데이터는 이벤트에 대한 사실을 나타내는 값으로 우리가 흔히 생각하는 값 그 자체를 의미합니다.
평상시에 개발을 진행하며, 어렴풋이 클린 코드를 위해서 함수는 하나의 기능만 제공해야 한다는 점을 중점으로 열심히 함수를 나누고 있었지만,
정작 이를 조금 더 넓은 시야에서 고민하지는 못했는데, 액션과 계산이라는 명확한 개념 분리를 통해 이를 조금 더 깊게 이해할 수 있었습니다.
쉽게 예시를 들면 아래와 같습니다.
function update_shipping_icons() {
let buy_buttons = get_buy_butttons_dom();
for(let i = 0; i < buy_buttons.length; i++) {
let button = buy_buttons[i];
let item = button.item;
if(item.price + shopping_cart_total >= 20) {
button.show_free_shipping_icon();
} else {
button.hide_free_shipping_icon();
}
}
}
아이템의 가격과 장바구니의 합이 20이 넘어가면 무료 배송 아이콘을 추가하는 코드인데,
보통의 경우에 이정도의 계산은 따로 리팩토링하지 않고 코드에 같이 추가해 넣는 경우가 많았습니다.
만약 추가 요구사항으로 아이템의 가격과 장바구니의 합이 20이 넘어가는 경우에 무료 배송 아이콘 추가 뿐만이 아닌,
가격에 굵은 강조 표시를 하거나, 색을 변경한다고 치면 if문 하위에 해당 엑션을 점점 추가해 주어야 합니다.
이 뿐만이 아닌, 장바구니 내부에 들어가서 장바구니 제품을 추가했을 때,
무료 배송이면 추가 버튼을 강조하는 영역이 있다면 그 때는 이와 비슷한 함수를 추가해서 로직을 작성해 주어야 합니다.
하지만 단순한 계산 로직인 item.price + shopping_cart_total >= 20
를 아래와 같이 명시적으로 분리한다면,
어느 곳에서든 해당 함수를 재사용하며 코드의 확장성을 키워나갈 수 있을 것입니다.
function get_free_shipping(cart) {
return calc_total(cart) >= 20;
}
이 책에서 인상깊었던 부분 중 하나는 설계하는 방법입니다.
대표적으로 계층형 설계 패턴을 통해 어떻게 하면 요구사항에 대해 더욱 확장성 있고, 명확하게 설계할 수 있는지에 대해 알려줍니다.
개발팀과 마케팅팀의 입장에서 예시를 들며
어떻게 하면 마케팅팀이 개발팀이 짠 코드를 모두 알지 못해도 기능을 추가할 수 있는지 '추상화 벽'에 대한 개념을 알려주고,
계층형 구조를 통해 코드의 확장성을 증가시키는 방법을 알려줍니다.
간단한 예시로 '넥타이 하나를 사면 무료로 넥타이 클립을 하나 제공한다' 로직을 통해 위 개념을 이해할 수 있습니다.
function freeTieClip(cart) {
let hasTie = false;
let hasTieClip = false;
for(let i = 0; i< cart.length; i++) {
let itme = cart[i];
if(item.name === 'tie') {
hasTie = true;
}
if(item.name === 'tie clip') {
hasTieClip = true;
}
}
if(hasTie && !hasTieClip) {
let tieClip = make_item('tie clip', 0);
return add_item(cart, tieClip);
}
return cart;
}
장바구니에 넥타이가 있고 넥타이 클립이 없으면 0원 가격으로 넥타이 클립을 장바구니에 추가하는 로직이 담겨 있습니다.
이를 리팩토링 하면 아래과 같이 액션에서 계산들을 분리할 수 있습니다.
function freeTieClip(cart) {
let hasTie = isInCart(cart, 'tie');
let hasTieClip = isInCart(cart, 'tie clip');
if(hasTie && !hasTieClip) {
let tieClip = make_item('tie clip', 0);
return add_item(cart, tieClip);
}
return cart;
}
function isInCart(cart, itemName) {
for(let i = 0; i< cart.length; i++) {
let item = cart[i];
if(item.name === name) {
return true;
}
}
return false;
}
리팩토링한 코드를 시각화하면 아래와 같이 보여집니다.
freeTieClip은 makeItme, addItem, isinCart 함수들을 의존하고,
이 함수들은 해당 함수들이 참조하는 더 하위의 함수들과 자바스크립트의 기본 기능들을 참고합니다.
위에서 아래로 갈수록 재사용성이 높으며, 자주 바뀌지 않을 로직들이 자리하게 됩니다.
for loop, array index는 많은 곳에서 쓰여지기 때문에 이 부분의 로직이 바뀐다면 이를 사용하고 있는 상위 계층들의 로직 또한 모두 달라지게 됩니다.
프론트엔드 개발자로써 많은 사람들이 고차 함수를 무의식적으로 사용하고 있었을 것이라 생각합니다.
자바스크립트에서 배열 메서드로 기본적으로 제공되는 map, reduce, filter 등으로 인해 이미 우리는 고차 함수를 지겹도록 써왔지만, 막상 고차 함수를 왜 사용하고 어떠한 장점이 있는지 깊게 생각하지 못했을 수도 있습니다.
고차 함수는 코드의 재사용성을 높이며, 세부 구현을 고차 함수 내부에 캡슐화 함으로써 코드의 추상화 수준을 높일 수 있습니다.
function emailsForCustomers(customers, goods, bests) {
let emails = [];
forEach(customers, function(customer) {
let email = emialForCustomer(customer, goods, bests);
emails.push(email);
});
return emails;
};
function custmerFullNames(customers) {
let fullNames = [];
forEach(customers, function(customer) {
let name = customer.firstName + ' ' + customer.lastName;
fullNames.push(name)
});
return fullNames;
};
고차 함수를 사용하지 않는다면, 배열을 반복하면서 배열의 요소를 변화시킨다는 하나의 추상적인 요청을 공통화 시키기 어렵습니다.
고차 함수를 사용한다면, 이 개념을 map이라는 고차 함수를 통해 추상화시켜 세부 구현을 위임할 수 있습니다.
function map(array, callback) {
let newArray = [];
forEach(array, function(element) {
newArray.push(element);
});
};
function emailsForCustomers(customers, goods, bests) {
let emails = [];
return map(customers, function(customer) {
return emialForCustomer(customer, goods, bests);
});
};
function custmerFullNames(customers) {
let fullNames = [];
return map(customers, function(customer) {
return customer.firstName + ' ' + customer.lastName;
});
};
마지막으로 반응형 아키텍처에 대한 개념입니다.
반응형 아키텍처를 한 마디로 정의한다면 "원인과 효과를 분리해 유연한 타임라인을 갖추기." 입니다.
조금 어려울 수도 있지만 관련해서 조금만 내용을 알게 된다면 쉽게 이해할 수 있습니다.
예시로 기존의 아키텍처로는 장바구니가 변경될 때마다 '무료 배송 아이콘'을 업데이트 해야 한다면,
기존의 아키텍처로는 장바구니가 변경되는 진입점마다 '무료 배송 아이콘' 업데이트 액션이 들어가야 했습니다.
장바구니에 제품을 추가하거나, 장바구니의 제품이 제거되는 시점마다 매번 이런 작업을 해주는 것은 번거로울 뿐만이 아니라,
다른 진입점이 생긴다면(장바구니 비우기 등), 해당 진입점에도 관련 액션을 추가해 주어야 했습니다.
function add_item_to_cart() {
...
update_shipping_icons()
}
function remove_item_to_cart() {
...
update_shipping_icons()
}
function clear_cart() {
...
update_shipping_icons()
}
반응형 아키텍처에서는
원인을 '장바구니에 제품 추가, 장바구니에 제품 제거, 장바구니 비우기'.
효과를 '무료 배송 아이콘 업데이트' 라고 판단합니다.
위 코드는 '장바구니의 제품이 추가 -> 무료 배송 아이콘 업데이트' 와 같이 원인과 효과가 강하게 결합되어 있습니다.
반응형 아키텍처에서는 이를 '관찰자' 개념을 통해 두 개념을 명확하게 분리시킵니다.
위 코드를 자세히 보면 모든 원인들은 공통적으로 장바구니의 변경과 연관성을 가집니다.
반응형 아키텍처를 적용한다면, 아래와 같은 생각이 가능합니다.
'장바구니 변경을 관찰해, 장바구니의 변경이 일어났을 때 무료 배송 아이콘을 업데이트 해준다면 강한 결합을 해제시킬 수 있지 않을까?'
코드로 보면 다음과 같습니다.
function add_item_to_cart() {
...
}
function remove_item_to_cart() {
...
}
function clear_cart() {
...
}
shopping_cart.addWatcher(update_shipping_icons());
리뷰에서는 '쏙쏙 들어오는 함수형 코딩'을 읽으며 저에게 의미있게 다가오는 챕터 위주로 간단하게 정리를 했습니다.
하지만 실제로는 이보다 더 많은 내용들이 담겨 있다는 점 참고해 주세요.
이 책은 신입 개발자가 읽기에는 조금 어려울 수도 있지만, 어느정도 개발 경력이 있는 1~2년차 개발자 이상부터는 한 번쯤은 꼭 읽고가야 하는 책이라는 생각이 듭니다.
이 책을 읽으면서, 기존에 react를 개발하며 약간은 놓치고 있었던 함수형 프로그래밍의 중요성과 개념을 다시 한 번 다질 수 있게 해주고,
코드를 작성하는 데에 있어서 많은 영감을 제공해 주었습니다.
또한, 책에서 읽은 개념들을 바로 코드와 설계에 적용하기에 수월했던 점도 큰 장점을 다가오는 것 같습니다.
~마지막으로 부수 효과로 팀원에게 칭찬을 들을 수도 있었습니다. ㅋㅋㅋㅋ~