mardi 18 octobre 2011

Pas si simple de faire simple

La version express de ce billet est que nous ne faisons pas assez la différence entre “construire avec des briques simples” et “faire des objets simples à utiliser”. Ce n’est pas du tout la même chose!
  • La différence :
    • La simplicité d’utilisation est
      • Contextuel
      • Composé
      • Plutôt fermé à l’évolution
    • Les briques simples sont
      • Généralistes
      • Unitaires
      • Ouverts à l’évolution
  • C’est possible d’avoir les deux.
Voici la version longue, avec des explications et, promis, des exemples.

Dans ce keynote Stuart Halloway explique que le mot simple veut dire quelque chose de très fondamentale. Il soutient que la définition du mot simple est non composé ou non assemblé (non compound en anglais).

Puis il continue à nous expliquer pourquoi il est primordiale de construire seulement à partir de choses simples. Notamment il dit
  • Essayez de faire quelque chose de simple à partir de choses non simples … C’est impossible
Autrement dit, pour avoir une librairie ou application simple, on doit l’assembler de choses simples. Puis il finit son keynote par dire.
  • Si un peu plus du code était écrit dans le respect de la simplicité le monde serait un peu meilleur.
Bon finalement c’était juste le Single Responsibility Principle dans une nouvelle sauce. Soit, seulement quelques jours plus tard, il m’est venu à l’ésprit qu’il y a un paradoxe. Car si j’écris tout mon code de cette manière alors il serait chiant à utiliser, même pour moi. Notamment lorsqu’on utilise fréquemment une fonction/objet avec les mêmes paramètres ce serait chouette de ne pas le répéter partout dans le code. Un code simple n’est pas toujours simple à utiliser.   En regardant de près il n’y a pas de paradoxe, mais la réflexion m’a fait comprendre quelque chose que je vais partager ici.

Pour rendre la vie facile pour ceux qui utilisent notre code (souvent nous mêmes) nous avons tendance à faire du code simple à utiliser. Je suis convaincu que si on échoue à faire quelque chose de simple c’est souvent parce qu’on veut faire quelque chose de simple à utiliser! Et pourtant ce compromis n'est pas nécessaire.

Si on revient à la définition de simple on constate que simple est une qualité intrinsèque et non contextuelle. Il est possible de juger de la simplicité de manière objective.

Avec Simple à utiliser c’est l’inverse. Par définition c’est simple pour l’utilisation prévue. Si on change un peu le contexte il y a toutes les chances que ce ne soit plus très simple à utiliser. Simple à utiliser est contextuel.

Souvent nous trouvons des choses simples à utiliser parce que ça ressemble à quelque chose de connu, par exemple un patron de conception que nous avons déjà utilisé. Simple à utiliser est subjectif.

Ce serait bien dommage de faire des compromis entre une qualité "absolue" et une qualité contextuelle et subjective !

Prenons par exemple cet objet qui fait partie d’une application de crawl basique.

BatchCrawler est simple à utiliser car nous n’avons pas à fournir beaucoup d’arguments pour l’instancier. Par contre il n’est pas Simple car il fait un tas de choses en plus de crawler une liste d’urls :

  • Il spécifie où est configuré le timeout de chargement d’une page.
  • Il contraint ce timeout à ne changer dans toute la JVM voire même on doit redéployer pour la changer.
  • Il contraint à utiliser un RetryingHttpCrawler – qui retente en cas de timeout et qui passe par le protocole HTTP.

Limitant ainsi fortement le potentiel de réutilisation de l’objet.

Pour que l’objet puisse être qualifié de Simple, le constructeur devrait ressembler à ça

Mais alors il deviendrait chiant à utiliser. Si cet objet est instancié de plusieurs endroits de l’application il y aurait de la duplication. Nous allons donc réintroduire ce constructeur sous forme de FactoryMethod avec un nom explicatif (documentation) parce qu’il s’agit d'une façon de configurer ce graph d’objets.

Comme avec tout exemple simplifié c’est n’est jamais trop grave. C’est encore suffisamment petit pour être corrigé après-coup. Seulement dans une application réelle ça peut être un cauchemar car régulièrement on se retrouve avec l’initialisation d’une grappe d’objets - ici BatchCrawler initialise un RetryingHttpCrawler etc. Presque à chaque fois l’initialisation des objets plus bas dans le graphe ont des conséquences indésirables, c’est souvent ça qui nous rend les tests après si difficiles ... Le remplacement d’un objet dans ce graphe n’est pas toujours aisé et le code nécessaire teintera le code d’initialisation avec complèxité.

En construisant uniquement avec des objets et fonctions Simples on arrive à l’enrober avec une simplicité d’utilisation… pour le besoin actuellement connu. Quand le besoin change (lire toujours) on est le roi du pétrole. On a le beurre et l’argent du beurre.

Note : je ne dis pas qu’il faut essayer d’imaginer le besoin futur, seulement qu’il faut décomposer son code actuel en des briques les plus simples possible. Par dessus on peut ensuite construire la simplicité d’utilisation avec par exemple des valeurs par défaut.

Conclusion
Je ne vais pas conseiller de toujours faire ainsi, car à vrai dire rien n’est toujours vrai. Simplement le message que j’essaie de passer est

  • Il y a une différence importante :
    • La simplicité d’utilisation est
      • Contextuel
      • Composé
      • Plutôt fermé à l’évolution
    • Les briques simples sont
      • Généralistes
      • Unitaires
      • Ouverts à l’évolution
  • C’est possible d’avoir les deux.