web-dev-qa-db-fra.com

Entraînant une très grande fonction / programme en fonctions plus petites. Efficace?

Je sais que les noms de fonction peuvent être très expressifs. Et par conséquent, il peut être tentant de séparer un programme dans des fonctions particulières et les appeler à partir d'un grand fichier de fonctions "supervisé".

Cependant, cela est-ce effectif pour un programmeur? Étant donné que chaque fonction s'appuie généralement sur une entrée fournie à partir d'une fonction précédente, une valeur renvoyée ou une variable globale ou toute autre chose que votre langue utilise - elle ne fonctionne souvent que dans son contexte avec la fonction précédente. Un passage à une fonction précédente serait souvent évidemment détruire la fonctionnalité dans la deuxième fonction.

Lorsqu'un programme est divisé en fonctions qui sont jumelles dans un fichier de fonctions. L'effet que les changements ne sont pas nécessairement très clairs. Alors, c'est une bonne idée de diviser les choses de cette manière?

4
Maximilian Travis

En tant que personne qui l'a récemment fait, j'ai refactoré quelques longues feuilles de code dans un ensemble de petites fonctions, je peux attester de son efficacité.

Je suppose que vous avez entendu parler du principe UNIX: "Faites une chose et faites-le bien". Cela aide énormément.

Une fonction courte est facile à observer, facile à raisonner, et facile à tester. Ce qui est important, il est généralement facile de tester en absence de la plupart des autres fonctions, et tout en se moquant que quelques objets, le cas échéant, cela dépend.

Lorsque vous écrivez une fonction courte, vous êtes obligé de penser à ce que cela fait exactement, proposez un nom propre (DOIT () ne correspond pas), de bons noms de paramètres, etc. Il rend votre code plus descriptif, et cela fait Vous comprenez mieux le code. Il vous offre également des concepts de niveau supérieur pour décrire votre programme.

Splitting Des transitions d'état complexes en courtes fonctions vous font penser au flux de données du programme et de démêler le plus possible. Cela conduit à moins de dépendances de données, souvent plus courtes de certaines données (importantes lorsque les données sont énormes) et moins d'erreurs telles que des courses de données, les mises à jour de l'état s'accumulent, etc.

L'affactation de tout fragment de code plus grand que d'un dépistage dans une fonction vous permet également de remarquer et de factoriser le code collé à la copie, même légèrement modifié. Trouver le modèle commun aide à comprendre.

Parfois, vous vous retrouvez avec des fonctions que vous reconnaissez instantanément, car elles font (presque) la même chose que certaines fonctionnalités de la bibliothèque. Vous réduisez la ligne comptant et réutilisez-vous le travail existant de quelqu'un d'autre (et des améliorations à venir et des bugsfixes).

Inversement, les fonctions que vous avez extraites peuvent être réutilisées dans vos autres programmes. Vous ne pouvez pas réutiliser un fragment de code monolithique, sauf en le colissant de la copie.

Beaucoup de gens ont du mal à écrire des fonctions courtes. Ils ont tendance à écrire un long script monolithique qui fait le tout, car ils écriraient tout un chapitre de prose. L'un des remèdes n'est pas simplement d'écrire du code, mais de l'exécuter immédiatement.

Si vous avez une langue dynamique comme Python ou JS, ou même des choses comme Scala, avez un REPL ouvert à tout moment. Lorsque vous arrivez avec un fragment du code, essayez-le dans la replication. Vous aurez besoin d'objets dépendants pour cela? Écrivez une fonction qui fournit chaque objet dépendant. Une fois que vous avez trouvé un fragment de code qui produit quelque chose de significatif, enveloppez-le également dans une fonction. Vous aurez également besoin de À la prochaine étape. De cette façon, vous vous retrouvez avec un ensemble de fonctions qui composent dans l'ensemble du programme dont vous aviez besoin et que vous avez déjà exécuté déjà!

Avec des langues qui ne fournissent pas facilement de REPL choses sont plus difficiles. Ici, "conception à tester" peut aider: vous ne pouvez pas jouer librement avec vos fragments de code dans une replaction, mais vous pouvez l'exécuter À moindre coût en tant que tests. Comme vous allez, vous vous retrouvez avec l'ensemble du programme et avec un ensemble de tests vérifiant et expliquez-le. Avec un monolithe, c'est impossible.

Si votre programme est un script jetable, pas assez important pour être examiné par des pairs avant de courir en production, une paroi de code peut être bien.

Sinon, l'affacturage de votre code en pièces plus petites paye chaque fois que vous devez prolonger, modifier, revoir les modifications de quelqu'un d'autre, ou simplement le consulter.

7
9000

Étant donné que chaque fonction s'appuie généralement sur une entrée fournie à partir d'une fonction précédente, une valeur renvoyée ou une variable globale ou toute autre chose que votre langue utilise - elle ne fonctionne souvent que dans son contexte avec la fonction précédente.

Alors que je favoris toujours des fonctions compactes et autonomes sur d'énormes arbres si/autres arbres, il est vrai que les programmes du monde réel sont rarement aussi gentils et soignés (démêlés) que nous le souhaiterions. L'état d'application (global ou non) est généralement inévitable.

Je crois qu'il est également vrai qu'il y a des retours décroissants pour fractionnement des choses dans des fonctions plus petites et plus petites. Pour un extrême logique, jetez un coup d'œil au livre Clean Code de Robert C. Martin. Tout le monde ne sera pas d'accord avec moi, mais je trouve beaucoup de ses exemples d'être légèrement moins compréhensible Après Son refactoring. (Peut-être que je n'écris pas assez Java pour les apprécier?)

