`forEach` vs `reduce`
함수형 프로그래밍 관점에서의 배열 메서드 선택
forEach를 지양해야 하는 이유
1. 부수효과(Side Effect) 발생
forEach는 본질적으로 부수효과를 발생시키는 메서드입니다.
// ❌ forEach - 부수효과 발생
let total = 0;
numbers.forEach(num => {
total += num; // 외부 변수를 수정 (부수효과)
});
// ✅ reduce - 순수함수
const total = numbers.reduce((sum, num) => sum + num, 0);
2. 함수형 프로그래밍 원칙 위배
함수형 프로그래밍에서는 불변성(Immutability)과 순수함수(Pure Function)를 지향합니다.
// ❌ forEach - 기존 배열을 수정
const users = [{name: 'John', active: false}];
users.forEach(user => {
user.active = true; // 원본 데이터 수정
});
// ✅ map - 새로운 배열 반환
const activeUsers = users.map(user => ({
...user,
active: true
}));
3. 의도가 불분명
forEach는 “무엇을 하는지” 명확하지 않습니다.
// ❌ forEach - 의도 불분명
const results = [];
items.forEach(item => {
if (item.price > 100) {
results.push(item.name);
}
});
// ✅ filter + map - 의도 명확
const results = items
.filter(item => item.price > 100)
.map(item => item.name);
상황별 적절한 배열 메서드 선택
1. 데이터 변환 - map 사용
// 가격에 세금 추가
const productsWithTax = products.map(product => ({
...product,
priceWithTax: product.price * 1.1
}));
2. 데이터 필터링 - filter 사용
// 활성 사용자만 선택
const activeUsers = users.filter(user => user.isActive);
3. 누적 계산 - reduce 사용
// 총합 계산
const totalPrice = orders.reduce((sum, order) => sum + order.amount, 0);
// 객체 생성
const userMap = users.reduce((map, user) => {
map[user.id] = user;
return map;
}, {});
4. 조건 확인 - some/every 사용
// 하나라도 조건을 만족하는지
const hasExpensiveItem = products.some(product => product.price > 1000);
// 모든 항목이 조건을 만족하는지
const allInStock = products.every(product => product.stock > 0);
실무 적용 사례
전자상거래 할인 계산 개선
// ❌ forEach 사용 (부수효과)
function calculateDiscountedPrice(basePrice, discounts) {
let finalPrice = basePrice;
discounts.forEach(discount => {
if (discount.type === 'percentage') {
finalPrice *= (1 - discount.value / 100);
} else {
finalPrice -= discount.value;
}
});
return finalPrice;
}
// ✅ reduce 사용 (함수형)
function calculateDiscountedPrice(basePrice, discounts) {
return discounts.reduce((price, discount) => {
if (discount.type === 'percentage') {
return price * (1 - discount.value / 100);
} else {
return price - discount.value;
}
}, basePrice);
}
데이터 처리 파이프라인
// ❌ forEach로 여러 단계 처리
const processedData = [];
rawData.forEach(item => {
if (item.status === 'active') {
const processed = {
id: item.id,
name: item.name.toUpperCase(),
score: item.score * 1.1
};
processedData.push(processed);
}
});
// ✅ 메서드 체이닝으로 명확한 파이프라인
const processedData = rawData
.filter(item => item.status === 'active')
.map(item => ({
id: item.id,
name: item.name.toUpperCase(),
score: item.score * 1.1
}));
성능 비교
벤치마크 테스트
const largeArray = Array.from({length: 1000000}, (_, i) => i);
// forEach 성능 테스트
console.time('forEach');
let sum1 = 0;
largeArray.forEach(num => sum1 += num);
console.timeEnd('forEach');
// reduce 성능 테스트
console.time('reduce');
const sum2 = largeArray.reduce((sum, num) => sum + num, 0);
console.timeEnd('reduce');
// for 루프 성능 테스트 (가장 빠름)
console.time('for');
let sum3 = 0;
for (let i = 0; i < largeArray.length; i++) {
sum3 += largeArray[i];
}
console.timeEnd('for');
일반적인 성능 순서: for > reduce > forEach
forEach를 사용해도 되는 경우
1. 순수한 부수효과가 목적인 경우
// 로깅, DOM 조작 등
users.forEach(user => {
console.log(`Processing user: ${user.name}`);
});
// 이벤트 리스너 등록
buttons.forEach(button => {
button.addEventListener('click', handleClick);
});
2. 외부 API 호출
// 각 사용자에게 이메일 발송
users.forEach(async user => {
await sendEmail(user.email, message);
});
함수형 프로그래밍 베스트 프랙티스
1. 메서드 체이닝 활용
const result = data
.filter(item => item.isValid)
.map(item => item.value)
.reduce((sum, value) => sum + value, 0);
2. 불변성 유지
// ❌ 원본 수정
const updateUser = (users, id, updates) => {
const user = users.find(u => u.id === id);
Object.assign(user, updates);
return users;
};
// ✅ 새로운 객체 반환
const updateUser = (users, id, updates) => {
return users.map(user =>
user.id === id ? { ...user, ...updates } : user
);
};
3. 순수함수 작성
// ❌ 부수효과 있는 함수
let counter = 0;
const impureFunction = (arr) => {
counter++; // 외부 상태 변경
return arr.length;
};
// ✅ 순수함수
const pureFunction = (arr) => {
return arr.length;
};
기타
배열 메서드 선택 가이드
- 변환이 목적인가? →
map사용 - 필터링이 목적인가? →
filter사용 - 누적 계산이 목적인가? →
reduce사용 - 조건 확인이 목적인가? →
some/every사용 - 순수한 부수효과가 목적인가? →
forEach사용 가능
함수형 프로그래밍 체크리스트
- 함수가 순수함수인가?
- 불변성을 유지하고 있는가?
- 부수효과를 최소화했는가?
- 의도가 명확하게 드러나는가?
댓글