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. No dia a dia, 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:
se não consigo eliminar o 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 então usar o minifier :D), mas sim quem tem o código mais claro. A proposta hoje vai além de sintaxe. A meta é parar de escrever condicionais defensivas e começar a escrever intenção.
Vamos ver como aplicar Early Return, Encapsulamento e até mesmo o polêmico Switch da maneira coerente.
O Inimigo: A Programação Defensiva
Tive um professor na faculdade que, ao ensinar lógica de programação, apresentou uma regra que ia além do que normalmente se encontra na literatura. Um if isolado é perfeitamente aceitável. Dois, talvez acompanhados de um else, ainda exigem apenas atenção. Mas, a partir do terceiro, é um sinal claro de alerta: a probabilidade de a lógica ter sido mal modelada cresce significativamente.
Em essência, o tipo de if que mais polui o código não nasce da complexidade do domínio, mas da desconfiança. É o código que, antes de executar o que realmente importa, precisa validar, checar e reconfirmar uma série de condições defensivas. O resultado é conhecido: é um Arrow Code, onde a lógica principal se perde em meio a níveis crescentes de indentação e decisões encadeadas.
// ❌ Código Defensivo e Aninhado
function processOrder(order?: Order) {
if (order) {
if (order.isActive) {
if (order.items.length > 0) {
if (order.balance >= order.total) {
// Finally, the actual logic...
console.log("Processing...");
}
}
}
}
}
O problema aqui não é a existência da validação, mas a carga cognitiva. Você precisa ler 5 linhas de "ruído" para achar a lógica de negócio.
A Limpeza: Early Return (Guard Clauses)
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 processOrder(order?: Order): void {
if (!order || !order.isActive) return;
if (order.items.length === 0) return;
if (order.balance < order.total) return;
// O "Happy Path" fica livre e na raiz da função
console.log("Processing...");
}
Beeem melhor, neh? Mas ainda estamos lendo implementação. Estamos lendo saldo < total, quando deveríamos estar lendo uma regra de negócio.
Escrevendo Intenção (Encapsulamento)
Aqui está o pulo do gato para um código maduro. Se o seu if verifica múltiplas variáveis ou regras específicas, não exponha essa matemática na função principal. Dê um nome a ela.
Em vez de escrever código que verifica pedaços de dados, escreva código que pergunta se uma regra foi satisfeita.
// ❌ Leitura de implementação
if (user.age >= 18 && user.hasLicense && !user.isSuspended) {
rentCar();
}
// ✅ Leitura de Intenção (Predicados)
const canRentCar = (user: User): boolean =>
user.age >= 18 && user.hasLicense && !user.isSuspended;
if (canRentCar(user)) {
rentCar();
}
O if continua lá. O processador vai executar a mesma comparação. Mas para quem lê (você no futuro), a complexidade foi abstraída. Você parou de ler código defensivo e passou a ler a intenção do negócio.
A defesa do Switch: Roteamento vs Lógica
Muitas vezes, a complexidade não é booleana (sim/não), mas categórica (Tipo A, Tipo B, Tipo C). Nesses casos, o Object Map do artigo anterior é ótimo, mas e se precisarmos de lógicas complexas para cada tipo?
É aqui que o switch (ou cadeias de if) costuma virar um monstro de código espaguete, violando o princípio DRY (Don't Repeat Yourself).
O segredo para usar switch sem culpa é entender seu propósito: Ele deve ser um Roteador, não um Processador.
Utilize o switch apenas como uma Factory (Fábrica). Ele decide qual estratégia usar, mas a execução da lógica fica isolada em outro lugar (Classes ou Funções).
O jeito errado (Lógica Acoplada)
function calculateShipping(type: 'EXPRESS' | 'STANDARD', weight: number) {
switch (type) {
case 'EXPRESS':
// Lógica pesada misturada com a decisão
const rate = getTaxRate();
return (weight * rate) + 10;
case 'STANDARD':
// Mais lógica misturada...
if (weight > 30) throw new Error('Weight limit exceeded');
return weight * 5;
}
}
O jeito certo (Factory + Strategy)
Aqui, o switch serve apenas para escolher o especialista.
// As regras de negócio ficam isoladas (Strategy Pattern)
const ExpressShipping: ShippingStrategy = {
calculate: (weight) => (weight * 10) + 10
};
const StandardShipping: ShippingStrategy = {
calculate: (weight) => weight * 5
};
// O Switch serve APENAS para criar/rotear (Factory)
const getShippingStrategy = (type: 'EXPRESS' | 'STANDARD'): ShippingStrategy => {
switch (type) {
case 'EXPRESS': return ExpressShipping;
case 'STANDARD': return StandardShipping;
default: throw new Error('Invalid shipping type');
}
};
// Uso Limpo
const strategy = getShippingStrategy('EXPRESS');
strategy.calculate(10);
If e Switch não são o vilão
Eliminar ifs não deve ser um objetivo em si, mas o efeito colateral de um bom design.
O problema surge quando condicionais passam a compensar a ausência de estrutura, nomes semânticos e limites claros no código. Nesse cenário, o if vira defesa. O switch vira gambiarra. E a lógica de negócio se perde no meio do caminho.
Quando 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.
E 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.
Top comments (0)