Passer des fonctions à d’autres fonctions en tant que paramètres, mauvaise pratique?


40

Nous sommes en train de changer la manière dont notre application AS3 se connecte à notre ordinateur et nous mettons en place un système REST pour remplacer notre ancien.

Malheureusement, le développeur qui a commencé le travail est maintenant en congé de maladie de longue durée et il m'a été remis. Je travaille avec ce logiciel depuis environ une semaine et je comprends le système, mais il y a une chose qui me préoccupe. Il semble y avoir beaucoup de passage de fonctions en fonctions. Par exemple, notre classe qui appelle nos serveurs prend une fonction qu’elle appellera et transmettra un objet à la fin du processus et lorsque les erreurs auront été gérées, etc.

Cela me donne ce "mauvais pressentiment" où je sens que c'est une pratique horrible et je peux penser à certaines raisons mais je veux une confirmation avant de proposer un remaniement du système. Je me demandais si quelqu'un avait une expérience de ce problème possible?


22
Pourquoi vous sentez-vous comme ça? Avez-vous une expérience avec la programmation fonctionnelle? (Je suppose que non, mais vous devriez jeter un coup d'œil)
Phoshi

8
Alors considérez ceci comme une leçon! Ce que les universitaires enseignent et ce qui est réellement utile ont souvent peu de chevauchement. Il existe tout un monde de techniques de programmation qui ne sont pas conformes à ce type de dogme.
Phoshi

32
Le transfert de fonctions à d’autres fonctions est un concept tellement fondamental que, lorsqu'un langage ne le prend pas en charge, les utilisateurs s’efforcent de créer des "objets" triviaux dont le seul but est de contenir la fonction en question comme une solution de rechange.
Doval

36
De retour dans ma journée, nous avons appelé ces fonctions de transfert "Rappels"
Mike

Réponses:


84

Ce n'est pas un problème

C'est une technique connue. Ce sont des fonctions d'ordre supérieur (fonctions qui prennent des fonctions en tant que paramètres).

Ce type de fonction est également un élément de base de la programmation fonctionnelle et est largement utilisé dans des 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 à comprendre au début, mais elles peuvent être très puissantes et constituent un bon outil pour votre ceinture d'outils.


Merci pour cela, je n'ai aucune expérience réelle de la programmation fonctionnelle, je vais donc m'y intéresser. Cela ne me convenait pas, car, à ma connaissance, lorsque vous déclarez une fonction privée, seule cette classe doit y avoir accès et les fonctions publiques doivent être appelées en référence à l'objet. Je vais y jeter un coup d'œil et je m'attends à venir l'apprécier un peu plus!
Elliot Blackburn

3
La lecture sur les continuations , le style de continuation , les monades (programmation fonctionnelle) devraient également être utiles.
Basile Starynkevitch

8
@BlueHat vous déclarez que quelque chose est privé si vous voulez contrôler son utilisation, si cette utilisation sert de rappel pour des fonctions spécifiques, c'est parfait
reaket freak

5
Exactement. Si vous le marquez comme privé, vous ne voulez pas que quelqu'un d'autre vienne et y accède par son nom à tout moment. Transférer une fonction privée à une autre personne parce que vous souhaitez spécifiquement qu'elle l'appelle de la manière dont elle documente ce paramètre est tout à fait acceptable. Cela n'a pas besoin d'être différent de passer la valeur d'un champ privé en tant que paramètre à une autre fonction, qui n'est probablement pas mal avec vous :-)
Steve Jessop

2
Il est à noter que si vous écrivez des classes concrètes avec des fonctions en tant que paramètres de constructeur, vous le faites probablement mal tm.
Gusdor

30

Ils ne sont pas simplement utilisés pour la programmation fonctionnelle. Ils peuvent aussi être appelés callbacks :

Un rappel est un morceau de code exécutable transmis en tant qu'argument à un autre code, qui est censé rappeler (exécuter) l'argument à un moment opportun. L'invocation peut être immédiate, comme dans un rappel synchrone, ou peut se produire ultérieurement, comme dans un rappel asynchrone.

Pensez au code asynchrone pendant une seconde. Vous transmettez une fonction qui, par exemple, envoie des données à l'utilisateur. Lorsque le code est terminé, vous appelez cette fonction avec le résultat de la réponse, qu'elle 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énement non bloquante pour exécuter cette bibliothèque et obtenir des données, puis les renvoyer à l'utilisateur (par exemple, dans un contexte websocket). Imaginez que vous ayez 5 personnes connectées dans cette boucle d’événements et l’une des demandes visant à obtenir le blocage des données torrent de quelqu'un. Cela bloquera toute la boucle. Vous devez donc penser de manière asynchrone et utiliser des rappels - la boucle continue de s'exécuter et le processus de "restitution des données à l'utilisateur" ne s'exécute que lorsque l'exécution de la fonction est terminée. Vous ne devez donc pas l'attendre. Feu et oublie.


