La semaine dernière, j'ai parlé de la masturbation intellectuelle. J'aimerais maintenant débuter une nouvelle série qui parlera des anti-patterns et des mauvaises conceptions communs. Vous pourrez trouver tous les articles concernant ce sujet ici.

J'aimerais vous rappeler que je n'ai pas la science infuse, et que je n'ai pas la prétention de l'avoir. Ainsi, ce que je dis peut ne pas s'appliquer à certains contextes. Je suis cependant convaincu que les pratiques que je décris devraient faire l'objet d'une longue réflexion avant d'être mis en place, car j'ai été brûlé plus qu'une fois par ces anti-patterns et mauvaises conceptions.

Les méthodes statiques

Le mot-clef est staticdans bien des langages. Peu importe l'expression utilisée, on fait référence à la même horreur. Longtemps j'ai pensé que les classes et les fonctions statiques étaient d'une utilité cruciale, mais j'avais tort.

En fait, les méthodes statiques existent pour tenter. Succomber à la tentation mène directement vers un mauvais design et vers l'enfer des développeurs, alors prenez garde! La raison principale pour laquelle ce patron est nul est à cause de l'impossibilité d'appliquer le polymorphisme à travers une classe ou une fonction statique. Cela a de graves conséquences, y compris de coupler les types entre eux, d'empêcher tous système d'injection de dépendances de fonctionner, et de limiter la testabilité d'un composant.

Depuis près d'un an maintenant, je m’efforce de créer des interfaces, et parfois des classes de base à toutes les classes que je développe. L'exercice m'a semblé excessivement redondant pendant quelques mois: Écrire la méthode public dans la classe, puis copier sa signature dans l'interface. Par contre, l'exercice me permettait d'utiliser un comportement fort intéressant: la possible d'étendre mon code sans le modifier. Même si cette capacité a une limite, celle de la prévoyance, elle a de forts avantages, à commencer par la testabilité. Il est effectivement possible de créer une implémentation complètement moquée de chacune des composantes du système. Cela signifie que les tests unitaires peuvent effectivement s'exécuter sans jamais avoir besoin de communiquer avec une base de données. Cela fournit une base solide aux applications. De plus, ces tests peuvent s'exécuter, peu importe le contexte. Ainsi, les serveurs de build pourront alors assurer la qualité du code. Cela ne serait pas possible avec un design statique.

De plus, en utilisant certains outils dont je tairai le nom, je me suis rendu compte de la mauvaise utilisation répandue des classes et méthodes statiques. En effet, dans un des ces outils, beaucoup de fonctionnalités sont invoquées à l'aide du contexte statique de l'outil. Il s'en suit généralement un immense problème, car il est possible et même facile d'accéder à des fonctions alors que le contexte ne s'y prête pas. Il faut donc se questionner sur le contexte en cours avant d'invoquer une méthode, puisque l'outil ne le gère. Ce n'est pas très pratique...

Je ne dis pas qu'il faut éradiquer toutes les implémentations statiques de son code: j'aime personnellement me servir de méthodes statiques lorsqu'il s'agit de méthodes utilitaires qui peuvent être employées à de nombreux endroits. Je dis simplement qu'il est bien souvent possible de s'en sortir sans. Lorsque c'est possible, il ne faut pas hésiter à le faire.

Lorsque je choisis un design statique, je fais fort attention à suivre ces quelques règles.

Ne pas travailler avec des objets qui ont des états

Les méthodes statiques devraient toujours travailler avec des types immuables, comme des string ou des types sans accesseurs set publics. Cela évite les effets de bord indésirables. On peut également plus facilement invoquer une méthode alors que le contexte ne s'y prête absolument pas, comme dans mon exemple ci-haut. C'est exactement le même principe que des méthodes globales qui sont, elles aussi, considérées comme des tares. Je vous propose plutôt de créer une nouvelle instance de la classe avec laquelle la méthode travaille qui comprendra l'ensemble des modifications nécessaires.

Personnellement, j'utilise les méthodes statiques entre autres pour:

  • Extraire ou ajouter des paramètres en QueryString dans un URL.
  • Déterminer si un URL est local.
  • Extraire une donnée à l'aide d'une Regex bien définie.

Ne pas accéder à des ressources externes

Dans une méthode statique, n'accédez surtout pas à des ressources externes. La raison est que le créateur d'une ressource externe doit également être son destructeur. Mais comme les méthodes statiques ne peuvent pas gérer d'états multiples facilement, cette règle peut difficilement être mise en place. Ainsi, il faudra que l'utilisateur de cette méthode se rappelle de se débarrasser de cette ressource. Ce sera alors un détail inutile dont il faudra se souvenir. Je peux vous le garantir: des erreurs seront faites.

La solution à ce problème est simple: Il faut créer une classe. Comme le dit Uncle Bob, les méthodes sont là où vont les objets pour se cacher. N'hésitez pas à créer de nouveaux types si ceux-ci ont du sens.

En C#, par exemple, si vous avez besoin d'ouvrir une connexion à une ressource plutôt difficile d'accès, créez une classe pour cela, et faites-lui implémenter l'interface IDisposable. Vous aurez alors une chance de fermer la connexion pour l'utilisateur de votre classe.

using(var connexion = new ConnexionSuperCompliquee())
{
    //Faire ce que l'on doit ici.
}
/*La connexion est maintenant fermée et les ressources sont libérées.*/

Ne pas utiliser de contextes externes

Je conseille également fortement d'éviter d'accéder à des contextes externes. Dans un des CMS que j'ai eu l'occasion d'utiliser, il est possible d'accéder à un objet dans la base de données alors que le contexte de cette BD est oublié, car elle réside dans le contexte Http. La perte de ce contexte se produit entre autres quand on tente d'accélérer un processus en le parallélisant, ou quand on utilise dans méthodes asynchrones afin de ne pas bloquer les appels lors de l'attente de ressources IO. Il aurait été aisé de faire en sorte que le contexte du CMS soit passé sur la pile. À cause de cette erreur de conception, il appartient maintenant aux développeurs utilisant ce framework de faire attention à ce détail, au risque de se prendre une exception en pleine face. Un système qui nécessite de connaître des nombreux détails est un système qui est compliqué et que personne n'aime.

Un bon exemple de conception est le retrait de la propriété statique HttpContext.Current dans ASP.NET 5.0. Cette propriété permettait d'accéder au contexte Http en cours, avec le risque évident qu'aucun contexte ne soit disponible. Aujourd'hui, on privilégiera d'extraire les données du contexte dans le contrôleur MVC, de créer une instance d'un type DTO qui contiendra l'information, sans dépendance au Web, puis de le passer en paramètre aux différents composants qui en auront besoin. Ce sera alors plus facile de tester les systèmes, sans que ceux-ci ne nécessitent la connaissance de concept de cookies.

Qu'en dites-vous?

Comme je vous l'ai dit, ce que je partage avec vous ici sont des difficultés que j'ai vécu. J'aimerais vous les éviter, mais je sais que ce sera probablement en vain. Je voudrais tout de même connaître votre opinion sur le sujet des méthodes statiques. Agréez-vous avec ces conseils? Qu'ajouteriez-vous? Parlez-m'en dans les commentaires.