Refatorar Ifs Não Significa Eliminar Decisões
Source: Dev.to
Introdução
Já falei sobre Object Maps, uma técnica poderosa para substituir cadeias de switch ou if/else. Com ela, trocamos complexidade ciclomática por acesso direto em tempo constante (O(1)), tornando‑a ideal para cenários de mapeamentos estáticos do tipo chave → valor.
No entanto, o desenvolvimento de software no mundo real raramente é tão previsível. Lidamos com regras de negócio dinâmicas, faixas de valores, validações combinadas e decisões dependentes de contexto, situações em que um simples mapeamento deixa de ser suficiente.
É aí que surge a dúvida inevitável:
if, estou condenado a escrever código sujo?
A resposta é não. Refatorar condicionais não é um esporte onde ganha quem tem menos linhas de código (se fosse assim, bastava usar o minifier :D), mas sim quem tem o código mais claro. A proposta vai além de sintaxe: a meta é parar de escrever condicionais defensivas e começar a escrever intenção.
Código Defensivo e Aninhado
// ❌ Código Defensivo e Aninhado
function processarPedido(pedido) {
if (pedido) {
if (pedido.ativo) {
if (pedido.itens.length > 0) {
if (pedido.saldo >= pedido.total) {
// Finalmente, a lógica real...
console.log("Processando...");
}
}
}
}
}
O problema aqui não é a existência da validação, mas a carga cognitiva. Você precisa ler várias linhas de “ruído” para achar a lógica de negócio.
Guard Clauses (Early Return)
A primeira estratégia para organizar a casa é o Early Return (ou Guard Clauses). A regra é simples: trate as exceções primeiro. Se uma condição impede o código de rodar, retorne imediatamente. Isso elimina a necessidade de else e remove níveis de indentação.
// ✅ Guard Clauses: Limpando o fluxo
function processarPedido(pedido) {
if (!pedido || !pedido.ativo) return;
if (pedido.itens.length === 0) return;
if (pedido.saldo = 18 && user.hasLicense && !user.suspended) {
rentCar();
}
}
Leitura de intenção (Predicados)
// ✅ Leitura de Intenção (Predicados)
const podeAlugarCarro = (user) =>
user.age >= 18 && user.hasLicense && !user.suspended;
if (podeAlugarCarro(user)) {
rentCar();
}
O if continua lá, mas a complexidade foi abstraída para uma função com nome expressivo, facilitando a leitura futura.
Switch como Factory (Strategy)
Muitas vezes a complexidade não é booleana (sim/não), mas categórica (Tipo A, Tipo B, Tipo C). Nesses casos, o Object Map é ótimo, mas se precisarmos de lógicas diferentes para cada tipo, o switch pode virar um monstro de código espaguete.
Uso incorreto (Lógica acoplada)
// ❌ Lógica Acoplada
function calcularFrete(tipo, peso) {
switch (tipo) {
case 'SEDEX':
const taxa = obterTaxa();
return (peso * taxa) + 10;
case 'PAC':
if (peso > 30) throw new Error('Peso limite');
return peso * 5;
}
}
Uso correto (Factory + Strategy)
// Estratégias de negócio (Strategy Pattern)
const FreteSedex = { calcular: (p) => (p * 10) + 10 };
const FretePAC = { calcular: (p) => p * 5 };
// Switch como roteador (Factory)
const obterEstrategia = (tipo) => {
switch (tipo) {
case 'SEDEX': return FreteSedex;
case 'PAC': return FretePAC;
default: throw new Error('Tipo inválido');
}
};
// Uso limpo
const estrategia = obterEstrategia('SEDEX');
estrategia.calcular(10);
Conclusão
Eliminar ifs não deve ser um objetivo em si, mas o efeito colateral de um bom design. Quando as decisões estão bem distribuídas — Object Maps para mapeamentos estáticos, Guard Clauses para validações, regras de negócio explícitas e Switches atuando como roteadores — o código deixa de ser reativo e passa a ser declarativo.
Você não elimina decisões. Você elimina ruído. Esse é o tipo de código que continua legível mesmo depois que o contexto do problema já não está mais fresco na memória.