.NET, ce qui fait hérisser les cheveux de votre Lead Dev

Retour d’expérience de Thibault Herviou, Consultant Ividata LINK

Actuellement chez un nouveau client, j’ai pu voir plusieurs choses qui m’ont fait bondir de ma chaise. Ce qui m’a donné l’idée de faire un article sur quelques bonnes pratiques et choses à savoir.

Pour des raisons de confidentialité mais aussi de simplicité je ne vais pas reprendre le code de mes clients mais présenter ma propre implémentation.

Débogueur

Fenêtre de variable du débogueur

Comme vous le savez notre IDE préféré nous permet de visualiser un objet, une propriété ou un chant directement dans la fenêtre des variables. Deux possibilités s’offrent à nous, soit en utilisant la fenêtre à cet effet :

Soit faire une pause dans votre code et passer la sourie au niveau de la variable :

Par défaut, la valeur affichée fait appel à la méthode « ToString() » de l’objet en question. Cette méthode a pour but de représenter notre objet mais de façon plus adaptée à un affichage de type texte. C’est pourquoi là aussi par défaut cette méthode retourne le nom complet du type de l’objet, dans notre exemple « Ividatalink.TipsAndTricks.TipsAndTricks ».

Voici notre classe :

Super notre code fontionne bien. Mais voila, un jour un développeur passe par là et souhaite voir rapidement au niveau de sa fenêtre de débogage la valeur de « Count » et de « Flag ».

C’est tout naturellement qu’il a une vraie super mauvaise bonne idée, surcharger « ToString() » pour afficher ces deux variables :

Pourquoi ce n’est pas bien ?

Déjà, on perd le but premier de cette variable qui est de décrire notre objet.

De plus avec cette solution, le débogage influe sur le code de l’application, ce qui est mal.

Pour finir cette variable peut être utilisée autre part pour simplifier un affichage ou autre, il faut donc éviter de l’utiliser à des fins de débogage.

J’ai même pu voir ça :

Je ne sais pas trop quoi dire … avoir volontairement un comportement différent entre les différentes configurations de la solution DEBUG, RELEASE … on ne devrait même pas y penser, surtout que ce n’est pas explicite et si un autre développeur utilise cette variable sans vérifier l’implémentation de la méthode on va à la catastrophe.

Mais alors comment fait-on ?

Le Framework .NET a déjà répondu à cette question, depuis la version 2.0, avec l’attribue «DebuggerDisplayAttribute».

L’utilisation est triviale :

Maintenant que l’on sait gérer la façon dont un type ou un membre s’affiche dans la fenêtre de variable du débuggeur, nous allons voir comment afficher la valeur de cette variable et /ou type.

Si vous êtes familier avec les chaînes interpolées en C# vous ne serez pas déroutés :

Comme vous pouvez le voir, les champs texte entre accolades sont évalués comme une expression.

Cette fonctionnalité est très puissante et permet d’améliorer le débogage, mais comme vous le savez « un grand pouvoir implique de grandes responsabilités », il y a tout de même des règles à suivre pour ne pas voir les performances de débogage s’écrouler.

Il ne faut pas chercher à vouloir afficher tout notre objet comme ceci :

Pour chaque instance de « TipsAndTricks » chaque expression entre accolades est évaluée, et je ne vous cache pas que ça prend du temps, imaginez l’impact au niveau du débuggeur si vous avez une liste de quelques milliers de cet objet.

De plus, on évite d’utiliser le «DebuggerDisplay» de cette manière, on passe plutôt par une propriété privée et explicite :

Pour finir j’ai une petite préférence pour l’utilisation du mot clé « nameof » c’est un peu moins lisible mais ça garantit la validité de notre code :

Optimisation des listes

Au niveau des listes il y a deux implémentations récurrentes que j’ai pu observer lors de mes différentes missions.

La première, je ne comprends toujours pas d’ailleurs pourquoi elle est utilisée :

Dans les grandes lignes, ici on parcourt toute notre liste pour vérifier qu’elle n’est pas vide pour ensuite faire un traitement sur cette dernière, ce n’est vraiment pas performant, j’ai même pu voir une implémentation un peu plus réfléchie :

