Refatorar Ifs Não Significa Eliminar Decisões

Refatorar Ifs Não Significa Eliminar Decisões

Source: Dev.to

O Inimigo: A Programação Defensiva ## A Limpeza: Early Return (Guard Clauses) ## Escrevendo Intenção (Encapsulamento) ## A defesa do Switch: Roteamento vs Lógica ## If e Switch não são o vilã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. 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. 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. 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 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. Beeem melhor, neh? Mas ainda estamos lendo implementação. Estamos lendo saldo < total, quando deveríamos estar lendo uma regra de negócio. 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. 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. 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) O jeito certo (Factory + Strategy) Aqui, o switch serve apenas para escolher o especialista. 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. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse COMMAND_BLOCK: // ❌ 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..."); } } } } } Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: // ❌ 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..."); } } } } } COMMAND_BLOCK: // ❌ 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..."); } } } } } CODE_BLOCK: // ✅ 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..."); } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: // ✅ 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..."); } CODE_BLOCK: // ✅ 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..."); } COMMAND_BLOCK: // ❌ 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(); } Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: // ❌ 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(); } COMMAND_BLOCK: // ❌ 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(); } COMMAND_BLOCK: 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; } } Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: 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; } } COMMAND_BLOCK: 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; } } COMMAND_BLOCK: // 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); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: // 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); COMMAND_BLOCK: // 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); - Trocando complexidade ciclomática por O(1) com Object Maps