0w0

.map() 남용을 멈추자.

가끔 코드 리뷰를 하거나 StackOverFlow를 방문하면 종종 다음과 같은 코드 스니펫을 볼 때가 있습니다.

const fruitIds = ['apple', 'oragne', 'banana'];

fruitIds.map((id) => {
  document.getElementById(`fruit-${id}`).classList.add('actvie');
});

보다시피 fruitIds 배열의 모든 요소를 특정 DOM을 이용해 HTML 요소에 활성 클래스를 추가하는 반복문입니다.

많은 프로그래머(특히 신규 프로그래머)는 위의 코드가 문제있다는 것을 알아차리지 못할 것입니다.

그러나 여기에 한 가지 중요한 문제가 있습니다. 바로 .map()입니다.

지금부터 이것에 대해 설명하려 한합니다.

.map()이 뭐가 문제인가요?

이렇게 배열을 다루는 것에는 전혀 문제가 없습니다.

오히려 저는 이게 무척 편리하고 아름답게 반복 패턴을 하나로 매핑한다 생각합니다.

간단하게 말하면 매핑이란 컬렉션의 각 요소에 함수를 적용해 그 함수로 인해 요소가 변경되어 새로운 컬렉션을 반환하는 조작입니다.

예를 들어 숫자 배열 const nums = [1, 2, 3, 4];가 있는데 이를 2를 곱한 배열을 만들고 싶다면 원래 배열을 새로운 배열로 매핑하는데, 다음과 같이 합니다(JavaScript 기준)

const biggerNums = nums.map((n) => n * 2);
// >> [2, 4, 6 ,8];

biggerNums 배열은 본래 nums 배열에 2배를 한 것입니다.

.map()이 어떻게 사용되는가 주목해주시길 바랍니다.

이 메서드의 결과는 biggerNums 배열로 할당됩니다.

매핑이란 새로운 요소의 집합을 반환하는 조작이라는 것입니다.

글 초반의 코드가 문제가 있다는 뜻은 이 부분입니다.

.map()은 이미 새로운 배열을 반환하는데 그 배열이 필요하지 않은 경우에는 애초에 .map()를 사용할 필요가 없습니다.

이러한 특수한 케이스(단순 반복)에는 다른 메서드 .forEach()를 사용해야합니다.

(이런 경우를 위해 설계되어 있습니다.)

이 메서드는 새로운 컬렉션을 반환하는게 아니라 단순하게 배열을 돌면서 각 요소에 콜백 함수를 적용해 각 요소에 무엇인가 하기 때문입니다.

그러니 앞의 스니펫에 올바른 버전은 이렇습니다.

// good way
const fruitIds = ['apple', 'oragne', 'banana'];
fruitIds.forEach((id) => {
  document.getElementById(`fruit-${id}`).classList.add('active');
});

새로운 배열이 필요하지 않으므로 단순하게 fruitIds 배열을 반복하면서 배열 각 요소 HTML에 active 클래스를 붙입니다.

.map().forEach()보다 짧게 쓸 수 있는데 뭐가 문제일까요?

.map() 남용의 악영향

.map()을 악용하는 최악의 결과는 새로운 새 중복 배열을 반환한다는 것입니다.

구체적으로 표현하면 이 메서드가 호출된 것과 같은 크기의 새 배열을 반환합니다.

즉, 1000개 요소의 배열이 있는 경우 .map()매번 1000개 요소의 새 배열을 반홥합니다.

JavaScript에서 모든 함수는 값을 반환합니다. return 키워드를 사용하지 않더라도 함수는 암묵적으로 undefined 상태로 반환됩니다.

언어가 디자인된 방식이 그렇기 때문입니다.

이 규칙은 콜백에도 적용됩니다. 콜백도 함수이기 때문입니다.

충분히 설명한 것 같으니 본래 예시로 돌아가 봅시다.

// wrong way
const fruitIds = ['apple', 'oragne', 'banana'];
fruitIds.map((id) => {
  document.getElementById(`fruit-${id}`).classList.add('active');
});

여기에는 무슨 일이 일어나고 있는걸까요?

fruitIds 배열이랑 같은 크기의 다른 배열이 맵핑됩니다.

.map()으로 반환된 배열은 사용되지도 않는데, 메모리를 사용합니다.

이 새로운(사용도 하지 않는) 배열은 다음처럼 됩니다.

[undefined, undefined, undefined];

이것은 .map() 메서드에 전달된 콜백에 return 키워드가 없기에, 알고 계시겠지만 암묵적으로 undefined를 반환합니다.

나쁜 것 같지 않나요? 예, 어마어마하게 나쁩니다.

이 예시에서는 심각한 악영향은 없었습니다.

배열에 요소가 3개밖에 없었기 때문입니다.

3개 요소의 배열을 해봤자 크게 문제는 일어나지 않습니다.

그러나 문제는 복잡한 데이터 큰 배열을 다룰 경우에 발생합니다.

5000개 오브젝트가 있는 배열을 반복할 경우에 .map()을 남용하면 5000개의 요소가 있는 다른 배열이 작성합니다. 결국 10000개 요소를 메모리에 저장하게 되는데, 그중 절반은 중복입니다. 이는 최적화되어 있지 않아 어떤 경우에는 어플리케이션 과부하를 초래할 수도 있습니다. 이것이 우리가 올바른 일을 하기 위해 올바른 메서드를 선택해야 하는 이유입니다.

정리

본질적으로 나쁜 관행들이 많이 있지만, 부정적인 결과는 더 큰 데이터셋을 다룰 때만 눈에 보일 것 입니다.

이러한 관행 중 하나가 .map()의 남용입니다. 작은 배열을 조작할 때는 딱히 문제가 되지는 않겠지만, 더 큰 배열에서는 이러한 실수로 인해서 어플리케이션이 과부하되어 디버깅이 상당히 어려워 질 수도 있습니다.

그렇기에 우리는 이러한 남용을 절대 그냥 지나쳐서는 안됩니다. 우리는 이것을 세심히 다뤄야 합니다. 이제 그 이유를 이해해셨으면 좋겠습니다.