Chroniques d'un voyage vers l'est

Image credit: NBphotostream

Chroniques d'un voyage vers l'est

Frédéric Hardy bio photo By Frédéric Hardy

Voici le support de présentation que j’ai utilisé lors de ma conférence au PHP Tour 2015 qui s’est déroulé à Luxembourg du 12 au 13 mai 2015.
Je profite de l’occasion pour remercier l’antenne luxembourgeoise de l’AFUP pour sa confiance et son accueil.
Un projet d’exemple correspondant à cette conférence et comprenant code source des classes ainsi que les tests unitaires associés est disponible sur github. N’hésitez pas à vous en servir pour expérimenter et faire partager vos impressions ou vos idées sur le canal IRC ##east du réseau Freenode.

002

Dans les années 60, l’un des objectifs des chercheurs en informatique du Palo Alto Research Center de Xerox était de trouver un paradigme de programmation permettant :

  • de pouvoir réutiliser le code plus facilement
  • de pouvoir faire évoluer le code plus facilement

003

Et pour parvenir à atteindre cet objectif, l’un de ces chercheurs, Alan Kay, a imaginé un nouveau paradigme qu’il a appelé « Programmation orienté objet », même s’il a aujourd’hui tendance à penser que ce nom était bien mal choisi.

004

Pour définir ce paradigme, il s’est inspiré du fonctionnement des cellules biologiques qui encapsule la réalisation d’une tâche sans que le reste du monde en connaisse la nature ni la façon dont cette tâche est réalisée.

005

Une cellule biologique est en effet une « boite noire » aux yeux d’autres cellules, et les autres cellules sont également des « boites noires » du point de vue de la cellule. Une cellule biologique ne sait donc pas de quoi sont capables les autres cellules, et les autres cellules ne savent pas de quoi est capable la cellule.
Pourtant, malgré le fait qu’elles soient de parfaites abstractions les unes pour les autres, les cellules biologiques sont les briques de base d’organismes complexes, à commencer par l’être humain. De plus, une cellule ne peut vivre indépendamment des autres, puisqu’elle a des besoins (énergie, oxygène) qui ne peuvent être satisfaits qu’avec la collaboration d’autres cellules.
À première vue, il y a donc là un paradoxe : comment quelque chose peut-il collaborer avec autre chose sans rien savoir ni de son fonctionnement et de ses capacités ?
Cependant, à y regarder de plus près, ce paradoxe n’existe pas, grâce à la notion de message.

006

Du point de vue de la cellule, un message est la modélisation, sous la forme d’une molécule, d’une information sur l’état de la cellule qui l’émet, par exemple « j’ai besoin d’oxygène ». Et à la surface de chaque cellule, il y a d’autres molécules qui sont capables de s’appairer uniquement avec les molécules correspondant à certains messages. Ainsi, les cellules capables de fournir de l’oxygène sont par exemple capables de recevoir le message « j’ai besoin d’oxygène ».
Ainsi, en fonction de son type, une cellule est capable de réagir à certains messages et d’ignorer tous les autres. Et comme le message fournit des informations relatives uniquement à l’état de la cellule émettrice et que la cellule réceptrice est, par nature, capable de comprendre ce message, elle est capable d’y réagir sans rien savoir de la nature ou du fonctionnement interne de l’émetteur.
La cellule émettrice d’un message ne sait donc absolument rien de la cellule réceptrice, mais pour autant, la communication est possible entre les deux et chacune d’elle peut jouer son rôle indépendamment de l’autre.
En imaginant que nous disposons du pouvoir de remplacer, dans un organisme vivant, une cellule par une cellule différente capable de comprendre les mêmes messages, nous pourrions alors altérer comme bon nous semble le fonctionnement de cet organisme, uniquement en modifiant une et une seule cellule.
En clair, nous pourrions faire évoluer facilement l’organisme.
De même, si nous étions capables de prendre une cellule d’un organisme A et des cellules d’un organisme B capable de communiquer ensemble, nous pourrions créer un organisme C en les mettant en relation.
Ou bien alors nous pourrions injecter dans l’organisme B la cellule de l’organisme A et lui donner ainsi de nouvelles possibilités.
En clair, nous pourrions réutiliser facilement les cellules.
Le modèle des cellules biologiques est donc très pertinent pour répondre à la problématique des développeurs des années 60, puisqu’à condition de disposer des pouvoirs nécessaires, il facilite grandement la réutilisation et l’évolution.
L’idée géniale d’Alan Kay a donc été de donner ces pouvoirs aux développeurs.
Mais techniquement parlant, qu’est ce que cela veut dire ?

