Java · Desenvolvimento de Software · Engenharia de Software
O princípio Ifless: projetando APIs sem decisões ocultas
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
- APIs mais claras: o nome do método informa exatamente o que ele faz.
- Menos explosão combinatória nos testes: cada operação possui caminhos mais focados.
- Evolução com menos risco: inserção e atualização podem mudar separadamente.
- Alinhamento com o princípio da responsabilidade única: cada método tem uma razão clara para mudar.
- 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()emerge(), 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
- Eric Evans — Domain-Driven Design
- Martin Fowler — Replace Conditional with Polymorphism
- Clean Code, de Robert C. Martin
- O debate sobre UPSERT em SQL
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.
Experimento prático
Encontre a decisão escondida
Leia cada contrato e escolha onde a decisão já é conhecida. A resposta fica somente neste navegador.
O que este artigo provocou?
Você pode marcar mais de uma opção e desfazer quando quiser.
Comentários
Carregando comentários…