1
+1 Pour utiliser la terminologie de rappel. Je rencontre ce terme beaucoup plus souvent que "fonctions d'ordre supérieur".
Rodney Schuler

J'aime cette réponse, car l'OP semblait décrire les callbacks, en particulier, et pas seulement les fonctions d'ordre supérieur en général: "Par exemple, notre classe qui appelle nos serveurs prend en charge une fonction qui sera ensuite appelée et transmise à un objet. lorsque le processus est terminé et que les erreurs ont été traitées, etc. "
Ajedi32

11

Ce n'est pas une mauvaise chose. En fait, c'est une très bonne chose.

Transférer des fonctions en fonctions est tellement important pour la programmation que nous avons inventé les fonctions lambda en abrégé. Par exemple, on peut utiliser des algorithmes lambdas avec C ++ pour écrire un code très compact, mais expressif, qui permet à un algorithme générique d’utiliser des variables locales et d’autres états pour effectuer des opérations telles que la recherche et le tri.

Les bibliothèques orientées objet peuvent également avoir des 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 la pierre angulaire de la programmation événementielle , où le code de niveau cadre (peut-être même dans un autre thread) doit faire appel à 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 exploite le sucre syntaxique operator()()pour faire la même chose.

Enfin, il existe des pointeurs de fonction de style C qui ne devraient être utilisés qu'en C. Je n'entrerai pas dans les détails, je les mentionnerai simplement pour être complet. Les autres abstractions mentionnées ci-dessus sont de loin supérieures et devraient être utilisées dans les langues qui les possèdent.

D'autres ont mentionné la programmation fonctionnelle et le fait que les fonctions de passage sont très naturelles dans ces langues. Les lambda et les rappels sont ce que les langages procéduraux et POO imitent, et ils sont très puissants et utiles.


De plus, en C #, les délégués et les lambdas sont largement utilisés.
Mal Dog Pie

6

Comme déjà dit, ce n'est pas une mauvaise pratique. C'est simplement un moyen de découpler et de séparer la responsabilité. 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 consiste à déléguer une tâche spécifique - dont elle ne sait rien - à un autre objet implémentant une interface. La méthode générique ne connait que cette interface. Dans votre cas, cette interface serait une fonction à appeler.


1
Cet idiome englobe simplement la fonction dans une interface, en accomplissant quelque chose de très similaire au passage d'une fonction brute.

Oui, je voulais juste montrer que ce n’est pas une pratique inhabituelle.
Philipp Murry

3

En général, il n'y a rien de mal à passer des fonctions à d'autres fonctions. Si vous passez des appels asynchrones et souhaitez utiliser le résultat, vous aurez besoin d'un mécanisme de rappel.

Il existe cependant quelques inconvénients potentiels des rappels simples:

  • Effectuer une série d'appels peut nécessiter une imbrication profonde des rappels.
  • La gestion des erreurs peut nécessiter une répétition pour chaque appel dans une séquence d'appels.
  • Coordonner plusieurs appels est délicat, comme faire plusieurs appels en même temps et ensuite faire quelque chose une fois qu'ils sont tous terminés.
  • Il n'y a pas de moyen général d'annuler une série d'appels.

Avec des services Web simples, votre façon de procéder fonctionne bien, mais il devient gênant si vous avez besoin d'une séquence d'appels plus complexe. Il y a quelques alternatives cependant. Avec JavaScript, par exemple, l'utilisation de promesses a évolué ( ce qui est génial avec les promesses javascript ).

Ils impliquent toujours de transmettre des fonctions à d'autres fonctions, mais les appels asynchrones renvoient une valeur qui prend un rappel plutôt que de le rappeler directement eux-mêmes. Cela donne plus de flexibilité pour composer ces appels ensemble. Quelque chose comme cela peut être implémenté assez facilement dans ActionScript.


J'ajouterais également que le recours inutile à des rappels peut compliquer le suivi du processus d'exécution pour les lecteurs du code. Un appel explicite est plus facile à comprendre qu’un rappel défini dans une partie distante du code. (Points d'arrêt à la rescousse!) Néanmoins, il s'agit de la manière dont les événements sont déclenchés dans le navigateur et les autres systèmes basés sur des événements. Ensuite, nous devons comprendre la stratégie de l'émetteur de l'événement pour savoir quand et dans quel ordre les gestionnaires d'événements seront appelés.
joeytwiddle
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.