007

Cela signifie que tout comme une cellule biologique, un objet doit rester en toute circonstance une « boite noire » dans le sens le plus absolu du terme et donc ne jamais fournir d’information sur sa nature, ses possibilités ou son fonctionnement interne. Les seules choses qu’il doit exposer aux yeux du monde sont les messages auxquels il est susceptible de réagir.

008

Et pour parvenir à cela, il suffit de suivre une seule règle, énoncé dans un article de blog par James Ladd, un développeur Australien, en 2007.

009

De son point de vue, le flot d’un programme orienté objet peut aller dans quatre directions. Lorsqu’une méthode est appelée sans argument, le flot passe dans un niveau inférieur du programme, et il est donc possible de dire qu’il va vers le sud. À contrario, lorsque qu’une instruction return est utilisé, le flot du programme remonte d’un niveau et il est donc possible de dire qu’il va vers le nord. Lorsqu’une affection est réalisé, le flot se déplace vers la gauche et donc vers l’ouest. Et à contrario, lorqu’un appel de méthode transporte des arguments, le flot se déplace vers la droite et donc vers l’est. Et d’après James Ladd, c’est cette dernière direction qu’il faut suivre pour pouvoir profiter au maximum de la puissance de la programmation orientée objet, et il suffit pour cela de suivre une règle très simple.

010

Toutes les méthodes publiques d’une classe doivent retourner $this ou une nouvelle instance de la classe.

011

C’est en effet le seul moyen de s’assurer que l’objet reste une abstraction, puisque $this ou une nouvelle instance ne donne aucune indication sur sa composition ou son fonctionnement interne.

012

Ainsi, une méthode complex::addTo(complex $complex) devra retourner une nouvelle instance de la classe complex construite à partir de l’addition des parties réelles et imaginaires des deux instances.

013

De même, une méthode permettant de comparer deux instances de la classe age devra retourner $this. L’œil averti aura remarqué que les messages ne sont pas déclaratifs dans les exemples ci-dessus.

014

Dans son livre Practical Object-Oriented Design in Ruby, Sandi Metz, une développeuse Ruby, a défini trois niveaux d’abstraction différents, qu’elle résume de la manière suivante, du plus faible au plus puissant.

015 016 017 018 019 020

En résumé, plus le niveau d’abstraction mis en œuvre est élevé, plus le code correspondant sera susceptible de pouvoir évoluer et d’être réutilisable facilement.
Les messages précédents définis dans les classes complex et age sont l’exemple type du second niveau : l’émetteur du message sait ce que fait le récepteur. Cependant, nous sommes dans un cas particulier puisque ces message utilise des arguments de la classe elle-même. En conséquence, l’abstraction reste totalement opaque aux yeux du reste du monde et l’aspect déclaratif n’est donc pas une obligation dans ce contexte.
Reste que si une méthode ne peut retourner que $this ou une nouvelle instance de la classe, comment est-il possible de transmettre une information à une instance d’une classe différente ?

021

La solution consiste à utiliser des interfaces qui définissent un protocole de communication entre le consommateur et le fournisseur de l’information.

022

Ainsi, toute classe qui utilisera l’interface barman sera capable de (potentiellement) répondre aux besoins d’un client, et toute classe qui utilisera l’interface client sera capable de (potentiellement) répondre aux besoins du barman.
De plus, l’interface impose le format des messages, et en conséquence, la classe qui l’utilise sera forcément capable de l’interpréter correctement.
Et comme c’est le récepteur qui définit le format des messages qu’il est capable de comprendre, il n’y a aucune dépendance envers l’émetteur.
Une instance d’une classe qui implémente l’interface client voulant indiquer son âge à une instance d’une classe implémentant barman doit en effet obligatoirement le faire à l’aide d’une instance de la classe age, même si, en interne, son âge est modélisé d’une manière complètement différente.
Ainsi, si la façon dont l’âge est représenté au sein de la classe évolue, cela n’aura aucun impact sur les classes implémentant barman.
À l’inverse, si les besoins des « barmans » évoluent, il suffira d’ajouter les messages correspondant aux deux interfaces. Lorsqu’un développeur de classes implémentant barman ou client récupérera leur dernière version et qu’il lancera la compilation de son code, il sera automatiquement averti de l’évolution des interfaces et sera donc à même d’ajouter les méthodes manquantes dans les classes qui le demandent.
De plus, les classes qui utilisent ces interfaces ont une grande liberté, car les messages sont uniquement informatif. En conséquence, chaque type de récepteur est libre d’y réagir comme il l’entend. Il peut ignorer le message, ou bien il peut y réagir et le faire en fonction de son état du moment et de la manière qui l’arrange, vu qu’il n’est pas contraint par l’émetteur à une réaction spécifique.

