Dois-je placer des fonctions qui ne sont utilisées que dans une autre fonction, au sein de cette fonction?
31
Plus précisément, j'écris en JavaScript.
Disons que ma fonction principale est la fonction A. Si la fonction A effectue plusieurs appels à la fonction B, mais que la fonction B n'est utilisée nulle part ailleurs, dois-je simplement placer la fonction B dans la fonction A?
Est-ce une bonne pratique? Ou devrais-je toujours mettre la fonction B dans la même portée que la fonction A?
Je suis généralement en faveur des fonctions imbriquées, en particulier en JavaScript.
En JavaScript, la seule façon de limiter la visibilité d'une fonction est de l'imbriquer dans une autre fonction.
La fonction d'assistance est un détail d'implémentation privé. Le placer dans la même portée revient à rendre publiques les fonctions privées d'une classe.
S'il s'avère être d'une utilisation plus générale, il est facile de déménager, car vous pouvez être sûr que l'aide n'est actuellement utilisée que par cette seule fonction. En d'autres termes, cela rend votre code plus cohérent.
Vous pouvez souvent éliminer les paramètres, ce qui rend la signature et l'implémentation de la fonction moins verbeuses. Ce n'est pas la même chose que de rendre les variables globales. Cela ressemble plus à l'utilisation d'un membre de classe dans une méthode privée.
Vous pouvez donner aux fonctions d'assistance des noms meilleurs et plus simples, sans vous soucier des conflits.
La fonction d'aide est plus facile à trouver. Oui, il existe des outils qui peuvent vous aider. Il est toujours plus facile de déplacer un peu vos yeux vers l'écran.
Le code forme naturellement une sorte d'arbre d'abstraction: quelques fonctions générales à la racine, se ramifiant en plusieurs détails d'implémentation. Si vous utilisez un éditeur avec fonction repliée / réduite, l'imbrication crée une hiérarchie de fonctions étroitement liées au même niveau d'abstraction. Cela rend très facile d'étudier le code au niveau dont vous avez besoin et de masquer les détails.
Je pense que beaucoup d'opposition vient du fait que la plupart des programmeurs ont été élevés dans une tradition C / C ++ / Java, ou ont été enseignés par quelqu'un d'autre qui l'était. Les fonctions imbriquées ne semblent pas naturelles, car nous n'y étions pas beaucoup exposés lorsque nous apprenions à programmer. Cela ne signifie pas qu'ils ne sont pas utiles.
Vous devez le mettre à l'échelle mondiale, pour plusieurs raisons.
L'imbrication d'une fonction d'assistance dans l'appelant augmente la longueur de l'appelant. La longueur de la fonction est presque toujours un indicateur négatif; les fonctions courtes sont plus faciles à comprendre, à mémoriser, à déboguer et à maintenir.
Si la fonction d'assistance a un nom sensible, il suffit de lire ce nom sans avoir besoin de voir la définition à proximité. Si vous avez besoin de voir la définition d'aide afin de comprendre la fonction de l' appelant, alors que l' appelant est en train de faire trop, ou travaille sur trop de niveaux d'abstraction en même temps.
La disponibilité de l'assistant dans le monde permet à d'autres fonctions de l'appeler s'il s'avère qu'il est généralement utile après tout. Si l'assistant n'est pas disponible, vous êtes tenté de le copier-coller, ou de l'oublier et de le réimplémenter, mal, ou de rendre une autre fonction plus longue qu'elle ne doit l'être.
L'imbrication de la fonction d'assistance augmente la tentation d'utiliser des variables de la portée de l'appelant sans déclaration, de sorte qu'il devient difficile de savoir quelles sont les entrées et les sorties de l'aide. Si une fonction n'indique pas clairement sur quelles données elle opère et quels effets elle a, c'est généralement un signe de responsabilités peu claires. Déclarer l'assistant d'une fonction autonome vous oblige à savoir exactement ce qu'il fait réellement.
modifier
Cela s'est avéré être une question plus controversée que je ne le pensais. Clarifier:
En JavaScript, les fonctions étendues de fichiers volumineux remplissent souvent le rôle des classes car le langage ne fournit aucun autre mécanisme limitant la portée. Il est certain que les fonctions d'assistance devraient entrer dans ces quasi-classes, pas en dehors.
Et le point sur la réutilisation plus facile présuppose que si un sous - programme devient plus largement utilisé, vous êtes prêt à le déplacer complètement et à le mettre à la place appropriée, par exemple une bibliothèque d'utilitaires de chaîne ou dans votre registre de configuration global. Si vous ne voulez pas ordonner votre code comme ça, alors vous pourriez aussi bien imbriquer le sous-programme, tout comme vous le feriez avec un objet de méthode normal dans un langage plus "blocky".
Merci, tous de très bons points. En passant, comment dois-je généralement commander mes fonctions? Par exemple, pour le moment, avec de petits programmes, je les classe simplement par ordre alphabétique. Mais dois-je regrouper les fonctions d'assistance et d'appel? Je suppose que je devrais au moins décomposer mes fonctions en fonction des parties de l'application auxquelles elles s'appliquent, par exemple?
Gary
Je n'ai pas pris la peine de commander des définitions de méthode depuis longtemps. Si vous programmez avec un certain degré de professionnalisme, vous devriez avoir un environnement qui vous permet de passer à n'importe quelle définition avec quelques touches de toute façon. Par conséquent, les méthodes courtes sont utiles, mais les fichiers de classe courts ou même bien ordonnés ajoutent peu de valeur. (Bien sûr, JavaScript exigeait que toutes les définitions soient placées au-dessus de leurs utilisations ou le résultat serait techniquement un comportement indéfini - je ne me souviens pas si c'est toujours le cas ou non.)
Kilian Foth
2
C'est une excellente réponse. Le problème de l' encapsulation cassée est quelque chose que beaucoup ne considèrent pas lors de l'utilisation de fonctions internes.
sbichenko
2
Les globaux sont une odeur de code. Ils provoquent un couplage inutile qui empêche le refactoring, ils provoquent des conflits de noms, ils exposent les détails d'implémentation, etc. Ceci est particulièrement mauvais en Javascript, car toutes les variables sont mutables, le code est généralement chargé directement à partir de tiers, il n'y en a pas (encore) un système de modules largement disponible, ou même une implémentation largement disponible de "let". Dans un environnement aussi hostile, la seule stratégie défensive que nous avons est l'encapsulation à l'intérieur de fonctions à un coup.
Warbo
1
"Remplir le rôle des classes" essaie d'écrire JS comme s'il s'agissait d'autre chose. Quel est "le rôle des classes"? Vérification de type? Modularité? Compilation mise en scène? Héritage? La question implique la portée, donc je suppose que vous voulez dire les règles de portée grossières des classes. C'est juste passer la balle: comment les classes devraient-elles être délimitées? PHP les rend globaux, ce qui ne fournit aucune encapsulation. Python étend les classes lexicalement, mais si vous êtes dans un bloc à portée lexicale, pourquoi envelopper tout dans un autre bloc à portée de classe? JS n'a pas une telle redondance: utilisez simplement autant de portée lexicale que nécessaire.
Warbo
8
Je vais proposer une troisième voie, pour placer les deux fonctions dans une fermeture. Cela ressemblerait à:
var functionA =(function(){function functionB(){// do stuff...}function functionA(){// do stuff...
functionB();// do stuff...}return functionA;})();
Nous créons la fermeture en enveloppant la déclaration des deux fonctions dans un IIFE . La valeur de retour de l'IIFE est la fonction publique, stockée dans une variable du nom de la fonction. La fonction publique peut être invoquée exactement de la même manière que si elle était déclarée comme fonction globale, c'est-à-dire functionA(). Notez que la valeur de retour est la fonction , pas un appel à la fonction, donc pas de parens à la fin.
En enveloppant les deux fonctions de cette manière, il functionBest désormais complètement privé et n'est pas accessible en dehors de la fermeture, mais n'est visible que par functionA. Il n'encombre pas l'espace de noms global et n'encombre pas la définition de functionA.
La définition de la fonction B dans la fonction A donne accès à la fonction B aux variables internes de A.
Ainsi, une fonction B définie dans une fonction A donne l'impression (ou du moins la possibilité) que B utilise ou modifie les variables locales de A. Tout en définissant B en dehors de A, il est clair que B ne le fait pas.
Par conséquent, pour la clarté du code, je définirais B dans A uniquement si B a besoin d'accéder à des variables locales de A. Si B a un objectif clair qui est indépendant de A, je le définirais certainement en dehors de A.
Puisque la fonction B n'est utilisée nulle part ailleurs, vous pourriez tout aussi bien en faire une fonction interne à la fonction A car c'est la seule raison pour laquelle elle existe.
Je vais proposer une troisième voie, pour placer les deux fonctions dans une fermeture. Cela ressemblerait à:
Nous créons la fermeture en enveloppant la déclaration des deux fonctions dans un IIFE . La valeur de retour de l'IIFE est la fonction publique, stockée dans une variable du nom de la fonction. La fonction publique peut être invoquée exactement de la même manière que si elle était déclarée comme fonction globale, c'est-à-dire
functionA()
. Notez que la valeur de retour est la fonction , pas un appel à la fonction, donc pas de parens à la fin.En enveloppant les deux fonctions de cette manière, il
functionB
est désormais complètement privé et n'est pas accessible en dehors de la fermeture, mais n'est visible que parfunctionA
. Il n'encombre pas l'espace de noms global et n'encombre pas la définition defunctionA
.la source
La définition de la fonction B dans la fonction A donne accès à la fonction B aux variables internes de A.
Ainsi, une fonction B définie dans une fonction A donne l'impression (ou du moins la possibilité) que B utilise ou modifie les variables locales de A. Tout en définissant B en dehors de A, il est clair que B ne le fait pas.
Par conséquent, pour la clarté du code, je définirais B dans A uniquement si B a besoin d'accéder à des variables locales de A. Si B a un objectif clair qui est indépendant de A, je le définirais certainement en dehors de A.
la source
Vous ne devez pas l'imbriquer, car sinon la fonction imbriquée sera recréée chaque fois que vous appelez la fonction externe.
la source
Puisque la fonction B n'est utilisée nulle part ailleurs, vous pourriez tout aussi bien en faire une fonction interne à la fonction A car c'est la seule raison pour laquelle elle existe.
la source