Ici, on ne parcourt plus toute notre liste, mais on peut faire encore mieux.

Il existe une méthode qui permet de vérifier si au moins un des éléments d’une collection satisfait une condition, c’est « Any() » . Utilisé de cette manière, on détermine juste si notre collection n’est pas vide :

On peut retrouver le même type d’erreur avec LINQ :

Par contre, on a de la chance, LINQ comprend ce que l’on veut faire et ne parcours pas toute la liste, pour des raisons de lisibilité cette implémentation est meilleure :

Comme on est dans le sujet de l’optimisation des listes, je vois souvent des développeurs utiliser des listes intermédiaires, alors l’on peut passer outre.

Connaissez-vous le mot clé « Yield » ?

Le mot clé « Yield » signale au compilateur que la méthode dans laquelle il est, est un bloc itérateur, ce qui veut dire que l’on peut écrire ça :

Ici, il n’y a pas grand intérêt a faire ça, mais ça a le mérite d’être simple et clair, pour la démo voici un exemple concret :

Design pattern

Singleton :

Le singleton mon préféré, on le voit partout et à toutes les sauces.

Comme beaucoup de monde, je commençais à implémenter ce paterne de cette façon :

Eh oui monsieur je connais mes classiques (cf GOF, gang of four), mais les langages évoluent et cette implémentation n’est plus adaptée à une application moderne.

La première chose qui me vient à l’esprit est que cette implémentation n’est pas thread-safe, on vérifie à chaque appel que notre instance n’est pas nul. Bref à proscrire.

Après, il y a eu la mode des doubles vérifications, ce qui a permis d’être thread-safe et de conserver le mode d’instanciation différée, c’est celle que l’on retrouve le plus couramment :

Pourquoi une double vérification ? Un lock c’est coûteux et ça prend du temps, on évite donc de l’utiliser à chaque appel surtout si notre variable est initialisée. La deuxième vérification permet de garantir l’unicité de notre objet. En effet, comme vous le savait le « lock » n’interdit pas à un autre « thread » de poursuivre son exécution au niveau du code verrouillé. Mais il l’oblige à attendre que l’objet soit libéré et dans les cas où l’initialisation de notre objet prend du temps, il se peut que plusieurs « Thread » atteignent le « lock » avant la fin de l’initialisation.

Depuis C# 6 l’implémentation est grandement simplifiée :

Voilà, nous venons de créer un singleton thread-safe et devinez quoi, on a conservé l’instanciation différée. En effet, une variable « static » ne sera créée que lors du premier appel, ce mécanisme du CLR rend en même temps notre variable thread-safe.

Mais il ne faut surtout pas confondre avec cette implémentation :

Car là, vous créez une nouvelle instance pour chaque appel.

Décorateur :

« Ecrire du code SOLIDE / ROBUSTE », j’entends ça tous les jours et on se retrouve avec des classes où l’on a une vérification toute les deux ou trois lignes. Toutes ces vérifications ralentissent l’exécution du code, et rendent la classe moins lisible. Je ne dis pas que ce n’est pas bien de vérifier un minimum, ce qui me dérange c’est d’être obligé de passer par chaque vérification même si l’on est sûr que chaque condition est respectée.

Pour répondre à notre problème, le pattern décorateur permet dans notre cas de déplacer la responsabilité de validation des variables dans une autre classe. Prenons l’exemple d’une classe « logger » qui permet d’écrire au niveau de la sortie courante :

Dans tous les cas, on est obligé de vérifier que notre phrase n’est pas nulle, ni vide, même si l’on est sûr et certain qu’elle ne l’est pas. Voyons comment l’on pourrait améliorer ça :

Ici, on a déplacé la responsabilité de validation au niveau de la classe « NoNullOrEmptyString » ce qui nous permet d’afficher nos informations de deux manières différentes. La premier où l’on assume que notre variable n’est ni nulle ni vide et la deuxième où l’on est pas sûr de notre coup.

