9) Tell Don't Ask

Le code des Use Cases ressemble pour le moment furieusement à du code procédural en :
interrogeant des objets
prenant des décisions basées sur l'état de ces objets

Voici un exemple avec un Use Case existant :
Nous allons encapsuler la prise de décision au niveau du Domain et faire en sorte que les Use Cases respectent le principe Tell Don't Ask :
Prendre du temps pour comprendre ce qu'est le principe
Tell Don't AskEncapsuler le code
BusinessdesUse Casesdans leDomainRevoir l'encapsulation des objets afin de préserver l'état du
DomainRendre impossible de représenter un état invalide
Avoir des objets métiers porteurs de sens
Refactorer le Use Case : ReprendreLaPartie
ReprendreLaPartieOn commence par extraire le contenu business du
Use CaseRefactor->Extract->Extract Method

Nous devons passer la fonction
_timeProvideren paramètre de la méthode
Nous pouvons maintenant déplacer la méthode dans la classe
PartieDeChasseRefactor->Move
En déplaçant cette méthode dans le
Domain, un test d'Architecture échoue :Les exceptions lancées ne sont effectivement pas au sein du Domain

Nous devons déplacer ces exceptions
métiersau sein duDomain

Découvertes
On continue ce refactoring pour chaque
Use Caseet faisons quelques "découvertes" :1 vérification manquante
De la duplication de code dans chaque
Use Case:
Les méthodes
TireretTirerSurUneGalinettecontiennent du code dupliquéLes appels au
Savedu repository se font dès qu'on ajoute un événement dans la liste d'events (même si une exception est lancée)
Refactorer le Domain
DomainAprès les différents refactorings voici l'état de la classe
PartieDeChasse:
Supprimer les constructeurs (utiliser la Factory)
Nous sommes couverts par les tests, nous allons pouvoir nous amuser en terme de refactoring 😊
On commence par le feedback fourni par notre IDE sur les constructeurs

Nous n'avons plus qu'un seul constructeur
public:
Le constructeur est appelé par notre
Test Data Builder:
Nous voulons forcer l'instantiation de cette classe par sa factory afin de ne plus pouvoir instancié une
PartieDeChassedans un état invalideEx : Une partie de chasse démarre avec 0 galinettes sur le terrain et des chasseurs sans balles...
Nous allons donc faire appel à la
Factory Methodplutôt qu'à 1 constructeur
En effectuant ce refactoring, nous avons 22 tests qui échouent...

Pourquoi ? on ne
setplus lesEventsni leStatusà l'instantiationOn va faire en sorte d'avancer en mettant en place une solution transitoire
Nous n'avons plus que 7 tests qui échouent
Ils échouent car l'état des chasseurs n'est pas bon...
On ne set pas les galinettes tuées
On va simuler le fait que le chasseur a tué des galinettes en appelant les méthodes du
Domain
En continuant à fixer les tests, on identifie des tests qui ne sont pas consistants
On itère sur ce test :
D'autres tests posent problème dû à une mauvaise initialisation :
On adapte une nouvelle fois la méthode
Build()pour être capable d'être dans l'état décrit :
Nos tests sont maintenant verts et on peut supprimer les constructeurs inutiles :
Encapsuler le Status
StatusOn commence par passer le
seten private pour identifier les impacts
Nous devons retravailler le
Builderpour qu'il supporte l'encapsulation
On adapte le
Builder
Encapsuler les Events
En passant le
setterdesEventsàprivateon observe que seuls les tests de consultation de status échouentLa consultation du status est déjà testé par ailleurs dans les tests d'Acceptance
Ces tests se recoupant, on peut dès lors supprimer les Tests Unitaires
ConsulterStatussans impact sur notre couverture et notre confiance
Encapsuler les collections
Nous avons grandement amélioré l'encapsulation mais nous pouvons aller plus loin
On va créer des
backing fieldspour ces 2 collectionsLes "sécuriser" via l'utilisation d'une collection
immutable
Ce changement va nous "forcer" à bouger une partie de la logique de la
Factorydans le constructeurOn conserve les
Guardsdans laFactoryet bouge l'instantiation dans le constructeur
Simplification des méthodes de Tir
TirLa méthode
Tirerressemble à cela
On commence par inverser les
ifpour faire diminiuer lacyclomatic complexity

On
Saveen même temps que l'Emit de l'Event
On nomme les guards pour rendre le code plus explicite
En appliquant la même technique sur
TirerSurUneGalinetteon se rend compte de la duplication :
On va créer une méthode
privatede tir permettant de mutualiser le code
Encapsulation du Chasseur
ChasseurDepuis les méthodes de
Tiron va appeler une méthodeATiré()plutôt que d'utiliser 1 setter
On fait de même pour les
NbGalinettes
Un mot sur la duplication
En redescendant la logique dans le Domain et en refactorant ce dernier on a grandement amélioré la qualité du code :

Nouveau rapport SonarCloud disponible ici.
Reflect
Quel impact ce refactoring a eu sur les tests ?
Qu'est-ce qui est plus simple / compliqué maintenant ?

Last updated
Was this helpful?