Découvrir le principe du «AHA Programming»
Le concept DRY vous parait semé d'embûches mais personne ne semble de votre avis ? Cet article devrait vous intéresser.
Article publié le 22/12/2021, dernière mise à jour le 22/10/2024
J'ai récemment écrit un article pour présenter le concept de «DRY Programming», que je vous invite à lire si ce n'est pas déjà fait.
En substance, un code «DRY» évite toute sorte de répétition de code en regroupant les morceaux de codes similaires dans des abstractions (classes, fonctions, ...)
Les inconvénients du DRY
En théorie, il est difficile d'avancer que le "Don't Repeat Yourself" est un principe erroné, car la répétition naïve de code est rarement quelque chose de souhaitable dans un projet.
Néanmoins, l'abstraction à outrance, ou simplement la mauvaise abstraction, coûte plus cher que la répétition du code.
C'est notamment ce qu'avance Sandi Metz dans son article "The Wrong Abstration".
Une mauvaise abstraction peut induire (dans le désordre) :
- De la complexité accidentelle
- Des fonctions/méthodes trop lourdes ou incompréhensible
- Du code maintenu uniquement pour soutenir les tests, même si une grosse partie n'est plus actuellement utilisée dans le logiciel
- De l'aversion à la perte quant au risque de la suppression ou modification du-dit code
Et le WET alors ?
Certaines personnes, comme Conlin Durbin, on essayé de donner du sens à l'inverse du DRY en "créant" le principe WET : Write Everything Twice.
L'idée : Pour éviter l'abstraction inutile de code, s'autoriser à ré-écrire deux copies d'un même morceau de code, et commencer à créer une abstraction à partir de la troisième version.
Si l'idée est charmante en théorie, elle conserve le même travers que son antonyme DRY, c'est-à-dire qu'elle est dogmatique et ne laisse pas suffisamment la place à la réflexion.
On ressent dans ces concepts, comme une odeur de "Cargo Cult" : un principe devrait nous aider à réfléchir, pas se substituer à la réflexion.
Le compromis : le AHA Programming
C'est après avoir compris les contraintes des deux approches précédemment évoquées, que Kent C. Dodds est parvenu à introduire un nouveau concept prônant le meilleur des deux mondes : le AHA Programming (précédemment nommé MOIST, en référence à DRY et WET).
AHA signifie "Avoid Hasty Abstraction", soit "Eviter les abstractions hatîves" en Français
Il se base sur le principe décrit précédemment par Sandi Metz :
"prefer duplication over the wrong abstraction"
En se basant sur le fait qu'on ne connait pas quel sera le futur du code que l'on est en train d'écrire, et qu'il est contre-productif d'optimiser l'entièreté du code derrière des abstractions qui devront peut-être complètement changer à cause de mauvaises assomptions, ou de changement de direction pour la logique métier, Kent suit une directive :
"Optimize for change first" ou "D'abord optimiser pour changements"
Qu'est-ce que cela signifie en réalité ?
Comme nous sommes dans l'inconnu face à l'avenir de notre code, Kent favorise la duplication jusqu'à temps que l'on soit sûr et certains que ces morceaux de code peuvent (et doivent) effectivement être rendus abstraits.
Le risque de commencer l'abstraction trop tôt réside notamment dans le fait que pour chaque ligne, ressemblant de près ou de loin à quelque chose que vous avez déjà rendu abstrait, alors vous essayerez de tordre (modifier) l'abstraction pour coller à votre cas d'usage.
Et ceci, jusqu'à ce que votre fonction, censée simplifier le code, devienne en réalité illisible à force de refactor et d'ajouts de "if" à la chaine, pour prendre en compte tous les cas différents.
Exemple
Comme une ligne de code vaut mieux que mille mots, voici un exemple d'un code trop répétitif, la version DRY, et la version AHA :
À noter que ces exemples sont inspirés de la présentation de Kent C. Dodds pour l'évènement React Summit que je vous invite à regarder !
Base
// user-list.js
const name = user.name ? `${user.name.first.slice(0, 1)}. ${user.name.last}` : user.username ? `@ ${user.username}` : 'Anonymous';
console.log(name);
/*
Possible Results :
- J. Doe
- @jdoe
- Anonymous
*/
// profile.js
const name = user.username ? `@ ${user.username}` : user.email;
console.log(name);
/*
Possible Results :
- @jdoe
- jdoe@example.com
*/
// navbar.js
const name = user.name ? `${user.name.first}. ${user.name.last}` : 'Anonymous';
console.log('Hello', name);
/*
Possible Results :
- Hello John Doe
- Hello Anonymous
*/
DRY
function getName(user, {firstnameInitial, displayEmailByDefault}){
let name = 'Anonymous';
if(user.name) {
let first = user.name.first;
if(firstnameInitial){
first = first.slice(0, 1);
}
name = `${first}. ${user.name.last}`;
} else if(user.username) {
name = user.username;
} else if(displayEmailByDefault){
name = user.email;
}
return name;
}
// user-list.js
console.log(getName(user,{firstnameInitial: true}));
// profile.js
console.log(getName(user, {displayEmailByDefault: true}));
// navbar.js
console.log('Hello', getName(user));
AHA
function getFullName(user, firstnameInitial){
let name = 'Anonymous';
if(user.name) {
let first = firstnameInitial ? user.name.first.slice(0, 1) + '.' : user.name.first;
name = `${first} ${user.name.last}`;
}
return name;
}
function getUsernameOrEmail(user){
let username = user.username;
if(!username) {
username = user.email
}
return username;
}
// user-list.js
console.log(getFullname(user, true));
// profile.js
console.log(getUsernameOrEmail(user));
// navbar.js
console.log('Hello', getFullname(user));
Conclusion
Aucun des concepts de DRY ni de WET ne sont à jeter, mais ce qu'il faut éviter à tout pris, c'est l'application dogmatique des principes et l'abstraction trop hâtive, voir inutile.
Aucun commentaire pour l'instant