Programacao & Dev

forEach esconde a intenção do código. Os outros métodos não.

forEach é genérico demais. Métodos como map, filter e reduce declaram a intenção do código antes do corpo, facilitando a leitura e manutenção. Saiba quando usar cada um.

Equipe Blueprintblog1 min
forEach esconde a intenção do código. Os outros métodos não.

forEach não é errado. É genérico demais.

Quando alguém lê um loop forEach, precisa ler o corpo inteiro antes de entender o que ele faz. Transforma dados? Filtra resultados? Procura um item? Valida uma condição? Impossível saber pelo nome do método.

map, filter, find, some, every, reduce — cada um declara a intenção antes do corpo. O nome do método já é documentação.

Esse é o argumento real do functional programming em JavaScript. Não performance, não elegância. Clareza de intenção.


map() — transformação sem modificar o original

forEach com push é o padrão mais comum pra transformar arrays. Funciona. Mas mistura a iteração com a acumulação, e o leitor precisa percorrer tudo antes de entender que o resultado é uma lista transformada.

js
// ❌ intenção invisível até a última linha
const nomes = [];
usuarios.forEach(usuario => {
  if (usuario.ativo) {
    nomes.push(usuario.nome.toUpperCase());
  }
});
js
// ✅ intenção declarada pelo nome do método
const nomes = usuarios
  .filter(usuario => usuario.ativo)
  .map(usuario => usuario.nome.toUpperCase());

A versão com filter + map diz o que faz antes de mostrar como faz. Cada método tem uma responsabilidade: filter seleciona, map transforma. Separados, cada um é trivial de testar.

Um detalhe importante: map sempre retorna um array do mesmo tamanho. Se você quer tanto filtrar quanto transformar, filter vem antes.


filter() — seleção com critério explícito

forEach com condicional e push acumula elementos que passam em uma condição. O problema é que a condição de seleção fica misturada com a lógica de acumulação.

js
// ❌ o critério de seleção está escondido dentro do corpo
const resultado = [];
transacoes.forEach(t => {
  if (t.tipo === 'credito' && t.valor > 100) {
    resultado.push(t);
  }
});
js
// ✅ o critério de seleção está visível na assinatura
const transacoesGrandes = transacoes
  .filter(t => t.tipo === 'credito' && t.valor > 100);

filter recebe um predicado — uma função que retorna true ou false. Extrair o predicado como função nomeada torna o código ainda mais legível:

js
const ehCreditoGrande = t => t.tipo === 'credito' && t.valor > 100;

const transacoesGrandes = transacoes.filter(ehCreditoGrande);

Agora o critério tem nome, pode ser reutilizado, e pode ser testado isoladamente.


reduce() — uma passada, múltiplas computações

reduce é o método mais versátil do array, e o mais incompreendido. A resistência geralmente vem da sintaxe — o acumulador inicial parece mágica na primeira vez.

O caso de uso mais claro: quando você precisa calcular várias coisas sobre o mesmo array em uma passada só.

js
// ❌ três loops separados pra três cálculos diferentes
let totalCredito = 0;
let totalDebito = 0;
let porCategoria = {};

transacoes.forEach(t => {
  if (t.tipo === 'credito') totalCredito += t.valor;
});

transacoes.forEach(t => {
  if (t.tipo === 'debito') totalDebito += t.valor;
});

transacoes.forEach(t => {
  porCategoria[t.categoria] = (porCategoria[t.categoria] || 0) + t.valor;
});
js
// ✅ um reduce, três cálculos simultâneos
const resumo = transacoes.reduce((acc, t) => {
  // acumula por tipo
  acc[t.tipo] = (acc[t.tipo] || 0) + t.valor;

  // acumula por categoria
  acc.porCategoria[t.categoria] =
    (acc.porCategoria[t.categoria] || 0) + t.valor;

  // saldo corrente
  acc.saldo += t.tipo === 'credito' ? t.valor : -t.valor;

  return acc;
}, { credito: 0, debito: 0, porCategoria: {}, saldo: 0 });

reduce também serve pra transformar arrays em objetos — algo que map e filter não fazem:

js
// transforma array em mapa indexado por id
const usuariosPorId = usuarios.reduce((acc, usuario) => {
  acc[usuario.id] = usuario;
  return acc;
}, {});

// acesso direto: usuariosPorId[42] em vez de .find(u => u.id === 42)

Uma ressalva honesta: reduce com lógica complexa dentro do callback vira difícil de ler. Se o acumulador começa a acumular mais de três coisas diferentes, vale considerar separar em etapas.


flatMap() — transformação + achatamento

flatMap resolve um problema específico: quando cada item do array produz uma lista, e você quer uma lista única no final.

js
const usuarios = [
  { nome: 'Ana', habilidades: ['React', 'Node', 'GraphQL'] },
  { nome: 'Carlos', habilidades: ['Vue', 'Python'] },
  { nome: 'Marina', habilidades: ['Angular', 'TypeScript'] }
];

// ❌ map retorna array de arrays
const errado = usuarios.map(u => u.habilidades);
// [['React', 'Node', 'GraphQL'], ['Vue', 'Python'], ['Angular', 'TypeScript']]

