Java · Développement logiciel · Ingénierie logicielle
Le principe Ifless : concevoir des API sans décisions cachées
Introduction
L’une des lignes de code les plus dangereuses n’est pas celle qui lève une exception, mais celle qui cache une décision.
En tant qu’ingénieurs, nous apprécions souvent les méthodes flexibles : un unique save() qui sait « magiquement » quand insérer ou mettre à jour, un send() qui choisit le bon canal ou un approveOrReject() qui décide du résultat. Cela semble d’abord pratique. Mais derrière cette commodité grandissent la complexité, l’ambiguïté et une armée de conditions.
Le problème des trop nombreux if ne se limite pas à la lisibilité. Chaque scénario supplémentaire doit être conçu, testé et maintenu. Ce qui ressemble à « une condition de plus » peut faire croître le coût de façon exponentielle.
Avec le temps, j’ai adopté une approche que j’appelle le principe ifless : au lieu d’enfouir les décisions dans le code, nous les rendons explicites dans la conception de l’API et dans le domaine lui-même.
Le principe Ifless
L’idée est simple :
- ne laissez pas le service décider ce que l’appelant sait déjà ;
- représentez des opérations différentes par des méthodes, commandes ou entités différentes, même si leur comportement est d’abord identique ;
- placez l’intelligence dans des entités de domaine riches, conformément au Domain-Driven Design, afin que les règles vivent au bon endroit.
En bref : ifless ne consiste pas à supprimer les conditions, mais à placer les décisions au bon endroit.
Exemple 1 : enregistrer ou insérer/mettre à jour
❌ Version chargée de décisions
public void save(Order order)
{
if (order.getId() == null)
{
insert(order);
}
else
{
update(order);
}
}
✅ Version ifless
public void insert(Order order) { ... }
public void update(Order order) { ... }
Même si les méthodes sont identiques aujourd’hui, la conception anticipe que l’insertion et la mise à jour pourront évoluer différemment. Surtout, l’appelant connaît l’intention : le service n’a pas à la deviner.
Exemple 2 : approbations
❌ Décision cachée dans le service
public void approveOrReject(Transaction transaction, boolean approve)
{
if (approve) { ... } else { ... }
}
✅ API ifless
public void approve(Transaction transaction) { ... }
public void reject(Transaction transaction) { ... }
Chaque opération possède son propre cycle de vie, ses règles et son évolution. Les tests sont plus ciblés et l’API exprime clairement l’intention.
Exemple 3 : notifications
❌ Sélection conditionnelle du canal
notificationService.send(user, message, channel);
✅ Séparation ifless
emailNotification.send(user, message);
smsNotification.send(user, message);
pushNotification.send(user, message);
Au lieu d’une méthode à multiples branches, chaque canal applique ses propres règles. Ajouter un canal ne demande plus de modifier un immense switch.
Lien avec le Domain-Driven Design
En DDD, le modèle de domaine encapsule la logique centrale. Les invariants — par exemple « une commande ne peut être expédiée qu’après son paiement » — doivent vivre dans l’entité elle-même :
public class Order
{
public void ship()
{
if (!this.isPaid())
{
throw new BusinessException("Order must be paid before shipping");
}
// Poursuivre l’expédition.
}
}
Il reste un if, mais il n’est pas dispersé entre plusieurs services. La règle est encapsulée là où elle appartient vraiment : dans l’entité Order.
C’est l’esprit ifless : les décisions sont modélisées explicitement dans le domaine, et non déléguées à un service omniscient.
Bénéfices
- Des API plus claires : le nom de la méthode indique précisément ce qu’elle fait.
- Moins d’explosion combinatoire dans les tests : chaque opération possède des chemins plus ciblés.
- Une évolution moins risquée : insertion et mise à jour peuvent changer séparément.
- Un meilleur alignement avec le principe de responsabilité unique : chaque méthode a une raison claire de changer.
- Une architecture plus propre : les services restent minces, les entités riches et les décisions explicites.
Limites et contrepoints
Aucun principe n’est universel. Ifless a aussi un coût :
- verbosité : davantage de méthodes ou de services peuvent apparaître, même lorsque les différences sont faibles ;
- charge pour le client : l’appelant peut réellement préférer l’ergonomie d’un
save(); - conventions existantes : Hibernate et Spring Data utilisent déjà
save()etmerge(), et s’en écarter peut surprendre l’équipe.
Je considère donc ifless comme une boussole, pas comme un dogme. Utilisez-le lorsque la clarté, la testabilité et l’intention explicite valent davantage que la commodité d’une méthode générique.
Références
- Eric Evans — Domain-Driven Design
- Martin Fowler — Replace Conditional with Polymorphism
- Clean Code, de Robert C. Martin
- Le débat sur UPSERT en SQL
Conclusion
Chaque if a un coût, non seulement en complexité, mais aussi en tests, en maintenance et en évolution.
Le principe ifless consiste à rendre les décisions explicites dans la conception de l’API et du domaine. Il produit des contrats qui expriment l’intention sans ambiguïté.
Il ne signifie pas que vous n’écrirez plus jamais de condition. Il empêche l’architecture de les cacher aux mauvais endroits.
À une époque où les entreprises doivent évoluer avec moins de ressources, la clarté de conception n’est pas un luxe : c’est une condition de survie.
Expérience pratique
Trouvez la décision cachée
Lisez chaque contrat et repérez où un choix est déjà connu. Vos réponses restent dans ce navigateur.
Qu’est-ce que cet article a suscité ?
Choisissez plusieurs options et annulez-les à tout moment.
Commentaires
Chargement des commentaires…