Tell don't ask

dans oop par Renaud Humbert-Labeaumaz

Quand j'étais à l'école et que j'apprenais la programmation orientée objet (POO), on nous disait que les classes permettaient d'encapsuler des comportements et de cacher leur implémentation au reste du monde. Ainsi, le code qui appelle une méthode attend d'elle qu'elle se comporte comme elle a été spécifiée, quelle que soit son implémentation.

Prenons l'exemple d'un panier, dont le coût total doit être calculé selon certaines règles (somme de tous les articles, ajout éventuel de taxes, charges, etc.). En POO, on pourrait s'attendre à ce que la classe Basket ait une méthode calculateTotalCost qui va calculer ce coût. Après tout, le code appelant ne veut pas savoir comment calculer un coût, il veut juste le récupérer.

Pourtant, lors de mes premiers mois dans le monde du travail, je suis tombé sur ce genre ce code, au détour d'une méthode de la couche service (je vous fais grâce de la classe Basket)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public int calculateTotalCost(Basket basket) {

   int totalCost = 0;

   for (Article article : basket.getArticles()) {
      totalCost += article.getUnitPrice() * article.getQuantity();
   }

   totalCost = totalCost + TAX_RATE * totalCost;

   basket.setTotalCost(totalCost);

   return totalCost;
}

Pourquoi en est-on arrivé là ?

Selon moi, il ne s'agit pas d'une manière correcte de coder ce genre de fonctionnalités et c'est pourquoi j'ai décidé d'écrire cet article.

Le principe Tell don’t ask

Ce principe stipule qu'au lieu de demander (ask) à un objet des informations pour les exploiter, il vaut mieux dire (tell) à cet objet ce que l'on veut faire et il s'en chargera lui même.

Selon ce principe, la classe Basket utilisée ci-dessus devient

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Basket {

   private List<Article> articles;

   // ...

   public int calculateTotalCost() {

      int totalCost = 0;

      for (Article article : articles) {
          totalCost += article.priceWithoutTaxes();
      }

      return totalCost + TAX_RATE * totalCost;
   }
}

et le service devient

1
2
3
public int calculateTotalCost(Basket basket) {
   return basket.calculateTotalCost();
}

Plusieurs remarques peuvent être faites à la lecture de ce nouveau code :

De l'utilité des services et des couches

En suivant ce genre de principes, il devient évident que le service, ce fameux God Object qui faisait la pluie et le beau temps dans le code, est réduit à peau de chagrin. Il est même possible de s'en passer très souvent.

Cependant, il reste indispensable dans le cas où les objets doivent collaborer entre eux. Globalement, si je devais indiquer la règle que j'essaie de suivre au quotidien, je dirais ceci :

Restons pragmatiques

Comme toujours, les principes se confrontent à la réalité et ils ne gagnent pas toujours. Il faut parfois savoir faire des petites entorses à la règle et ne pas être dogmatique sur ce genre de sujets. Qui sait, les bonnes pratiques d'aujourd'hui seront peut-être les anti-patterns de demain.

Cependant, après quelques temps de pratique, je pense qu'il est possible d'appliquer le principe de Tell Don’t Ask au quotidien. Il est même possible de commencer dès aujourd'hui et sans devoir refactoriser l'intégralité du code source existant.

Pensez à ça lors de vos prochains développements et n'hésitez pas à en parler sur Twitter.

Renaud Humbert-Labeaumaz

Renaud est développeur Java. Il pratique le TDD, le refactoring et les revues de code au quotidien pour produire du code propre et maintenable à forte valeur métier.

Contactez-nous

Crafties réalise des audits de code, des formations, des accompagnements et des développements. Vous pouvez nous contacter à contact@crafties.fr pour tout besoin.