Un des problèmes retrouvé régulièrement dans les projets de développement est la redondance du code. Beaucoup de développeurs, notamment les juniors (par manque d’expérience) ont en effet tendance à dupliquer des méthodes ou blocs de code. Ce contre quoi se battent les CTO.
Pour justement normaliser ce combat, ou pour le définir de manière plus cadrée, des paradigmes de développement (ou patterns) ont été créés. C’est par exemple le cas du principe DRY : Don’t repeat yourself – ne vous répétez pas.
Vous voulez en savoir plus sur DRY ? Nous vous expliquons tout dans cet article !
DRY : l’origine du problème
Comme nous l’avons dit dans l’introduction, beaucoup de projets de développement contiennent des répétitions inutiles de morceaux de code. Et si ce n’est pas toujours un problème, cela peut le devenir, notamment lors des phases de maintenance et d’évolution du projet.
Typiquement, si nous devons faire une modification (pour la correction d’un bug ou le développement d’une nouvelle fonctionnalité) sur un bloc de code, et que celui-ci est répété à 3 endroits dans le projet ; il va falloir le modifier 3 fois. Ce qui peut devenir un problème si on oublie de le modifier à un endroit en particulier ; cela peut par exemple provoquer des bugs en cascade.
Mais, le plus simple pour comprendre, reste de voir un exemple concret.
Le problème en exemple
Prenons un exemple simple pour expliquer le problème, avec du JavaScript. Nous avons une application web, avec deux formulaires de connexion : un sur la page d’accueil, l’autre sur la page spécifique à la connexion.
Le développeur a choisi de créer deux fonctions séparées pour gérer distinctement les deux formulaires. Ce choix peut être cohérent, et l’implémentation qu’il a fait est la suivante :
// home page controller
loginFromHomePage = (email: string, password: string) => {
const connectRequest = "select * from user where user.email = " + email + " AND user.password = " + password;
const connectionResponse = UserConnector.executeRequest(connectRequest);
}
// login page controller
loginFromLoginPage = (email: string, password: string) => {
const connectRequest = "select * from user where user.email = " + email + " AND user.password = " + password;
const connectionResponse = UserConnector.executeRequest(connectRequest);
}
Il y a donc deux fonctions, chacune construisant une requête SQL de connection pour requêter la base de données.
Imaginons maintenant que, pour une question de rationalité de la base de données, nous décidons de préfixer toutes les colonnes par les trois premières lettres du nom de la table :
- user.email deviendra user.use_email ;
- user.password deviendra user.use_password.
Il faudra donc changer le code à tous les endroits où on construit des requêtes sur la table user. Dans notre cas de figure, il faudra le modifier à deux endroits. Le risque, c’est que le développeur oublie une des deux fonctions, et qu’ainsi le login ne marche plus partout…
Il aurait fallu construire la requête à un seul endroit. Nous verrons comment résoudre ce problème en particulier plus tard dans cet article.
Bien sûr, ce cas est extrêmement simple. Lorsqu’on parle d’appliquer DRY, c’est bien plus souvent au niveau des héritages et abstractions de classes.
Exemple d’application de DRY
Nous l’avons dit, là où le paradigme DRY prend tout son sens, c’est lorsqu’on parle de code à un plus haut niveau : héritages, abstractions, interfaces, etc.
Cela veut dire, par exemple, que plutôt que de répéter les mêmes attributs ou fonctions dans plusieurs classes, mieux vaut créer une “super-classe” contenant ces attributs et fonctions, et gérer le reste via de l’héritage. Il faut bien sûr que cela ait du sens au niveau logique.
Simplifier au maximum le code n’aide pas si cela en complique la compréhension.
Pour reprendre notre exemple de fonctions de login donné dans la section précédente, voyons comment nous aurions pu améliorer ces méthodes en appliquant le principe DRY.
À la place d’avoir ces deux fonctions exécutant le même code, en avoir une seule contenant la plupart de la logique métier aurait plus de sens. Par exemple, on pourrait avoir :
// home page controller
loginFromHomePage = (email: string, password: string) => {
userService.LogUserIn(email, password);
}
// login page controller
loginFromLoginPage = (email: string, password: string) => {
userService.LogUserIn(email, password);
}
// Fonction de connection principale dans le service
LogUserIn = (email: string, password: string) => {
const connectRequest = "select * from user where user.email = " + email + " AND user.password = " + password;
const connectionResponse = UserConnector.executeRequest(connectRequest);
}
Ainsi, si on en vient à modifier les tables SQL, nous ne devrons modifier le code qu’à un seul endroit. Cela évitera les oublis et donc, les possibles bugs.
Pourquoi vous ne devriez pas implémenter DRY
Attention cependant : DRY ne doit pas être implémenté tout le temps !
Cela peut paraitre contre-intuitif, car nous venons de dire et de voir que cela permet d’avoir un code plus propre et plus maintenable, avec moins de potentiels bugs.
Cependant, ce n’est pas aussi simple, et il ne faut pas pousser le principe DRY à son extrême.
Dans notre exemple précédent, nous aurions pu purement et simplement supprimer les deux fonctions de login, et appeler uniquement la nouvelle. Cela semblerait logique, et le faire ainsi aurait du sens. Le principe DRY serait ainsi appliqué et respecté jusqu’au bout.
Cependant, imaginons que le traitement doive différer en fonction de l’origine de la connexion ? Que le retour à l’interface, ou la redirection de l’URL doivent être différentes ? Dans ce cas, mieux vaut ne pas simplifier à l’extrême, et avoir un code légèrement découplé.
Lorsque nous développons une fonctionnalité, il faut essayer de penser au futur, plutôt que de vouloir tout simplifier à l’extrême lors de l’écriture du code. Cela veut dire que le don’t repeat yourself devrait être appliqué lors des phases de refactorisation du code, plutôt que dans sa phase d’écriture.
Même si, évidemment, une grosse partie du code peut être simplifiée dès le début.
Une autre origine possible de la sur-utilisation du DRY, ce sont les règles définies en internes, notamment par les CTO. Beaucoup de ces derniers veulent souvent, à tout prix, avoir un code ultra rationalisé dès le commencement d’un projet. Si cela a du bon, il vaut mieux garder un peu de souplesse, cela évitera de faire des retours en arrière par la suite.
L’effet couteau suisse : ou la mauvaise implémentation de DRY
Les mauvaises raisons d’utiliser le principe DRY citées juste avant peuvent mener à l’effet couteau suisse. On rassemble plusieurs fonctionnalités au même endroit pour en faire un super conteneur de features ; ce qui est en soi une bonne idée.
Mais que se passe-t-il si nous devons ajouter un nouvel outil au couteau, qui ne rentre pas dedans et nécessite de repenser entièrement sa construction ? Ou modifier un des outils présents pour optimiser sa fonctionnalité, faisant bouger ainsi tous les autres ?
Dans ce cas-là, ne vaut-il pas mieux séparer les outils, et avoir une caisse à outils plutôt que tout dans un seul conteneur ?
En clair, et encore une fois : il vaut mieux appliquer le principe DRY lors de la refactorisation du code, plutôt que lors de son développement.
Exemple typique : vous avez développé un composant front-end, un calendrier. Vous y avez intégré d’autres éléments : la gestion de l’heure par exemple. Maintenant, on vous demande de gérer les fuseaux horaires dans ce même calendrier. Cela va impacter non seulement la gestion de l’heure, mais aussi probablement le reste du composant, que cela soit au niveau de son UX/UI ou du code.
L’utilisation de deux composants séparés aurait permis une meilleure maintenance.
L’opposé du concept DRY : le WET (Write Everything Twice)
Á l’opposé du DRY, nous avons le WET ! Signifiant Write Everything Twice (tout écrire deux fois), ceci n’est pas un paradigme, c’est au contraire la mauvaise pratique que DRY essaye de combattre.
Cela vient bien sûr d’un jeu de mot, dry signifiant sec en anglais, et wet, mouillé.
Mauvaise pratique, c’est à relativiser ! Comme nous l’avons dit, il n’est pas forcément mauvais d’écrire le même code, ou des blocs de code proches, plusieurs fois ! Tout est une question de lisibilité, de propreté, et de maintenance.
Conclusion
Comme nous l’avons vu, le principe DRY – don’t repeat yourself – est un paradigme de programmation visant à aider les programmeurs à simplifier leur code. Cela a notamment pour effet de rendre le programme plus simple (à lire, à comprendre, à maintenir), mais aussi empêcher des bugs dus à la multiplication du code.
Cependant, DRY ne doit pas être appliqué partout ! Nous l’avons dit, il y a des exceptions, et il faut les respecter.
Il y a également d’autres principes que ce pattern que l’on peut appliquer pour rendre un code plus clair. Nous avons d’ailleurs dédié un article complet aux bonnes pratiques de codage.
Et vous, êtes-vous adepte de ce concept ? Quelles sont vos recommandations ? Dites-nous tout en commentaire !