Créer des fonctions imbriquées pour des raisons purement esthétiques?

16

Je me suis toujours demandé ce que les autres programmeurs pensaient de l'idée de créer des fonctions esthétiques pures.

Dire que j'ai une fonction qui traite un bloc de données: Function ProcessBigData. Disons que je besoin de plusieurs étapes du processus, valable uniquement pour les données: Step1, Step2, Step3.

L'approche normale que je vois le plus dans le code source est d'écrire des commentaires comme ceci:

Function ProcessBigData:
    # Does Step1
    Step1..
    Step1..

    #Does Step2
    Step2..
    Step2..

Ce que je fais habituellement, mais je me suis toujours senti mal en raison de l'absence d'un tel style de codage par les autres pairs est:

Function ProcessBigData:
    Function Step1:
        Step1..
        Step1..

    Function Step2:
        Step2..
        Step2..

    Step1() -> Step2()

Je m'inquiète principalement s'il y a des inconvénients pour un tel style en Javascript et Python

Y a-t-il des alternatives que je ne vois pas?

Slytael
la source
3
Je ne peux rien dire sur Python, mais pour Javascript, il y a un coût de performance pour les fonctions imbriquées: la plupart des moteurs JavaScript utilisent une structure de type liste liée pour représenter l'étendue des variables. L'ajout d'une couche supplémentaire de fonctions oblige donc le moteur à rechercher éventuellement une structure de données plus longue / plus grande lors de la résolution de variables. D'un autre côté, la racine de tout mal est, bien sûr, l'optimisation prématurée. :)
Marco

Réponses:

4

Ce n'est pas aussi étrange qu'on pourrait le penser. Par exemple, dans Standard ML, il est habituel de limiter la portée des fonctions d'assistance. Certes, SML a une syntaxe pour le faciliter:

local
    fun recursion_helper (iteration_variable, accumulator) =
        ... (* implementation goes here *)
in
    fun recursive_function (arg) = recursion_helper(arg, 0);
end

Je considérerais ce bon style, étant donné que 1) les petites fonctions facilitent le raisonnement sur le programme, et 2) il signale au lecteur que ces fonctions ne sont pas utilisées en dehors de cette portée.

Je suppose qu'il est possible qu'il y ait des frais généraux dans la création des fonctions internes chaque fois que la fonction externe est appelée (je ne sais pas si JS ou Python optimisent cela), mais vous savez ce qu'ils disent à propos de l'optimisation prématurée.

Doval
la source
11

C'est généralement une bonne chose de le faire chaque fois que possible, mais j'aime à penser à ce genre de travail non pas comme des «étapes», mais comme des sous-tâches .

Une sous-tâche est une unité de travail spécifique qui peut être effectuée: elle a une responsabilité spécifique et des entrées et des sorties définies (pensez au "S" dans SOLID ). Une sous-tâche n'a pas besoin d'être réutilisable: certaines personnes ont tendance à penser "Je n'aurai jamais à appeler cela depuis autre chose, alors pourquoi l'écrire en fonction?" mais c'est une erreur.

J'essaierai également de décrire les avantages et la façon dont cela s'applique aux fonctions imbriquées (fermetures) par rapport à une autre fonction de la classe. De manière générale, je recommande de ne pas utiliser de fermetures sauf si vous en avez spécifiquement besoin (il existe de nombreuses utilisations, mais la séparation du code en morceaux logiques n'en fait pas partie).

Lisibilité.

Plus de 200 lignes de code procédural (corps d'une fonction) sont difficiles à lire. Les fonctions de 2 à 20 lignes sont faciles à lire. Le code est pour les humains.

Imbriqués ou non, vous bénéficiez principalement de la lisibilité, à moins que vous n'utilisiez beaucoup de variables de la portée parent, auquel cas cela peut être tout aussi difficile à lire.

Limiter la portée variable

Le fait d'avoir une autre fonction vous oblige à limiter la portée des variables et à transmettre spécifiquement ce dont vous avez besoin.

Souvent, cela améliore également la structure du code, car si vous avez besoin d'une sorte de variable d'état d'une "étape" antérieure, vous pourriez en fait découvrir qu'il existe en fait une autre sous-tâche qui doit être écrite et exécutée en premier pour obtenir cette valeur. Ou en d'autres termes, il est plus difficile d'écrire des morceaux de code fortement couplés.

Le fait d'avoir des fonctions imbriquées vous permet d'accéder aux variables dans la portée parent depuis l'intérieur de la fonction imbriquée (fermeture). Cela peut être très utile, mais cela peut également conduire à des bogues subtils et difficiles à trouver car l'exécution de la fonction peut ne pas se produire de la manière dont elle est écrite. C'est encore plus le cas si vous modifiez des variables dans la portée parent (une très mauvaise idée, en général).

Tests unitaires

Chaque sous-tâche, implémentée une fonction (ou même une classe) est un morceau de code testable autonome. Les avantages des tests unitaires et du TDD sont bien documentés ailleurs.

L'utilisation de fonctions / fermetures imbriquées ne permet pas de tester les unités. Pour moi, c'est une rupture de marché et la raison pour laquelle vous devriez simplement une autre fonction, à moins qu'il n'y ait un besoin spécifique de fermeture.

Travailler en équipe / Conception descendante

Les sous-tâches peuvent être écrites par différentes personnes, indépendamment, si nécessaire.

Même par vous-même, il peut être utile lors de l'écriture de code d'appeler simplement une sous-tâche qui n'existe pas encore, tout en construisant la fonctionnalité principale, et de ne vous soucier de l'implémentation de la sous-tâche qu'après avoir su qu'elle obtiendra les résultats dont vous avez besoin dans un manière significative. Ceci est également appelé conception / programmation descendante.

Réutilisation du code

D'accord, donc malgré ce que j'ai dit plus tôt, il arrive parfois qu'il y ait une raison plus tard de réutiliser une sous-tâche pour autre chose. Je ne préconise pas du tout "l'astronaute de l'architecture", mais simplement qu'en écrivant du code faiblement couplé, vous pourriez finir par bénéficier plus tard de la réutilisation.

Souvent, cette réutilisation signifie une refactorisation, ce qui est parfaitement attendu, mais la refactorisation des paramètres d'entrée vers une petite fonction autonome est BEAUCOUP plus facile que de l'extraire d'une fonction de plus de 200 lignes plusieurs mois après son écriture, ce qui est vraiment mon point ici.

Si vous utilisez une fonction imbriquée, la réutiliser est généralement une question de refactoring dans une fonction distincte de toute façon, ce qui explique pourquoi je dirais que l'imbriqué n'est pas la voie à suivre.

gregmac
la source
2
Ce sont des points vraiment valables pour l'utilisation des fonctions en général, mais je n'ai pas tiré de votre réponse si vous pensez que les fonctions NESTED sont une bonne idée. Ou obtenez-vous les fonctions en amont?
Slytael
Désolé bon point, j'ai été pris dans les autres avantages que j'ai oublié de traiter de cette partie. :) Modifié.
gregmac