본문으로 바로가기

원본 데이터를 조작하는 경우 예상치 못한 결과를 낳을 수 있습니다. 코드의 앞부분에서 컬렉션의 무언가를 수정하면 훨씬 더 찾기 어려운 버그를 만들 수 있습니다. 조작이 항상 문제를 일으키는 것은 아니지만 잠재적으로 문제가 되는 것은 사실이므로, 가능하면 조작을 피하는 것이 좋습니다. 실제로 리덕스를 비롯한 인기 있는 자바스크립트 라이브러리는 원본을 건드는(조작) 함수를 허용하지 않습니다.

 

모던 자바스크립트의 상당수가 함수형 프로그래밍 형식을 취하기 때문에 부수 효과와 조작이 없는 코드를 작성해야 합니다. 배열을 조작하기 위해 흔히 사용하는 push() 메서드는 새로운 항목을 배열 뒤에 추가해 원본 배열을 변경합니다. 즉, push로 항목을 추가하면 원본 배열을 조작하는 셈입니다. 하지만, 펼침 연산자를 이용하면 원본 배열이 조작되는 부수 효과를 방지할 수 있습니다. 해결법을 알아보기 전에 먼저 push() 메서드로 인해 발생하는 문제부터 확인하겠습니다.

 

장바구니 상품 목록을 받아서 내용을 요약하는 간단한 함수입니다. 이 함수는 할인 금액을 확인하고 할인 상품이 두 개 이상이면 오류 객체를 반환합니다. 만약 오류가 없다면 상품을 많이 구매한 사람에게 사은품을 줍니다.

const cart = [
    {
        name: 'Pad',
        price: 20.55,
        discount: false
    },
    {
        name: 'Mouse',
        price: 12.55,
        discount: false
    },
    {
        name: 'Book',
        price: 120.55,
        discount: true
    },
]

const reward = {
    name: 'keyboard',
    price: 10.12,
    discount: true
}

const addFreeGift = (cart) => {
    if (cart.length > 2) {
        cart.push(reward);
        return cart;
    }

    return cart;
}

const summarizeCart = (cart) => {
    const discountable = cart.filter(item => item.discount);
    if (discountable.length > 1) {
        return {
            error: '할인 상품은 하나만 주문이 가능합니다.'
        }
    }

    const cartWithReward = addFreeGift(cart);
    return {
        doscounts: discountable.length,
        items: cartWithReward.length,
        cart: cartWithReward
    }
}

 

 

장바구니는 일반적인 배열이며, 사은품은 단순히 배열에 추가하는 항목입니다. 문제는 이 코드가 오류를 일으키기 일보 직전이라는 것입니다. 코드를 살펴보면서 문제를 찾아봅시다. 아래 코드는 저와 같은 개발자가 코드를 정리하기 위해 모든 변수를 함수의 상단으로 옮긴 것입니다.

const summarizeCart = (cart) => {
    const cartWithReward = addFreeGift(cart);
    const discountable = cart.filter(item => item.discount);
    if (discountable.length > 1) {
        return {
            error: '할인 상품은 하나만 주문이 가능합니다.'
        }
    }

    return {
        doscounts: discountable.length,
        items: cartWithReward.length,
        cart: cartWithReward
    }
}

 

 

이제 버그가 눈에 보입니다. 함수 addFreeGift()를 사용하면 배열 cart를 조작합니다. 상품이 세 개 이상이면, 할인이 적용된 아이템(free gift)이 추가됩니다. 반환 값, 즉 장바구니(cart)에 사은품을 추가해 새로운 변수에 할당하더라도, 원본 배열인 cart가 이미 조작된 후입니다. 결국 상품을 세 가지 이상 선택하고 그중 하나가 할인 상품인 모든 고객에게 오류가 발생합니다.

 

예제를 통해 살펴본 문제의 대부분은 분리된 함수에서 의도치 않게 원본을 조작한 것이 원인입니다. 사실 그 점이 조작이 위험해질 수 있는 이유입니다. 함수를 호출할 때는 함수에 전달한 값을 변경하지 않을 것이라는 신뢰가 필요합니다. 부수 효과가 없는 함수를 순수 함수라고 하며, 우리는 순수 함수를 만들기 위해 노력해야 합니다.

 

이제 위의 코드의 문제를 해결해 보도록 하겠습니다. 해결법은 펼침 연산자를 이용하면 너무나 간단하게 해결할 수 있습니다.

const addFreeGift = (cart) => {
    if (cart.length > 2) {
        return [...cart, reward];
    }

    return cart;
}

const summarizeCart = (cart) => {
    const cartWithReward = addFreeGift(cart);
    const discountable = cart.filter(item => item.discount);
    if (discountable.length > 1) {
        return {
            error: '할인 상품은 하나만 주문이 가능합니다.'
        }
    }
    
    return {
        doscounts: discountable.length,
        items: cartWithReward.length,
        cart: cartWithReward
    }
}

 

 

기존의 배열을 가져다 대괄호에 펼쳐 넣고, 새로운 상품을 배열의 마지막에 추가하면 됩니다. 결론적으로 내용을 목록으로 다시 쓰기만 하면 됩니다. 이렇게 하면 새로운 배열을 생성하기 때문에 원본 배열을 변경할 가능성은 전혀 없습니다. 원본 배열의 내용만 재사용해 새로운 배열을 만드는 것입니다.