Bonjour à tous. Dans cette séquence, nous allons parler
de la campagne anti if, et en particulier pourquoi est-ce
que renvoyer nul et tester nul n'est pas terrible.
Voici un exemple de code qui n'est pas du tout objet, et
on va voir que là on a une méthode qui prend un animal en
paramètre, et en fonction du type de l'animal on va faire
des choses différentes. Si c'est un chien, on va lui
demander de remuer la queue. Si c'est un canard, on va
lui demander de faire coin coin.
Et si c'est un chat, on va lui demander de faire autre chose.
Pourquoi ce n'est pas bon et pourquoi en général et l'utilisation
de if, en particulier quand c'est appliqué pour vérifier
le type du receveur, pourquoi est-ce que ça ce n'est pas très bon?
Parce que si je veux ajouter un nouvel animal, il va
falloir que je cherche partout dans tout le code du
projet, tous les if qui pourraient être intéressants.
Donc, je vais devoir modifier plein de codes un peu
partout dans le projet, et ça ce n'est vraiment pas terrible.
Et puis en plus, parce que les méthodes, à force de
rajouter des cas, elles vont grossir de plus en plus et
elles vont être noyées dans les détails, c'est-à-dire que
si maintenant après avoir ajouté plein d'animaux, la
méthode va être très longue et pour chaque animal, je
peux avoir plein de détails. Par exemple ici c'est
quelque chose qui est particulièrement simple mais si
maintenant je me dis peut-être que les chiens n'ont pas
tous des queues, il va falloir que je fasse un cas "Si le
chien a une queue, je fais ça.
Si le chien n'a pas de queue, alors je fais ça " Le code
va être de plus en plus gros et de moins en moins compréhensible.
Pour remplacer tous ces trucs-là, on envoie des messages.
C'est tout ce qu'on vous répète depuis le début de ce
cours-là, et c'est vraiment la chose que vous devez
retenir: l'envoi de messages. On peut remplacer
facilement le code précédent par celui-ci où on
implémente une méthode showHappiness dans chaque classe
qui nous intéresse, et c'est chaque classe qui va décider
comment est-ce que tel ou tel animal va montrer qu'il est content.
Ensuite une fois que j'ai un animal, il me suffit
d'envoyer le message, showHappiness à l'
animal, et une de ces méthodes va être exécutée.
Là, on voit bien que c'est Pharo qui fait le if.
C'est Pharo qui se dit "Si mon animal est de ce type-là,
alors c'est cette méthode-là qui va s'exécuter.
" Tout ça se fait automatiquement, je n'ai pas besoin de
l'écrire en tant que développeur.
Donc, il n'y a aucune raison d'écrire des if par rapport
au type des objets, c'est Pharo qui gère ça.
Ça rend juste le code plus compliqué à lire et moins évolutif.
On va parler maintenant du cas particulier du nil parce
que si une méthode retourne un nil, vous allez forcer les
clients à utiliser if.
Et on vient de voir qu'utiliser if, c'était rarement une bonne idée.
Ici, si je prends un exemple de code qui dit l'idée c'est
de faire quelque chose en fonction d'un paramètre et en
fonction d'un objet inférenceur, peu importe ce que ce code-là fait.
Par contre ici, je vois que dans certains cas, il renvoie nil.
Ce qui fait quand je vais devoir utilise ce code, donc
quand j'envoie le message rulesForFact à un référenceur,
il va falloir que je teste.
Est-ce que rulesForFact m'a renvoyé Nil?
Si oui alors je fais ça.
Si ce n'est pas nil, alors je fais autre chose.
Dans ce cas-là en particulier, on peut se rendre compte
que ici comme on a un pluriel et ici on a un pluriel,
probablement que cette méthode-là va renvoyer une collection.
Pour éviter nil, une bonne solution souvent c'est que
quand on a une méthode qui retourne une collection, c'est
de retourner une collection vide, ça marche vraiment très souvent.
Et retourner une collection vide plutôt que de retourner
nil, ça facilite vraiment l'écriture du code.
Parce que maintenant les clients ont juste à itérer sur
la collection, si elle est vide il ne va rien se passer,
c'est exactement ça qui était prévu.
Pour les cas exceptionnels, par exemple vous avez un flux
d'écriture de fichiers et puis votre flux n'a pas été
ouvert en écriture, et donc il y a une erreur.
Plutôt que retourner nil, vous informez le système de la
situation exceptionnelle en levant une exception.
En Pharo, on appelle ça "signaler une exception".
On crée une instance d'une classe exception ou d'une
sous-classe et on envoie un signal, ce
qui permet d'éviter que directement le client de la
méthode, ici de la méthode nextPutAll, ait besoin de
tester "Si c'est nil, probablement que quelque chose s'est
mal passé, alors il faut que je fasse ça.
" Là, soit le client décide de traiter l'exception, soit
c'est le client du client qui décide de traiter l'exception,
soit c'est le client du client du client, et caetera.
On n'est pas obligé de faire ça à tous les niveaux, on
peut décider d'un niveau qui nous intéresse pour capturer
l'exception et en faire quelque chose.
Ça évite tout un tas de if.
Un autre cas où on trouve des vérifications par rapport à
la valeur nil, c'est dans les variables d'instances qui
ne sont pas initialisées. Si vous avez du code qui dit
"Si ma variable est encore à nil, alors je fais ça", dans
ces cas-là il vaut mieux initialiser la variable tout de
suite avec une valeur qui va marcher dans tous les cas.
Là, ma variable members qui est censée contenir une
collection, plutôt que la laisser à nil je l'initialise à
une collection vide dès le départ.
Ça encore une fois ça marche très souvent.
Quand vous voulez donner une valeur à une variable et que
c'est coûteux de calculer la valeur de cette variable-là,
vous pouvez attendre le dernier moment avant de calculer cette valeur-là.
Peut-être qu'elle ne sera jamais calculée et donc que
vous aurez gagné du temps d'exécution.
Dans ces cas-là, on utilise l'initialisation paresseuse
et c'est au moment où on va avoir besoin d'une valeur qu'on
va regarder si la valeur est encore à nil, alors je mets
quelque chose dedans. Si elle n'est plus à nil, je
retourne directement sa valeur.
Ce qui fait qu'ici, j'ai un if par rapport à nil mais je n'en ai qu'un.
Tous les autres utilisateurs de la variable vont passer
par cette méthode et n'auront pas à tester si c'est nil.
Parfois, il y a des cas où il va falloir tester.
Il va falloir vérifier est-ce que j'ai quelque chose à
faire ou pas, c'est ce qu'on trouve dans cet exemple.
L'idée de ça c'est qu'on a une palette d'outils.
Si on a un outil qui est sélectionné, on peut faire quelque chose.
Si aucun outil n'est sélectionné, on ne peut rien faire.
Si selectedTool retourne nil, il n'y a pas d'outil
sectionné, donc on ne fait rien.
Si selectedTool retourne quelque chose, alors on va
demander à ce quelque chose de faire une
action. Une bonne façon de remplacer ça, c'est d'utiliser
le design pattern null Object.
Ce que je fais c'est que plutôt qu'avoir les 2 cas, j'ai
un outil, je n'ai pas d'outil, on ne va avoir qu'un seul
cas j'ai un outil et un des outils ne fera rien.
Et ce sera l'outil qui ne fera rien qui sera sélectionné par défaut.
Là, je crée un outil qui ne fait rien.
Quand je lui demande de faire des actions, il ne fait rien.
Et par défaut, dans ma palette d'outils je vais sectionner ce truc-là.
Plutôt que de ne rien sélectionner, je sélectionne un
outil qui ne fait rien. C'est le design pattern Null
Object, pour en savoir plus vous avez ses références qui sont ici.
En conclusion. Les messages marchent toujours mieux que les if.
Les if, vous allez vous en servir dans certains cas, mais
souvent vous allez pouvoir vous en passer et envoyer des
messages sera souvent mieux.
Évitez de retourner la valeur nil parce que ça force les
vérifications avec if: si la valeur est nulle, alors je
fais ça; si la valeur n'est pas nulle, alors je fais ça.
"
Initialisez vos variables soit dès la création de l'objet,
soit de façon paresseuse.
Si vous pouvez, créez des objets qui représentent un
comportement par défaut ou une absence de comportement.
Tout ce que je viens de vous raconter, c'est valable pour
Pharo, mais c'est valable aussi pour tous les langages objet.
C'est important de garder tout ce que je viens de vous
raconter en tête quel que soit le langage que vous utilisez.