Introdução

Uma das linhas de código mais perigosas não é aquela que lança uma exceção — é aquela que esconde uma decisão.

Como engenheiros, muitas vezes celebramos métodos flexíveis: um único save() que “magicamente” sabe quando inserir ou atualizar, um send() que escolhe o canal correto ou um approveOrReject() que decide o resultado. No início isso parece conveniente. Mas, por trás da conveniência, crescem a complexidade, a ambiguidade e um exército de condicionais.

O problema de ter muitos if não se limita à legibilidade. Cada cenário adicional precisa ser projetado, testado e mantido. O que parece ser “apenas mais uma condição” pode aumentar o custo de forma exponencial.

Com o tempo, adotei uma abordagem que chamo de princípio ifless: em vez de enterrar decisões no código, tornamos essas decisões explícitas no design da API e no próprio domínio.

O princípio Ifless

A ideia é simples:

  • não deixe o serviço decidir aquilo que o chamador já sabe;
  • represente operações diferentes como métodos, comandos ou entidades diferentes, mesmo que inicialmente compartilhem comportamento;
  • coloque a inteligência em entidades de domínio ricas, seguindo Domain-Driven Design, para que as regras vivam no lugar correto.

Em resumo: ifless não significa eliminar condicionais, mas colocar as decisões no lugar certo.

Exemplo 1: salvar ou inserir/atualizar

❌ Versão carregada de decisões

public void save(Order order)
{
    if (order.getId() == null)
    {
        insert(order);
    }
    else
    {
        update(order);
    }
}

✅ Versão ifless

public void insert(Order order) { ... }
public void update(Order order) { ... }

Mesmo que os métodos sejam idênticos hoje, o design antecipa que inserção e atualização podem evoluir de maneiras diferentes. Mais importante: o chamador conhece a intenção, portanto o serviço não precisa adivinhá-la.

Exemplo 2: aprovações

❌ Decisão escondida no serviço

public void approveOrReject(Transaction transaction, boolean approve)
{
    if (approve) { ... } else { ... }
}

✅ API ifless

public void approve(Transaction transaction) { ... }
public void reject(Transaction transaction) { ... }

Cada operação ganha seu próprio ciclo de vida, regras e caminho de evolução. Os testes ficam mais específicos e a API expressa claramente a intenção.

Exemplo 3: notificações

❌ Seleção condicional de canal

notificationService.send(user, message, channel);

✅ Separação ifless

emailNotification.send(user, message);
smsNotification.send(user, message);
pushNotification.send(user, message);

Em vez de um método com vários desvios, cada canal implementa suas próprias regras. Adicionar um novo canal não exige alterar um enorme switch.

Conexão com Domain-Driven Design

Em DDD, o modelo de domínio encapsula a lógica central. Invariantes — como “um pedido só pode ser enviado depois de pago” — devem viver na própria entidade:

public class Order
{
    public void ship()
    {
        if (!this.isPaid())
        {
            throw new BusinessException("Order must be paid before shipping");
        }

        // Continue o envio.
    }
}

Ainda existe um if, mas ele não está espalhado por vários serviços. A regra está encapsulada onde realmente pertence: na entidade Order.

Isso é ifless em essência: as decisões são modeladas explicitamente no domínio, não delegadas a um serviço onisciente.

Benefícios

  1. APIs mais claras: o nome do método informa exatamente o que ele faz.
  2. Menos explosão combinatória nos testes: cada operação possui caminhos mais focados.
  3. Evolução com menos risco: inserção e atualização podem mudar separadamente.
  4. Alinhamento com o princípio da responsabilidade única: cada método tem uma razão clara para mudar.
  5. Arquitetura mais limpa: serviços permanecem finos, entidades ficam ricas e decisões se tornam explícitas.

Limites e contrapontos

Nenhum princípio é universal. Ifless também tem custos:

  • verbosidade: podem surgir mais métodos ou serviços, mesmo quando as diferenças são pequenas;
  • carga para o cliente: às vezes o chamador realmente prefere a ergonomia de um save();
  • convenções existentes: Hibernate e Spring Data já adotam métodos como save() e merge(), e contrariar a convenção pode surpreender a equipe.

Por isso, vejo ifless como uma bússola, não como dogma. Use-o quando clareza, testabilidade e intenção explícita forem mais valiosas que a conveniência de um método genérico.

Referências relacionadas

Conclusão

Todo if tem um custo — não apenas em complexidade, mas também em testes, manutenção e evolução.

O princípio ifless consiste em tornar decisões explícitas no design da API e do domínio. Ele produz contratos que expressam intenção sem ambiguidade.

Não significa nunca escrever uma condicional. Significa impedir que a arquitetura esconda condicionais nos lugares errados.

Na era de empresas que precisam escalar com menos recursos, clareza de design não é luxo — é sobrevivência.