Cela dit, je crois personnellement que La fonction est l'abstraction la plus puissante à la disposition de nous . Bien sûr, fonctions peut être utilisé de manière incorrecte. Il n'y a absolument rien de vous arrêter d'écrire un code terrible et enchevêtré avec un tas de minuscules fonctions, même sans ces armes diaboliques, les variables mondiales. Mais dans un ensemble, les fonctions sont une force de bonté et de lumière.

Lorsque pratiquement possible, il a interdit la sagesse standard de la foule dans la création de fonctions "propres":

  • Ne modifiez pas tout état externe dans vos fonctions.
  • Une fonction doit être déterministe: la même entrée doit TOUJOURS Produire la même sortie.

Juste ces deux règles éviteront 90% des maux de tête de conception les plus courants. Il vous permet également d'écrire facilement des tests pour vos fonctions!

Au-delà, essayez simplement votre plus difficile de ne faire que des fonctions "propres" lorsque vous le pouvez. Selon l'application, cela peut en réalité être un exercice très difficile au début. Mais on va mieux à ça. Comme pour la plupart des choses, c'est un véritable métier et il faut de l'expérience (expérience de programmation en général et à l'expérience de ce projet, en particulier) pour l'obtenir juste.

Le code démêlé ne se produit pas seulement. C'est un travail acharné et un art ... et la peine totalement.

6
DaveGauer

Étant donné que chaque fonction s'appuie généralement sur une entrée fournie à partir d'une fonction précédente, une valeur renvoyée ou une variable globale ou toute autre chose que votre langue utilise - elle ne fonctionne souvent que dans son contexte avec la fonction précédente.

^ Cette déclaration est vraie, même si vous laissez votre code comme une grande fonction! Au lieu de petites fonctions en fonction de l'autre, vous avez lignes de code dans une grande fonction En fonction de l'autre. Cela est bien pire car il n'y a aucun moyen d'indiquer quelles variables s'appliquent à quel code dans quelle étape de l'exécution; Tout dans la portée locale est disponible pour tout dans la fonction.

Si vous deviez le casser dans une série de fonctions plus petites, vous pouvez définir des contraintes sur les fonctions qui fonctionnent avec quelles dépendances, en les déclarant en tant que paramètres et en tirant parti de la portée locale de chaque fonction. Ainsi, vous pouvez casser un problème plus important dans une série de problèmes plus petits que (1) ont des pauses claires entre eux, que chacun plus facile à comprendre, et (2) avoir une relation claire, définie dans le code, qui vous indique comment le problème est décomposé en subproblèmes.

C'est loin, de loin préférable aux longues fonctions qui couvrent de nombreuses pages qui nécessitent le programmeur de faire défiler de haut en bas pour comprendre comment cela fonctionne. Après des années de programmation, je peux vous dire de l'expérience que celles-ci sont les fonctions qui ont tendance à être pleines de bugs les plus méchants.

4
John Wu

cela ne fonctionne souvent que et a du sens dans son contexte avec la fonction précédente. Un passage à une fonction précédente serait souvent évidemment détruire la fonctionnalité dans la deuxième fonction.

Si tel est le cas, vos fonctions sont mal conçues. Ils n'ont pas de responsabilités ou de contrats clairs et ne représentent pas de bonnes abstractions.

Les fonctions bien conçues font une chose qui est conceptuellement facile à comprendre à partir du nom de la fonction sans connaître les détails de la mise en œuvre et est principalement isolé du reste de l'application.

Vous constaterez que si vous avez de telles fonctions qui sont de vraies abstractions de la fonctionnalité, elles seront beaucoup plus souvent réutilisables dans différents contextes et moins susceptibles de disposer d'effets secondaires indésirables lorsqu'ils sont modifiés.

Avec une fonction mal conçue, tout changement que vous faites, vous vous inquiétez de casser les choses dans 5 autres endroits que cela s'appelle. Avec une fonction bien conçue, tout changement que vous faites sera quelque chose (tel qu'un bugFix) que vous voulez Pour appliquer aux 5 autres endroits, on l'appelle.

2
Michael Borgwardt

Cependant, cela est-ce effectif pour un programmeur?

Les langages de programmation fonctionnelle font tout avec des fonctions. Même une langue avec peu de soutien à la programmation fonctionnelle devrait être capable de se rapprocher de jolie proche. Donc oui.

Un passage à une fonction précédente serait souvent évidemment détruire la fonctionnalité dans la deuxième fonction.

Alors ne fais pas ça. Laissez l'ancienne fonction et en faites un nouveau, éventuellement à l'aide de l'ancienne fonction pour faire la majeure partie du travail et simplement peaufiner la sortie.

La philosophie générale des langages de programmation fonctionnelle est comme créer un vocabulaire des mots de jargon, puis écrit quelque chose avec eux. Et comment obtenez-vous les mots de jargon? Eh bien, vous écrivez des définitions en termes d'autres mots que vous avez déjà. Au lieu de penser en tant que pièce géante de code existant qui doit être rompue dans des endroits aléatoires et poussé dans des fonctions, pensez-y comme créant un vocabulaire de fonctions qui facilitent la rédaction de la fonction de niveau supérieur.

1
Michael Shaw