DEV Community

Bruno Egito
Bruno Egito

Posted on • Edited on

Refatorar Ifs Não Significa Eliminar Decisões

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...");
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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...");
}
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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)