Dans cette implémentation, on pourrait parler de micro-optimisation, mais pouvoir se passer de quatre/cinq vérifications, dans ce cas, ça commence à devenir intéressant surtout si l’on boucle sur une liste plus ou moins importante.

Il faut tout de même faire attention et bien réfléchir à son implémentation. Ce pattern permet de rendre nos classes plus lisibles et plus performantes, mais à l’inverse, on peut se retrouver avec des dérives de ce genre au fur et à mesure que la classe évolue :

Dans un contexte de validation ce pattern permet aussi de choisir l’ordre de validation. En effet, il peut être avantageux dans une circonstance plutôt qu’une autre d’intervertir l’ordre de validation.

Les évènements

Je vois de plus en plus de développeurs qui ont consciences de couplage fort qui provoque l’abonnement a un évènement, l’un des patterns les plus utilisé est celui-ci :

On s’appui ic sur le pattern « disposable » imposé par l’interface « IDisposable » pour se désabonner de l’événement. Ça fonctionne plutôt bien, surtout qu’il y a une double protection si jamais, pour une raison ou une autre, la méthode « Dispose() » n’a pas été appelée, le finaliser s’en occupera ( ~EventDisposable() ).

Mais, lorsque l’on passe sur des objets qui n’implémentent pas le pattern « disposable », ça se complique, j’ai pu voir ça :

A première vu on se dit que c’est bien, il s’est abonné et a pensé à se désabonner de son évènement. NON !!!

Comme je vous l’ai dit plus haut, s’abonner à un événement, c’est faire une référence forte entre deux objets tant que cette référence existe ou que nos deux objets ne sont pas libérés, le finaliser ne sera jamais appelé. Voici un exemple:

On crée une classe qui contient notre événement, la classe « MyListener » s’abonne à ce dernier, puis on déclenche notre évènement.

Ensuite on supprime notre objet « MyListener », et on force le « GarbageCollector » à suspendre nos objets.

On déclenche notre évènement une seconde fois.

Et pour finir, on supprime notre classe « MyEvent » et ensuite on force le « GarbageCollector » à suspendre nos objets une seconde fois.

Le but est de mettre en valeur le couplage fort de nos deux objets et l’inutilité de notre finaliser :

Comme vous pouvez le voir, notre classe continue d’écouter les évènements même après son assignation à « null » car notre classe « MyEvent » en a gardé une référence dite forte.

Vous l’aurez compris, pour remédier à ce problème nous allons utiliser une référence dite faible. Il y a juste deux lignes à modifier au niveau du code précédent :

Et voici le résultat

C’est tout de même mieux, non ? En tout cas cela ressemble davantage à ce que l’on a voulu faire lors de notre première implémentation.

Il faut donc être bien attentif lorsque l’on utilise les évènements mais surtout à la manière dont on se désabonne. Heureusement, la classe « WeakEventManager » nous facilite grandement le travail. Le seul point noir de cette implémentation est au niveau du « listeneur » qui gère la référence faible.

Conclusion

Le .Net Framework est très riche au niveau fonctionnalité tout comme le C#, on s’y perd très vite.

Mais lorsqu’on se donne la peine de faire des recherches, on retrouve des patrons de conception et aussi des classes qui nous facilitent grandement la vie et/ou optimisent notre code. La documentation officielle sur MSDN est très bien pour ça avec des exemples concrets et nous propose toujours d’aller plus loin. Quand je me documente au niveau de MSDN sur une classe/objet je me retrouve très vite avec cinq ou six onglets ouverts sur mon navigateur.

A propos de l’auteur :

Thibault Herviou : Architect logiciel / développeur d’application, je suis avant tout technophile / bidouilleur en tous genres, du raspberry pi aux imprimantes 3D en passant par le brassage de bières amateur. De nature curieuse, je suis adepte du « DIY », en effet, j’aime avoir la satisfaction de faire les choses par moi-même, mais surtout, pouvoir comprendre le fonctionnement des objets/technologies qui m’entourent.

! Retrouvez aussi son dernier article en cliquant ici !