web-dev-qa-db-fra.com

Quand devrais-je créer une fonction distincte (ou une classe)

Je fais des programmes pendant plusieurs années.
Et maintenant je sais de mes collègues mes avantages et mes inconvénients:
Avantages: je peux résoudre un problème très complexe
[.____] Contre: je fais des solutions surchargées pour des tâches simples.

Maintenant, j'essaie de réparer mes inconvénients et de rechercher des principes génériques et des directives pour cette question:

Quand devrais-je créer une pièce de code supplémentaire (une fonction, une classe, un module, quoi que ce soit). Et quand je ne devrais pas.

J'ai vu une réponse de certains Python Guy: Chaque fois que vous le pouvez, ne créez pas, rendez-la aussi simple que possible.

C'est un bon conseil, mais avoir une grande fonction n'est pas une bonne idée aussi.

Le problème pour moi est que j'ai un principe quand créer une fonction distincte à coup sûr, mais je n'ai pas de sens opposé (lorsqu'il n'est pas créé). Le principe est:

Créez une fonction lorsque vous devrez copier-coller du code et, par conséquent, vous aurez du code dupliqué.

(pourquoi: le code dupliqué est mauvais, car vous oublierez de résoudre certains des copies lorsque vous y corrigez un bogue, vous devrez créer des moments autant de tests que vous copiez, et c'est la fois plus de lettres. Pour lire votre équipe et vous-même plus tard, etc ...) Cela peut paraître comme exemple de quel genre de réponses que je recherche.

Alors, comment décidez-vous de créer une fonction distincte ou d'étendre un existant?

Merci à tous ceux qui ont déjà répondu à cette question, mais s'il vous plaît ajouter au moins certains principes lorsque vous devriez non Fonction fractionnée (lorsque vous devriez mieux le garder dans son ensemble) .

Parce que pour le moment la plupart (tous?) Les réponses ici ne concernent que lorsque vous devrait . Et mon problème est que je fais trop de fonctions, je me suis séparé trop souvent, car les codeurs me disent, et c'est pourquoi ma question diffère de celle-ci Devrais-je extraire une fonctionnalité spécifique dans une fonction et pourquoi? .

6
Yuri Yaryshev

Semble une question assez large, mais je vais faire plaisir à partager ma "sagesse" fwiw

  1. Des portions de code pouvant être extraites sur des fonctions pures (la production calculée uniquement par des entrées) doit être extraite à des fonctions pures. Par exemple, si vous déconnectez à mi-chemin de votre fonction que vous avez besoin d'une chaîne pour être formatée d'une certaine manière, vous pouvez extraire la logique de formatage de chaîne à une bibliothèque de chaînes. Même si vous utilisez uniquement la fonction une fois, cela rendra le code plus lisible et réduira la complexité de votre fonction principale.

  2. Si le nom de votre fonction est ridicule, il doit peut-être être divisé, par exemple. OpenFileParseContentsAndCreateObjects semble que cela pourrait être mieux séparé en trois fonctions.

  3. Si la fonction ne peut pas être lue et comprise par un développeur moyen dans une minute, il pourrait probablement bénéficier d'une simplification ou d'être divisé en petits morceaux.

  4. Si la fonction nécessite ~ 20 tests d'unités ou plus afin d'exercer 100% des chemins logiques, il devrait être brisé.

  5. Si la fonction est plus grande qu'une seule page visible de l'EDI, sauf s'il y a une raison convaincante, il devrait être pris en compte pour la restructuration.

13
John Wu

Une façon de dire si vous devriez casser une fonction, c'est quand vous voyez un motif comme celui-ci:

error_t getInfoFromFile(const char* filename)
{
    // Open the file
    ... 5 lines to open a file...

    // Check the magic number
    ... 3 lines read some bytes and make sure they match the magic number...

    // Read the header
    ... 5 lines to read the header

    // Find the offset of the info
    ... 10 lines to find the offset of the information you need

    // Read the info
    ... 5 lines to read the information you need

    // Reformat the info
    ... 10 lines to change the format of the information into whatever you need ...

    // etc.
}

Chacun de ces commentaires indique probablement une fonction distincte pouvant être écrite pour la tâche à accomplir. Vous n'avez peut-être pas besoin de tous. Peut-être que vous pouvez laisser le code d'ouverture de fichier là-bas, par exemple. Mais vous aurez probablement autre code qui doit lire l'en-tête et trouver un décalage dans le fichier également, de sorte que celles-ci pourraient être des fonctions distinctes. La lecture des informations est ce que la fonction est censée faire, alors je quitterais cela. Reformatage Il devrait probablement être une fonction distincte, mais il pourrait être raisonnable de l'appeler de cette fonction, en fonction de sa portée. Fondamentalement, si plusieurs étapes sont multiples pour effectuer la tâche à accomplir, si ces étapes sont plus qu'une ligne ou deux, je les mettiens dans une fonction.

11
user1118321

Les modules, les fonctions de classes sont des outils de programmeur que nous utilisons pour la création d'applications.

Le programmeur doit utiliser des outils librement sans restrictions pour leur montant. Si, pendant le développement, vous vous retrouvez avec 100 fonctions et classes, cela signifie que vous décidez que vous en avez besoin de la nécessité - et c'est bon.
Le programmeur en soi ne sera jamais en mesure de répondre à une question est ma solution excédentaire pour cette tâche particulière.
C'est pourquoi nous avons Codereviews - où d'autres programmeurs peuvent voir votre approche et raconter leur opinion. Ensuite, mettez votre opinion et vos opinions de réviseurs vous retrouvez avec une approche qui satisfait à votre équipe.

La programmation est tout au sujet du contexte, qui ne connaissait que des connaissances en béton que nous pouvons dire quelle approche doit être choisie et la quantité d'abstractions suffira pour cette tâche.
[.____] Mais notre problème est que, au début de la tâche, nous avons moins d'informations sur le contexte du problème (à propos du domaine du problème), alors à la fin de la tâche. C'est pourquoi, généralement après la tâche, le programmeur verra/sentez-vous que cela devrait être fait de manière différente. Bien sûr, vous ne pouvez pas réécrire votre solution, car vous devez commencer la tâche suivante. Qui conduisent après 1-2 ans dans la construction de solutions de contournement affreux pour rendre les choses effectuées sans réécriture massive du code existant.

C'est pourquoi il est si important d'écrire une application qui peut être modifiée avec moins d'effort/coût.

Couplage, les dépendances droites sont peu nombreuses de nombreuses raisons qui empêchent de changer efficacement. Vous vous retrouvez avec une situation où les changements au même endroit suivront beaucoup de changements dans d'autres.

C'est pourquoi il est très important de séparer la logique dans différents "blocs" pouvant être remplacés, combinés avec un minimum d'effort.

Les principes solides peuvent être une très bonne ligne directrice pour le programmeur. Moins d'expérience de programmation Vous avez plus strictement, vous devez suivre cette ligne directrice.

Dans votre cas particulier "Principe de responsabilité unique" ("S" en solide) est votre ligne directrice - "La classe/la fonction ne devrait avoir qu'une seule raison de changer"

Une approche utile pour trouver des candidats à l'extraction de la fonction/classe de classe est d'écrire un code dans la section "Test d'abord" (développement TDD ou axé sur les tests).

Lorsque vous commencez à écrire des tests pour une fonction et de vous retrouver avec plusieurs cas de test différents, lorsque la configuration de cas de test devient compliquée - vous pouvez mettre une partie de la logique derrière des abstractions pouvant être dissimulées dans les tests.

2
Fabio

En plus des autres réponses, quelques indicateurs supplémentaires suggèrent qu'il pourrait être une bonne idée de diviser une fonction/méthode:

  • Les outils d'analyse de code statique avertissent complexité cyclomatique
  • Il viole le principe CQS .
  • Il contient une logique qui ne correspond pas à son nom Sémantique - par exemple, une méthode appelée ReadFile qui exécute également une requête sur une base de données SQL.
  • Il contient un switch avec des blocs importants ou non triviaux case.
  • Il accepte des arguments d'indicateur boolean qui modifient considérablement son comportement.
  • Un ou plusieurs de ses arguments ne sont pertinents que dans les branches imbriquées conditionnelles ou ne semblent pas liées aux autres - par exemple Save(File[] files, string sqlConnectionString, TextBoxControl directoryTextBox, IpAddress webServer).
2
Ben Cottrell

Appliquer le principe de responsabilité unique pour atteindre un fort séparation des préoccupations . Cela entraîne un code flexible et maintenu.

Robert C. Martin exprime le principe de responsabilité unique comme "une classe ne devrait avoir qu'une seule raison de changer". Ne pensez pas que cela ne s'applique qu'à OO Code. La classe doit être considérée comme un type de méta-classe . Le principe est applicable à toute approche pour la décomposition, les modules, Fonctions, méthodes, procédures pourraient remplacer le mot "classe" et le principe est vrai.

Toute pièce de code devrait avoir la responsabilité sur une seule partie de la fonctionnalité. Il existe un corollaire que la responsabilité devrait être entièrement encapsulée par la "classe" pour minimiser le couplage. Tout service de logiciel doit être étroitement aligné sur la responsabilité.

En pratique, si vous ne pouvez pas décrire la fonctionnalité en une seule phrase courte, vous devez décomposer le code. Divisez le code sur le mot 'et'.

Compte tenu de l'exigence "une personne est composée d'un nom et d'un âge", entraînerait trois morceaux de code distincts une personne, un nom et un âge.

Cette approche optimise la fiabilité et la main-d'œuvre tout en localisant et en minimisant la complexité, à travers une séparation des préoccupations .

1
Martin Spamer

Si je comprends bien, la question centrale consiste à modifier une fonction existante et à la création d'une nouvelle, lors de la création d'une nouvelle pour créer un code dupliqué.

Premièrement, la raison pour créer une nouvelle fonction est que vous avez besoin d'une fonction qui fait quelque chose de différent de ce que fait une fonction existante. Si la fonction existante sert son objectif, je ne la modifierais pas pour servir un autre but pour éviter le double du code.

Mais la création d'une nouvelle fonction ne signifie pas que vous devez dupliquer le code. Si votre nouvelle fonction nécessite un morceau de code identique à celui de la fonction existante, vous pouvez extraire cela dans sa propre fonction et l'appeler à la fois des fonctions existantes et nouvelles.

IOW, que ce soit pour modifier une fonction existante et si un code en double est généralement des questions sans rapport.

Si le code dupliqué est requis plus d'une fois dans une classe donnée que vous pouvez le refroidir dans une méthode. Si cela est nécessaire entre les classes, c'est un bon cas pour une nouvelle classe. Si un morceau de code est a) suffisamment de temps que vous ne voulez pas dupliquer et b) suffisamment utile que vous en avez besoin plus d'une fois, cela vaut probablement des tests unitaires.

1
Scott Hannen

En plus de ce que vous avez déjà écrit sur l'évité de la duplication de code:

Vous devriez le faire chaque fois que vous trouvez un bon nom pour la fonction ou la classe. Un bon nom indique au lecteur quelle fonction la fonction ou la classe sans avoir à examiner la mise en œuvre.

0
Frank Puffer