Certaines fonctions se comportent comme une formule de tableur : donnez-leur les mêmes entrées, elles rendent toujours la même sortie, et rien d'autre ne bouge dans le programme.
Ces fonctions sont dites pures. Elles se testent sans préparation, se relisent sans contexte, et se déplacent d'un fichier à l'autre sans surprise.
Définition
Une fonction pure respecte deux conditions simultanément. Elle renvoie toujours le même résultat pour les mêmes arguments, et elle ne produit aucun Effet de bord, c'est-à-dire aucune modification en dehors d'elle-même.
function totalTTC(prixHT, taux) {
return prixHT * (1 + taux);
}
console.log(totalTTC(100, 0.2)); // 120
console.log(totalTTC(100, 0.2)); // 120, toujours
let remise = 0.1;
function totalImpur(prixHT) {
return prixHT * (1 - remise); // dépend d'une variable extérieure
}
console.log(totalImpur(100)); // 90
remise = 0.5;
console.log(totalImpur(100)); // 50, même appel, autre résultatLa seconde fonction n'est pas fausse, elle est simplement imprévisible en isolation : son résultat dépend d'une valeur qui n'apparaît nulle part dans son appel.
Le deuxième piège : modifier ses arguments
Une fonction peut renvoyer la bonne valeur et rester impure, parce qu'elle a modifié au passage ce qu'on lui a confié.
function ajouterImpur(liste, article) {
liste.push(article); // modifie l'argument reçu
return liste;
}
function ajouterPur(liste, article) {
return [...liste, article];
}
const panier = ["clavier"];
ajouterPur(panier, "souris");
console.log(panier.length); // 1, l'original est intact
ajouterImpur(panier, "souris");
console.log(panier.length); // 2La version pure produit une nouvelle liste avec le Spread (...). C'est le principe de l'Immuabilité, appliqué à l'échelle d'une fonction.
Les impuretés qui se cachent
Math.random()etDate.now(): deux appels identiques, deux résultats. Passez la valeur en argument.- La lecture d'une variable extérieure : le résultat dépend d'un état invisible depuis l'appel.
- La modification d'un objet reçu : l'appelant se retrouve avec une donnée qu'il n'a pas changée.
- L'écriture d'un journal ou d'un stockage : même sans changer le résultat, c'est un effet.
Une fonction pure est mémoïsable par construction : puisque le résultat ne dépend que des arguments, le cache ne peut pas se tromper. Voir Mémoïsation.
Questions fréquentes
Tout un programme peut-il être pur ?
Non, et ce n'est pas le but. Un programme qui n'écrit rien, n'affiche rien et n'appelle aucun service ne sert à rien. L'objectif est de rassembler les effets à la lisière du code et de garder pur tout ce qui calcule, ce qui représente souvent la majeure partie d'une application.
Une fonction pure peut-elle appeler une autre fonction ?
Oui, à condition que celle-ci soit pure elle aussi. La pureté se propage : une fonction qui n'appelle que des fonctions pures et ne touche à rien d'extérieur reste pure. Un seul appel impur dans la chaîne suffit en revanche à contaminer l'ensemble.
Quel bénéfice concret pour les tests ?
Un test de fonction pure tient en une ligne : on appelle avec des valeurs, on compare le retour. Aucune base à préparer, aucun service à simuler, aucun nettoyage après coup. C'est la raison pour laquelle un Test unitaire sur du code impur est presque toujours plus long à écrire qu'à faire passer.