N'abusez pas de la programmation fonctionnelle
-
Soit
History
une classe dont le constructeur initialise les propriétésidentifier
etold_value
.$histories = [ new History(1, 'anomaly'), new History(2, 'signed'), new History(3, 'incomplete'), new History(2, 'canceled'), new History(1, 'signed') ];
Prenons cette boucle en programmation fonctionnelle, et essayons de comprendre ce qu’elle fait :
$identifiers = []; array_walk($histories, function($history) use (&$identifiers) { if (!isset($identifiers[$history->identifier])) { $identifiers[$history->identifier] = $history->old_value; } });
Si on demande à plusieurs développeurs ce que contiendront$history
et$identifiers
à la fin de la boucle :- combien de temps mettront-ils à donner leur réponse ?
- quel taux d’erreurs obtiendront-nous ?
Voyons maintenant ce code, strictement équivalent, et posons la même question à d’autres développeurs (qui n’ont pas vu la première partie du problème, pour ne pas être influencés) :
$identifiers = []; foreach ($histories as $history) { if (!isset($identifiers[$history->identifier])) { $identifiers[$history->identifier] = $history->old_value; } }
La différence entre ces deux portions de codes peut sembler vraiment très légère, et de l’ordre du détail, mais peut-on en déduire laquelle est la plus maintenable ?
Pour quelles raisons les développeurs comprennent en général mieux et plus rapidement la seconde version ?array_walk
nécessite un niveau d’expertise plus avancé : tous les développeurs PHP savent lire unforeach
couramment, mais les développeurs qui on l’habitude de lire couramment de la programmation fonctionnelle sont moins nombreux, ce code étant moins fréquemment rencontré dans les logiciels. Le code est donc moins maintenable, car ouvert à un public plus restreint de développeurs.
- L’écriture fonctionnelle, dans ce cas, nécessite plus de “mots” : 32 en programmation fonctionnelle contre 26 en programmation classique (je compte ici comme mots chaque mot, et chaque opérateur dans la liste suivante compte pour un mot : ->, =, {}, (), [], &, !, ;. bref chaque chose qui donne une information supplémentaire au développeur). On pourrait aussi plus brutalement compter le nombre de caractères : 169 compte 200. De nombreuses études montrent que la complexité du code et les risques de bogues sont avant tout liés à la quantité de code : moins de code entraine moins de bogues. Cela s’applique également quand on parle de lisibilité du code, un code plus concis, s’il énonce aussi clairement ce qu’il fait qu’un code plus important, sera de toutes les façons moins complexe et plus compréhensible. Donc plus maintenable.
- Un autre argument contre la généralisation de l’écriture fonctionnelle : à votre avis quel sera le coût en terme de temps d’exécution ?
- Ecriture fonctionnelle : on l’a vu, il y a plus de mots, donc assez logiquement plus d’opérations. Il y aura donc plus de bytecode à exécuter.
- Le coût d’un appel et de la réponse à un appel (déplacement dans le code) est relativement important : on se déplace deux fois dans le code, avec gestion des piles d’appels et de la mémoire, par fonction appelée. Il y a un appel de fonction par entrée dans le tableau
$histories
, en plus de l’appel àarray_walk
, dans la version fonctionnelle. Dans la version procédurale on a un retour au début du foreach, qui est un seul saut sans gestion de pile mémoire, donc plus de 2x plus rapide qu’un appel+réponse à une fonction. On peut donc légitimement penser que les performances seront meilleures si on s’absout de l’étape fonctionnelle. Voir test de performances sur les boucles.
Alors s’il vous plaît, ne mettez pas de la programmation fonctionnelle partout : ne le faites que si ça apporte un réel gain fonctionnel ou de lisibilité. Mais si c’est pour juste remplacer une petite boucle, ça n’a pas d’utilité.