Nous avons été en train de changer la façon dont notre application AS3 parle à notre back-end et nous sommes en train d'implémenter un système REST pour remplacer l'ancienne).
Malheureusement, le développeur qui a commencé les travaux est maintenant en congé de maladie de longue durée et il m'a été remis. Je travaille avec elle depuis environ une semaine et je comprends le système, mais il y a une chose qui m'inquiète. Il semble y avoir beaucoup de passage de fonctions en fonctions. Par exemple, notre classe qui fait l'appel à nos serveurs prend une fonction à laquelle il appellera et passera ensuite un objet lorsque le processus sera terminé et que les erreurs auront été gérées, etc.
Cela me donne ce "mauvais pressentiment" où j'ai l'impression que c'est une pratique horrible et je peux penser à quelques raisons, mais je veux une confirmation avant de proposer un remaniement du système. Je me demandais si quelqu'un avait une expérience avec ce problème possible?
Ce n'est pas un problème.
C'est une technique connue. Ce sont fonctions d'ordre supérieur (fonctions qui prennent des fonctions comme paramètres).
Ce type de fonction est également un élément de base de programmation fonctionnelle et est largement utilisé dans les langages fonctionnels tels que Haskell .
De telles fonctions ne sont ni mauvaises ni bonnes - si vous n'avez jamais rencontré la notion et la technique, elles peuvent être difficiles à saisir au début, mais elles peuvent être très puissantes et sont un bon outil à avoir dans votre ceinture d'outils.
Ils ne sont pas seulement utilisés pour la programmation fonctionnelle. Ils peuvent également être appelés rappels :
Un rappel est un morceau de code exécutable qui est passé en argument à un autre code, qui devrait rappeler (exécuter) l'argument à un moment opportun. L'invocation peut être immédiate comme dans un rappel synchrone ou elle peut se produire plus tard, comme dans un rappel asynchrone.
Pensez au code asynchrone pendant une seconde. Vous passez une fonction qui, par exemple, envoie des données à l'utilisateur. Ce n'est que lorsque le code est terminé que vous appelez cette fonction avec le résultat de la réponse, que la fonction utilise ensuite pour renvoyer les données à l'utilisateur. C'est un changement de mentalité.
J'ai écrit une bibliothèque qui récupère les données Torrent de votre seedbox. Vous utilisez une boucle d'événements non bloquante pour exécuter cette bibliothèque et obtenir des données, puis les renvoyer à l'utilisateur (par exemple, dans un contexte de socket Web). Imaginez que vous avez 5 personnes connectées dans cette boucle d'événement, et l'une des demandes pour que les données de torrent de quelqu'un stoppent. Cela bloquera toute la boucle. Vous devez donc penser de manière asynchrone et utiliser des rappels - la boucle continue de fonctionner et le "redonner les données à l'utilisateur" ne s'exécute que lorsque la fonction a terminé son exécution, il n'y a donc pas d'attente. Tirez et oubliez.
Ce n'est pas une mauvaise chose. En fait, c'est une très bonne chose.
Passer des fonctions dans des fonctions est si important pour la programmation que nous avons inventé fonctions lambda comme raccourci. Par exemple, on peut tiliser des lambdas avec des algorithmes C++ pour écrire du code très compact mais expressif qui permet à un algorithme générique la possibilité d'utiliser des variables locales et d'autres états pour faire des choses comme la recherche et le tri.
Les bibliothèques orientées objet peuvent également avoir callbacks qui sont essentiellement des interfaces spécifiant un petit nombre de fonctions (idéalement une, mais pas toujours). On peut ensuite créer une classe simple qui implémente cette interface et transmettre un objet de cette classe à une fonction. C'est une pierre angulaire de programmation événementielle , où le code au niveau du framework (peut-être même dans un autre thread) doit appeler un objet pour changer d'état en réponse à une action de l'utilisateur. L'interface ActionListener de Java en est un bon exemple.
Techniquement, un foncteur C++ est également un type d'objet de rappel qui utilise le sucre syntaxique, operator()()
, pour faire la même chose.
Enfin, il existe des pointeurs de fonction de style C qui ne doivent être utilisés qu'en C. Je n'entrerai pas dans les détails, je les mentionne simplement pour être complet. Les autres abstractions mentionnées ci-dessus sont de loin supérieures et doivent être utilisées dans les langues qui en disposent.
D'autres ont mentionné la programmation fonctionnelle et comment le passage de fonctions est très naturel dans ces langages. Les lambdas et les rappels sont la façon dont les langages procéduraux et OOP imitent cela, et ils sont très puissants et utiles.
Comme déjà dit, ce n'est pas une mauvaise pratique. C'est simplement un moyen de découpler et de séparer les responsabilités. Par exemple, dans OOP vous feriez quelque chose comme ceci:
public void doSomethingGeneric(ISpecifier specifier) {
//do generic stuff
specifier.doSomethingSpecific();
//do some other generic stuff
}
La méthode générique délègue une tâche spécifique - dont elle ne sait rien - à un autre objet qui implémente une interface. La méthode générique ne connaît que cette interface. Dans votre cas, cette interface serait une fonction à appeler.
En général, il n'y a rien de mal à passer des fonctions à d'autres fonctions. Si vous effectuez des appels asynchrones et que vous souhaitez faire quelque chose avec le résultat, vous aurez besoin d'une sorte de mécanisme de rappel.
Il existe cependant quelques inconvénients potentiels des rappels simples:
Avec des services Web simples, la façon dont vous le faites fonctionne bien, mais cela devient gênant si vous avez besoin d'un séquencement d'appels plus complexe. Il existe cependant quelques alternatives. Avec JavaScript par exemple, il y a eu un changement vers l'utilisation des promesses ( Ce qui est si génial avec les promesses javascript ).
Ils impliquent toujours la transmission de fonctions à d'autres fonctions, mais les appels asynchrones renvoient une valeur qui prend un rappel plutôt que de prendre un rappel directement eux-mêmes. Cela donne plus de flexibilité pour composer ces appels ensemble. Quelque chose comme ça peut être implémenté assez facilement dans ActionScript.