11) "Avoid Exceptions"

Quel est le problème avec ce code ?
tirerUseCase.Handle(new Domain.Commands.Tirer(id, Data.Bernard));
public TResponse Handle(TRequest command)
{
var partieDeChasse = _repository.GetById(command.PartieDeChasseId);
if (partieDeChasse == null)
{
throw new LaPartieDeChasseNexistePas();
}
var response = _handler(partieDeChasse, command);
_repository.Save(partieDeChasse);
return response;
}Si on regarde la signature de la méthode Handle :
TRequest->TResponseQue l'on peut traduire par :
Pour tout TRequest je te retourne 1 TResponseCe qui est faux puisque cette méthode et la méthode d'handling peuvent lancer des exceptions
La signature de cette méthode ne représente pas de manière explicite les sorties possibles de cette dernière
Souvent notre code contient ce genre de mensonges...

Pour aller plus loin sur ce sujet je t'invite à regarder la super conférence de Scott Wlaschin sur le sujet : Functional Design Patterns :
Nous allons chercher à rendre ce code plus explicite en :
Évitant l'utilisation à outrance des
ExceptionElles sont beaucoup trop utilisés pour représenter des cas d'erreurs business sous contrôles
Les remplaçant par des retours de type
ErrorUtilisant les fameuses
Monads
Comment ?
Prendre du temps pour lire ces pages :
À l'aide de
T.D.Det duStranglerpattern, refactorer leUse CaseTirerafin que la signature deHandleressemble à :TRequest->Either<Error, TResponse>soit
Commands.Tirer->Either<Error, VoidResponse>On limitera le type
Errorà 1 message décrivant l'erreur qui s'est produite
Le Use Case : Tirer
Use Case : TirerPour implémenter notre méthode nous allons repartir de la Test List actuelle :
🔴 On commence par écrire 1 test qui échoue dans la classe de test existante
On y décrit nos attentes vis-à vis de la future méthode
On ne compile pas et donc le test échoue

On génère depuis le test le code de la méthode
HandleSansException
On ajoute les références nous permettant d'utiliser des monades existantes :
LanguageExt
On doit maintenant générer la classe
Error

On "fixe" les assertions du test pour pouvoir compiler
On est maintenant au
rougepour une bonne raison

🟢 On fait passer le test au vert le plus rapidement possible
Ici on peut retourner directement 1
Errorgrâce à l'import denamespaceci-dessous et l'implicit conversion :
🔵 Qu'est-ce qui peut être amélioré ici ?
Peut-être, faire une
Factory Methodpour l'instantiation desError
On peut également refactorer le test afin que ce dernier respecte la structure
Given / When / ThenOn peut isoler les méthodes actuelles dans une classe
partialOn sait qu'à terme elle pourra être supprimée...

On décrit ce qu'on voudrait dans le
Given / When / Thenet l'extrait puis "générifie" le tout
Voici où on en est :
Le chasseur n'est pas dans la partie
🔴 On modifie le test existant
🟢 On doit adapter la méthode HandleSansException pour supporter cette fonctionnalité
🔵 Appeler le code du Domain
Le chasseur n'a plus de balle
🔴 On modifie le test existant
🟢 On adapte encore une fois la méthode HandleSansException
🔵 Cette logique d'exception devra disparaitre, il est donc temps de s'y attaquer.
On va modifier la méthode du
Domainpour qu'elle ressemble à celà :string->Func<DateTime>->IPartieDeChasseRepository->Either<Error, PartieDeChasse>
Nous voulons quelque chose du genre dans le
Use Case:
On adapte le code de la
PartieDeChasseafin de couvrir les besoins actuelles :
On a bien avancé :
Finir la Test-List
En finissant la
Test-Listnous avonc le code duDomainqui ressemble à ça :
On a pas mal de duplication à supprimer
Chaque message est construit 2 fois -> pour l'event et l'erreur
De la même manière, avons-nous encore besoin d'appeler le
Savedu repository ?Celui-ci était présent car les exceptions "coupaient" le flow...
Notre
Use Casepeut très bien le faire de manière systématique
🔵 On extrait une nouvelle méthode
On itère dessus, ainsi que sur le code du Use Case :
On ajoute une méthode sur le repository afin de pouvoir construire 1 pipeline :
Après avoir tout refactoré...
Une fois que l'on a fini de refactoré le Domain et tous les Use Cases pour retourner des monads il est temps de nettoyer le code
On commence par vérifier que les exceptions ne sont plus utilisées

On peut les supprimer de manière
safe

On supprime le code mort comme la méthode
GetByIddu repository

On supprime les paramètres inutiles (
repository)

À la fin de cette étape le code du `PartieDeChasseUseCase` ressemble à ça :
Et le code d'un
Use CasecommeTirer:
Impact sur Sonar Cloud
Sonar Cloud
Plus aucun Code Smells à signaler sans se focaliser dessus 🤗👍
Nouveau rapport SonarCloud disponible ici.
Reflect
Qu'est-ce que vous pensez des
Monads?Quel est leur impact sur notre code ?
Quel impact ce refactoring a eu ?
Qu'est-ce que ça pourrait avoir comme impact sur votre code de production ?

Last updated
Was this helpful?