// com .flat() depois resolve, mas são duas passadas
const manual = usuarios.map(u => u.habilidades).flat();

// ✅ flatMap faz as duas em uma só
const todasHabilidades = usuarios.flatMap(u => u.habilidades);
// ['React', 'Node', 'GraphQL', 'Vue', 'Python', 'Angular', 'TypeScript']

flatMap também é útil pra filtrar e transformar ao mesmo tempo — retornar [] do callback é equivalente a remover o item:

js
// transforma e filtra em uma passada: retorna [] pra excluir
const creditos = transacoes.flatMap(t =>
  t.tipo === 'credito' ? [t.valor] : []
);

some() e every() — validação com early termination

forEach pra validação exige variáveis de controle externas e percorre o array inteiro mesmo quando a resposta já é conhecida.

js
// ❌ percorre o array inteiro mesmo que o primeiro item já seja inválido
let todosValidos = true;
usuarios.forEach(u => {
  if (!u.email || !u.email.includes('@')) {
    todosValidos = false;
  }
});
js
// ✅ para na primeira falha — O(1) no melhor caso
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

const todosEmailsValidos = usuarios.every(u => emailRegex.test(u.email));
const algumAdmin = usuarios.some(u => u.papel === 'admin');

every para no primeiro false. some para no primeiro true. Esse comportamento — short-circuit evaluation — não é documentação de marketing, é como os métodos funcionam na spec do JavaScript.

A vantagem sobre forEach é direta: você não precisa de variável externa, não precisa de flag, e o método já comunica a intenção. every significa "todos devem passar". some significa "pelo menos um deve passar".


find() e findIndex() — busca com early termination

O padrão de busca com forEach tem um problema clássico: precisa verificar se o item já foi encontrado pra não sobrescrever.

js
// ❌ continua percorrendo mesmo depois de encontrar
let encontrado = null;
usuarios.forEach(u => {
  if (u.id === idBuscado && !encontrado) {
    encontrado = u;
  }
});
js
// ✅ para assim que encontra
const usuario = usuarios.find(u => u.id === idBuscado);
const indice = usuarios.findIndex(u => u.id === idBuscado);

find retorna o item ou undefined. findIndex retorna o índice ou -1. Os dois param na primeira ocorrência.

Um padrão útil: find com fallback via operador || ou ??:

js
const usuario = usuarios.find(u => u.id === id) ?? { nome: 'Convidado', papel: 'guest' };

findLast e findLastIndex existem no ES2023 — buscam do fim pro início. Úteis quando a última ocorrência é mais relevante que a primeira.


Composição de funções — combinando operações

Quando várias transformações precisam ser aplicadas em sequência, function composition torna a intenção legível mesmo antes de ler o código.

js
// utilitário: pipe aplica funções da esquerda pra direita
const pipe = (...fns) => valor => fns.reduce((acc, fn) => fn(acc), valor);

// funções reutilizáveis com assinatura (predicate/transform) => array => resultado
const filtrar = predicado => arr => arr.filter(predicado);
const transformar = fn => arr => arr.map(fn);
const ordenar = comparar => arr => [...arr].sort(comparar);
const pegar = n => arr => arr.slice(0, n);

Com isso, um pipeline de processamento fica legível como uma lista de etapas:

js
const processarTransacoes = pipe(
  filtrar(t => t.tipo === 'debito'),
  ordenar((a, b) => b.valor - a.valor),
  pegar(5),
  transformar(t => ({ ...t, valorFormatado: `R$ ${t.valor.toFixed(2)}` }))
);

const top5Despesas = processarTransacoes(transacoes);

Cada função do pipeline é testável isoladamente. O pipeline em si descreve o processo em linguagem natural.

Isso não é obrigatório — chaining direto funciona bem em muitos casos. Composição faz mais sentido quando você precisa reutilizar pipelines ou quando o número de etapas cresce.


Quando forEach ainda faz sentido

Nem toda iteração precisa retornar algo. forEach é a escolha certa quando o objetivo é um side effect — atualizar o DOM, enviar eventos, logar, disparar requisições:

js
// forEach faz sentido aqui: side effect intencional
botoes.forEach(botao => {
  botao.addEventListener('click', handleClick);
});

Usar map nesse caso seria errado — map implica que você vai usar o array retornado, e aqui o retorno seria descartado.

A distinção prática:

  • Se precisa do resultado da iteração → map, filter, reduce, find, some, every
  • Se quer apenas o efeito da iteração → forEach

O que muda na prática

Trocar forEach pelos métodos específicos não é refatoração por elegância. É tornar o código mais escaneável — quem lê vê a intenção antes de processar a implementação.

map diz: vou transformar cada item. filter diz: vou selecionar alguns itens. find diz: vou parar no primeiro que passar. some diz: basta um. every diz: todos devem passar.

Código que declara intenção é código que o próximo desenvolvedor — ou você daqui a seis meses — consegue ler sem precisar simular a execução mentalmente.

Esse é o argumento. O resto é consequência.


Referências

Tags do artigo

Artigos relacionados

Receba os ultimos artigos no seu email.

Follow Us: