En général, j'utilise des méthodes privées pour encapsuler des fonctionnalités réutilisées à plusieurs endroits de la classe. Mais parfois, j'ai une grande méthode publique qui pourrait être divisée en étapes plus petites, chacune selon sa propre méthode privée. Cela raccourcirait la méthode publique, mais je crains que forcer quiconque lisant la méthode à utiliser différentes méthodes privées ne nuise à la lisibilité.
Y a-t-il un consensus à ce sujet? Est-il préférable d'avoir de longues méthodes publiques ou de les diviser en petites pièces même si chaque pièce n'est pas réutilisable?
coding-style
readability
Jordak
la source
la source
Réponses:
Non, ce n'est pas un mauvais style. En fait c'est un très bon style.
Il n’est pas nécessaire que les fonctions privées existent simplement pour des raisons de réutilisation C’est certainement une bonne raison de les créer, mais il en existe une autre: la décomposition.
Considérons une fonction qui en fait trop. Il compte cent lignes et il est impossible de raisonner.
Si vous divisez cette fonction en morceaux plus petits, elle "effectue" toujours autant de travail qu'auparavant, mais en morceaux plus petits. Il appelle d'autres fonctions qui devraient avoir des noms descriptifs. La fonction principale se lit presque comme un livre: faites A, puis B, puis C, etc. Les fonctions qu’elle appelle ne peuvent être appelées qu’à un seul endroit, mais elles sont maintenant plus petites. Toute fonction particulière est nécessairement séparée des autres fonctions par un bac à sable: elles ont des portées différentes.
Lorsque vous décomposez un problème important en problèmes plus petits, même si ces problèmes (fonctions) ne sont utilisés / résolus qu'une seule fois, vous bénéficiez de plusieurs avantages:
Lisibilité. Personne ne peut lire une fonction monolithique et comprendre complètement ce qu’elle fait. Vous pouvez continuer à vous mentir ou le diviser en petits morceaux qui ont du sens.
Localité de référence. Il devient maintenant impossible de déclarer et d'utiliser une variable, puis de la conserver et de l'utiliser à nouveau 100 lignes plus tard. Ces fonctions ont différentes portées.
Essai. Bien qu'il soit seulement nécessaire de tester à l'unité les membres publics d'une classe, il peut également être souhaitable de tester certains membres privés. S'il existe une section critique d'une fonction longue susceptible de bénéficier d'un test, il est impossible de la tester indépendamment sans l'extraire dans une fonction distincte.
Modularité. Maintenant que vous avez des fonctions privées, vous pouvez en trouver une ou plusieurs qui pourraient être extraites dans une classe distincte, que celle-ci soit utilisée uniquement ici ou qu'elle soit réutilisable. Au point précédent, cette classe séparée sera probablement plus facile à tester, car elle nécessitera une interface publique.
L'idée de séparer les gros codes en éléments plus petits, plus faciles à comprendre et à tester, est un point clé du livre de Clean Code, de Oncle Bob . Au moment de la rédaction de cette réponse, le livre a neuf ans, mais il est tout aussi pertinent aujourd'hui qu'il l'était à l'époque.
la source
C'est probablement une bonne idée!
Je ne suis pas d'accord avec la division de longues séquences d'actions linéaires en fonctions distinctes uniquement pour réduire la longueur moyenne de la fonction dans votre base de code:
Vous avez maintenant ajouté des lignes de source et réduit considérablement la lisibilité totale. Surtout si vous passez maintenant beaucoup de paramètres entre chaque fonction pour garder trace de l'état. Beurk!
Toutefois , si vous avez réussi à extraire une ou plusieurs lignes dans une fonction pure qui remplit un seul objectif clair ( même si vous ne l'avez appelée qu'une fois ), vous avez amélioré la lisibilité:
Cela ne sera probablement pas facile dans des situations réelles, mais des fonctionnalités pures peuvent souvent être découvertes si vous y réfléchissez suffisamment longtemps.
Vous saurez que vous êtes sur la bonne voie lorsque vous avez des fonctions avec de beaux noms de verbe et que votre fonction parent les appelle et que tout se lit pratiquement comme un paragraphe de prose.
Puis, lorsque vous revenez des semaines plus tard pour ajouter plus de fonctionnalités et que vous constatez que vous pouvez réellement réutiliser une de ces fonctions, alors, oh, joie extatique! Quel délice radieux!
la source
foo = compose(reglorbulate, deglorbFramb, getFrambulation);
La réponse est que cela dépend fortement de la situation. @Snowman aborde les aspects positifs de la rupture d'une fonction publique importante, mais il est important de se rappeler qu'il peut aussi y avoir des effets négatifs, car vous vous inquiétez à juste titre.
De nombreuses fonctions privées avec des effets secondaires peuvent rendre le code difficile à lire et assez fragile. Cela est particulièrement vrai lorsque ces fonctions privées dépendent des effets secondaires les uns des autres. Évitez d’avoir des fonctions étroitement couplées.
Les abstractions sont des fuites . Bien qu’il soit agréable de prétendre comment une opération est effectuée ou comment les données sont stockées n’a pas d’importance, il existe des cas où cela est le cas et il est important de les identifier.
La sémantique et le contexte sont importants. Si vous ne saisissez pas clairement le rôle d'une fonction dans son nom, vous pouvez à nouveau réduire la lisibilité et accroître la fragilité de la fonction publique. Cela est particulièrement vrai lorsque vous commencez à créer des fonctions privées avec un grand nombre de paramètres d'entrée et de sortie. Et tandis que depuis la fonction publique, vous voyez quelles fonctions privées elle appelle, depuis la fonction privée, vous ne voyez pas comment les fonctions publiques l'appellent. Cela peut conduire à une "correction de bogue" dans une fonction privée qui casse la fonction publique.
Le code fortement décomposé est toujours plus clair pour l'auteur que pour les autres. Cela ne signifie pas que cela ne peut toujours pas être clair pour les autres, mais il est facile de dire que cela a un sens parfait que cela
bar()
doit être appelé avantfoo()
au moment de la rédaction.Réutilisation dangereuse. Votre fonction publique a probablement limité les entrées possibles à chaque fonction privée. Si ces hypothèses d'entrée ne sont pas correctement capturées (il est difficile de documenter TOUTES les hypothèses), il est possible que quelqu'un réutilise incorrectement l'une de vos fonctions privées, en introduisant des bogues dans la base de code.
Les fonctions de décomposition sont basées sur leur cohésion interne et leur couplage, et non sur leur longueur absolue.
la source
MainMethod
(assez facile à obtenir, les symboles sont généralement inclus et vous devriez avoir un fichier de déréférencement de symbole) qui est constitué de 2k lignes (les numéros de ligne ne sont jamais inclus dans rapports de bugs) et c'est un NRE qui peut arriver à 1k de ces lignes, eh bien je serai damné si j'examine cela. Mais s’ils me disent que cela s’est passé enSomeSubMethodA
8 lignes et que l’exception était un NRE, alors je trouverai probablement une solution assez simple.À mon humble avis, l'intérêt de retirer des blocs de code uniquement pour dissocier la complexité serait souvent lié à la différence de complexité entre:
Une description complète et précise en langage humain de ce que fait le code, y compris de la façon dont il traite les cas critiques , et
Le code lui-même.
Si le code est beaucoup plus compliqué que la description, le remplacement du code en ligne par un appel de fonction peut faciliter la compréhension du code environnant. Par ailleurs, certains concepts peuvent être exprimés de manière plus lisible dans un langage informatique que dans un langage humain. Je considérerais
w=x+y+z;
, par exemple, plus lisible quew=addThreeNumbersAssumingSumOfFirstTwoDoesntOverflow(x,y,z);
.Au fur et à mesure que se divise une fonction importante, il y aura de moins en moins de différence entre la complexité des sous-fonctions et leurs descriptions, et l'avantage des subdivisions ultérieures diminuera. Si les éléments sont divisés au point que les descriptions seraient plus compliquées que le code, des fractionnements supplémentaires aggraveraient le code.
la source
int average3(int n1, int n2, int n3)
. Si on divisait la fonction "moyenne", quelqu'un qui voudrait savoir si elle répondait aux besoins devait regarder à deux endroits.C'est un défi d'équilibre.
Plus c'est mieux
La méthode privée fournit efficacement un nom pour le code contenu, et parfois une signature significative (sauf si la moitié de ses paramètres sont des données de tramp complètement ad-hoc avec des interdépendances peu claires et non documentées).
Donner des noms à des constructions de code est généralement bon, à condition que les noms suggèrent un contrat significatif pour l'appelant, et que le contrat de la méthode privée corresponde exactement à ce que le nom suggère.
En se forçant à penser à des contrats significatifs pour de plus petites parties du code, le développeur initial peut détecter certains bogues et les éviter sans douleur, même avant tout test de développement. Cela ne fonctionne que si le développeur aspire à une dénomination concise (sonorité simple mais précise) et souhaite adapter les limites de la méthode privée afin qu'une dénomination concise soit même possible.
La maintenance ultérieure est également facilitée, car un nommage supplémentaire aide le code à s'auto-documenter .
Jusqu'à ce que ça devienne trop fin
Est-il possible d'exagérer de donner des noms à de petits morceaux de code et de se retrouver avec trop de méthodes privées, trop petites? Sûr. Un ou plusieurs des symptômes suivants indiquent que les méthodes deviennent trop petites pour être utiles:
XxxInternal
ouDoXxx
, surtout s'il n'y a pas de système unifié pour les introduire.LogDiskSpaceConsumptionUnlessNoUpdateNeeded
la source
Contrairement à ce que d'autres ont dit, je dirais qu'une méthode publique longue est une odeur de conception qui n'est pas corrigée par la décomposition en méthodes privées.
Si tel est le cas, je dirais que chaque étape devrait être un citoyen de première classe avec une responsabilité unique. Dans un paradigme orienté objet, je suggérerais de créer une interface et une implémentation pour chaque étape de manière à ce que chacune ait une responsabilité unique, facilement identifiable et puisse être nommée de manière à ce que la responsabilité soit claire. Cela vous permet de tester unitairement la méthode (autrefois) grande publique, ainsi que chaque étape, indépendamment les unes des autres. Ils devraient tous être documentés aussi bien.
Pourquoi ne pas décomposer en méthodes privées? Voici quelques raisons:
C'est un argument que j'ai eu avec l'un de mes collègues récemment. Il fait valoir qu'avoir le comportement complet d'un module dans le même fichier / la même méthode améliore la lisibilité. Je conviens que le code est plus facile à suivre lorsqu'il est ensemble, mais le code est moins facile à raisonner à mesure que la complexité augmente. À mesure que le système se développe, il devient difficile de raisonner sur le module dans son ensemble. Lorsque vous décomposez la logique complexe en plusieurs classes, chacune ayant une responsabilité unique, il devient alors beaucoup plus facile de raisonner chaque partie.
la source