023

Si l’interface barman contenait la définition de la méthode giveAlcoholToClient(client $client), les « barmans » seront implicitement obligés d’y réagir en donnant un verre d’alcool au client qui en a fait la demande. Et de même, l’émetteur du message s’attend à ce que le client reçoive de l’alcool.
À contrario, avec le message alcoholIsAskedByClient(client $client), le nombre de réactions différentes que peut avoir le « barman » est infini, et le client n’attend aucune réaction particulière du reste du monde.

024

Les possibilités sont donc décuplées, et le comportement global du programme peut être altéré radicalement en remplaçant un « barman » par un autre ayant un comportement complètement différent.

025

Cependant, le très fort découplage entre l’émetteur et le récepteur permis par la programmation orientée « message » n’a pas que ce seul intérêt.
En effet, lorsque le récepteur reçoit un message, il a totalement le contrôle, puisque le message lui a été envoyé dans un format qu’il a imposé et qu’il est totalement autonome par rapport au reste du monde. Ses changements d’état n’ont donc strictement aucune influence sur le reste du monde.

Le récepteur est donc libre de se mettre dans l’état qui lui convient pour réagir à un message, et il peut même établir une relation exclusive avec l’émetteur et ainsi masquer aux yeux du reste du monde les changements d’état susceptible de se produire au cours du dialogue.
Dans un tel contexte, les accès concurrents ne sont plus un problème, et un « barman » peut ainsi servir plusieurs clients simultanément et/ou indépendamment.

026

Dans l’exemple ci-dessus, lorsqu’un client informe le barman qu’il veut de l’alcool, ce dernier se met dans l’état qui lui permettra de réagir à cette information via l’instruction new self. Ainsi, tout ce qui se passe par la suite est indépendant de l’état du barman avant la réception du message puisque ce message est traité systématiquement par un nouveau barman. De plus, ce nouveau barman n’est connu que du client qui a manifesté son désir d’alcool et le reste du monde ne peut donc pas l’influencer.

027

Le fait de faire retourner $this ou une nouvelle instance de la classe à toutes les méthodes publiques d’une classe a de plus un effet de bord très intéressant : les tests unitaires deviennent très simples à écrire.

028

En effet, les dépendances des classes sont représentées par des interfaces qu’il est très facile de bouchonner.

029

Les tests unitaires d’une classe « east oriented » se résume donc dans la grande majorité des cas à vérifier que la classe émet les bons messages lorsqu’elle reçoit un message.

030

Les fonctionnalités d’une classe ne sont donc plus définies par le nom de ces méthodes, mais par les messages qu’elle émet. Il est donc plus pertinent de concevoir une classe en fonction des messages qu’elle va devoir interpréter et émettre plutôt qu’en fonction de la représentation mentale que l’on peut en avoir au premier abord.

031

Cette approche de la programmation orientée objet nécessite un effort certain de la part de celui qui veut s’y essayer, d’autant qu’il est relativement difficile de trouver de l’aide à ce sujet. Si vous êtes intéressé, si vous avez des questions, vous pouvez vous rendre sur le canal IRC ##east sur le réseau Freenode. Des exemples de code sont également disponibles sur github et gist. Ils sont susceptibles de vous aider au cours de votre voyage vers l’est !

032

Je tiens à remercier James Ladd pour avoir formalisé cette méthode de développement ainsi que pour sa disponibilité, Jim Gay aka @saturnflyer pour avoir également à l’est, en avoir tiré un enseignement et avoir divulgué ce qu’il a appris, Sylvain Robez-Masson et Cédric Courtois pour m’avoir apporté de manière constructive et très systématique la contradiction, ce qui m’a permis d’avancer dans mes réflexions, ainsi que Peter Di Salvo aka @peterdisalvo, un autre voyageur vers l’est, pour ses excellents billets relatifs à ses pérégrinations.

033

Vous pouvez donner votre avis à propos de cette conférence sur joind.in !