Je parle souvent aux programmeurs qui disent " Ne mettez pas plusieurs déclarations avec la même méthode. " Quand je leur demande de me dire pourquoi, tout ce que je reçois est " La norme de codage le dit. " Ou " C'est déroutant. " Quand ils me montrent des solutions avec une seule déclaration de retour, le code me parait plus laid. Par exemple:
if (condition)
return 42;
else
return 97;
" C'est moche, il faut utiliser une variable locale! "
int result;
if (condition)
result = 42;
else
result = 97;
return result;
En quoi ce problème de code à 50% facilite-t-il la compréhension du programme? Personnellement, je trouve cela plus difficile, car l’espace d’état vient d’être augmenté d’une autre variable qui aurait pu être facilement évitée.
Bien sûr, normalement j'écrirais simplement:
return (condition) ? 42 : 97;
Mais de nombreux programmeurs évitent l’opérateur conditionnel et préfèrent la forme longue.
D'où vient cette notion de "un seul retour"? Y at-il une raison historique à l’origine de cette convention?
la source
Réponses:
"Single Entry, Single Exit" était écrit lorsque la plupart de la programmation était faite en langage assembleur, en FORTRAN ou en COBOL. Il a été largement mal interprété, car les langues modernes ne supportent pas les pratiques mises en garde par Dijkstra.
"Entrée simple" signifiait "ne créez pas de points d'entrée alternatifs pour les fonctions". En langage assembleur, bien entendu, il est possible de saisir une fonction pour n’importe quelle instruction. FORTRAN prenait en charge plusieurs entrées de fonctions avec l’
ENTRY
instruction suivante:"Single Exit" signifie qu'une fonction ne doit retourner qu'à un seul endroit: l'instruction qui suit immédiatement l'appel. Cela ne signifiait pas qu'une fonction ne devrait revenir que d' un seul endroit. Lors de l' écriture de la programmation structurée , il était courant qu'une fonction indique une erreur en retournant à un autre emplacement. Fortran a soutenu cela via "retour alternatif":
Ces deux techniques étaient très sujettes aux erreurs. L'utilisation d'entrées alternatives a souvent laissé une variable non initialisée. L'utilisation d'autres déclarations posait tous les problèmes posés par une instruction GOTO, avec comme complication supplémentaire le fait que la condition de branche n'était pas adjacente à la branche, mais quelque part dans le sous-programme.
la source
const
avant la naissance de nombreux utilisateurs ici, donc plus besoin de constantes de capital même en C. Mais Java conservé toutes ces mauvaises vieilles habitudes C .setjmp/longjmp
)Cette notion de SESE ( Single Entry, Single Exit ) provient de langages à gestion de ressources explicite , comme le C et l’assemblage. En C, un code comme celui-ci perdra des ressources:
Dans de telles langues, vous avez essentiellement trois options:
Répliquez le code de nettoyage.
Pouah. La redondance est toujours mauvaise.
Utilisez a
goto
pour passer au code de nettoyage.Cela nécessite que le code de nettoyage soit la dernière chose dans la fonction. (Et c’est pourquoi certains prétendent que cela
goto
a sa place. Et c’est vrai - en C.)Introduisez une variable locale et manipulez le flux de contrôle.
L'inconvénient est que le flux de commande manipulé par la syntaxe (pensez
break
,return
,if
,while
) est beaucoup plus facile à suivre que le flux de contrôle manipulé par l'état des variables (parce que ces variables ont pas d' état quand vous regardez l'algorithme).En assembleur, c'est encore plus étrange, car vous pouvez accéder à n'importe quelle adresse d'une fonction lorsque vous appelez cette fonction, ce qui signifie que vous disposez d'un nombre presque illimité de points d'entrée pour n'importe quelle fonction. (Parfois, cela est utile. De tels thunks sont une technique courante permettant aux compilateurs d'implémenter l'
this
ajustement de pointeur nécessaire pour appeler desvirtual
fonctions dans des scénarios d'héritage multiples en C ++.)Lorsque vous devez gérer les ressources manuellement, exploiter les options permettant d'entrer ou de quitter une fonction n'importe où conduit à un code plus complexe, et donc à des bogues. Par conséquent, une école de pensée est apparue qui a propagé SESE, afin d'obtenir un code plus propre et moins de bugs.
Cependant, lorsqu'une langue comporte des exceptions, (presque) n'importe quelle fonction peut être abandonnée prématurément à (presque) n'importe quel moment. Vous devez donc prévoir le retour prématuré de toute façon. (Je pense que
finally
c'est principalement utilisé pour cela en Java etusing
(lors de l'implémentationIDisposable
,finally
sinon) en C #; C ++ utilise à la place RAII .) Une fois que vous avez effectué cela, vous ne pouvez pas vous empêcher de nettoyer après vous-même en raison d'unereturn
déclaration précoce . l'argument le plus fort en faveur de SESE a disparu.Cela laisse la lisibilité. Bien sûr, une fonction 200 LoC avec une demi-douzaine d'
return
instructions saupoudrées au hasard n'est pas un bon style de programmation et ne permet pas un code lisible. Mais une telle fonction ne serait pas facile à comprendre sans ces retours prématurés.Dans les langues où les ressources ne sont pas ou ne devraient pas être gérées manuellement, l'adhésion à l'ancienne convention SESE n'a que peu, voire aucune valeur. OTOH, comme je l'ai déjà expliqué, SESE rend souvent le code plus complexe . C'est un dinosaure qui (à l'exception du C) ne correspond pas à la plupart des langues actuelles. Au lieu d’aider le caractère compréhensible du code, il l’entrave.
Pourquoi les programmeurs Java s'en tiennent-ils à cela? Je ne sais pas, mais de mon point de vue (extérieur), Java a pris un grand nombre de conventions de C (où elles ont un sens) et les a appliquées à son monde OO (où elles sont inutiles ou carrément mauvaises), où il s’en tient maintenant. eux, peu importe les coûts. (Comme la convention pour définir toutes vos variables au début de la portée.)
Les programmeurs s'en tiennent à toutes sortes de notations étranges pour des raisons irrationnelles. (Les énoncés structurels profondément imbriqués - les "pointes de flèches" - étaient, dans des langages comme Pascal, considérés jadis comme un code magnifique.) L'application d'un raisonnement purement logique à cet objectif ne semble pas convaincre la majorité d'entre eux de s'écarter de leurs méthodes habituelles. La meilleure façon de changer de telles habitudes est probablement de leur apprendre très tôt à faire ce qui est le mieux, et non ce qui est conventionnel. En tant que professeur de programmation, vous l’avez entre les mains.
:)
la source
finally
clauses dans lesquelles il est exécuté, qu’il soitreturn
ou non à l’origine.finally
plupart du temps.malloc()
etfree()
dans un commentaire comme exemple), je parlais des ressources en général. Je n'impliquais pas non plus que GC résolve ces problèmes. (J'ai mentionné le C ++, qui n'a pas de GC prêt à l'emploi.) D'après ce que j'ai compris, Javafinally
est utilisé pour résoudre ce problème.D'une part, les instructions à retour unique facilitent la journalisation, ainsi que les formes de débogage reposant sur la journalisation. Je me souviens de nombreuses fois où j'ai dû réduire la fonction en retour simple pour imprimer la valeur de retour en un point unique.
D'autre part, vous pouvez reformuler ceci dans
function()
cet appel_function()
et enregistrer le résultat.la source
_function()
, avecreturn
des emplacements appropriés, et une enveloppe nomméefunction()
qui gère la journalisation superflue, plutôt qu’une seulefunction()
logique avec une logique contournée pour que tous les retours s’intègrent dans une seule sortie. -point juste pour que je puisse insérer une déclaration supplémentaire avant ce point."Single Entry, Single Exit" tire son origine de la révolution de la programmation structurée du début des années 1970, lancée par la lettre d'Edsger W. Dijkstra au rédacteur en chef " Déclaration de GOTO considérée comme nuisible ". Les concepts de la programmation structurée ont été exposés en détail dans le livre classique "Structured Programming" d'Ole Johan-Dahl, Edsger W. Dijkstra et Charles Anthony Richard Hoare.
"Déclaration GOTO considérée comme nuisible" est une lecture obligatoire, même aujourd'hui. La "programmation structurée" est datée, mais toujours très, très enrichissante, et devrait figurer en haut de la liste "À lire" de tout développeur, bien au-dessus de quoi que ce soit, par exemple Steve McConnell. (La section de Dahl détaille les bases des classes dans Simula 67, qui constituent la base technique des classes en C ++ et de toute la programmation orientée objet.)
la source
goto
pourrait littéralement aller partout , comme à droite dans un point au hasard dans une autre fonction, sans passer par toute notion de procédures, des fonctions, une pile d'appels, etc. Aucun langage permet sain d' esprit que ces jours -ci avec une ligne droitegoto
. C'ssetjmp
/longjmp
est le seul cas semi-exceptionnel que je connaisse, et même cela nécessite une coopération des deux côtés. (C'est un peu ironique que j'utilise le mot "exceptionnel", sachant que les exceptions font presque la même chose ...) En gros, l'article décourage une pratique qui est morte depuis longtemps.Il est toujours facile de relier Fowler.
L'un des principaux exemples qui vont à l'encontre de SESE sont les clauses de sauvegarde:
Remplacer le conditionnel imbriqué par des clauses de garde
la source
_isSeparated
et_isRetired
peut être vrai (et pourquoi cela ne serait-il pas possible?), Vous retournez le mauvais montant.J'ai écrit un article sur ce sujet il y a quelque temps.
L'essentiel, c'est que cette règle provient de l'âge des langues qui ne disposent pas de récupération de place ou de gestion des exceptions. Aucune étude formelle n'a montré que cette règle conduit à un meilleur code dans les langues modernes. N'hésitez pas à l'ignorer chaque fois que cela conduira à un code plus court ou plus lisible. Les gars de Java qui insistent là-dessus sont aveuglément et aveugles, suivant une règle obsolète et inutile.
Cette question a également été posée sur Stackoverflow
la source
Un retour facilite le refactoring. Essayez d’exécuter la «méthode extract» dans le corps interne d’une boucle for contenant un retour, une rupture ou une suite. Cela échouera si vous avez interrompu votre flux de contrôle.
Le fait est que je suppose que personne ne prétend prétendre écrire du code parfait. Donc, le code est régulièrement en cours de refactoring pour être "amélioré" et étendu. Mon objectif serait donc de garder mon code aussi convivial que possible pour la refactorisation.
Souvent, le problème est que je dois reformuler complètement les fonctions si elles contiennent des disjoncteurs de flux de contrôle et si je souhaite n’ajouter que peu de fonctionnalités. Ceci est très sujet aux erreurs car vous modifiez tout le flux de contrôle au lieu d’introduire de nouveaux chemins pour des imbrications isolées. Si vous n'avez qu'un seul retour à la fin ou si vous utilisez des gardes pour sortir d'une boucle, vous avez bien sûr plus d'imbrication et plus de code. Mais vous bénéficiez des capacités de refactoring prises en charge par le compilateur et l'IDE.
la source
Considérez le fait que plusieurs instructions de retour équivalent à avoir GOTO dans une seule instruction de retour. C'est le même cas avec les déclarations de rupture. Ainsi, certains, comme moi, les considèrent comme des GOTO à toutes fins pratiques.
Cependant, je ne considère pas ces types de GOT comme néfastes et n'hésiterai pas à utiliser un véritable GOTO dans mon code si j'en trouve une bonne raison.
Ma règle générale est que les GOTO sont uniquement pour le contrôle de flux. Ils ne devraient jamais être utilisés pour une boucle, et vous ne devriez jamais aller «vers le haut» ou «vers l'arrière». (comment fonctionnent les pauses / retours)
Comme d’autres l’ont déjà mentionné, il convient de lire la déclaration de GOTO considérée comme préjudiciable
, mais gardez à l’esprit que cela a été écrit en 1970, lorsque les GOTO étaient largement surutilisés. Tous les GOTO ne sont pas nocifs et je ne découragerais pas leur utilisation tant que vous ne les utilisez pas à la place de constructions normales, mais plutôt dans le cas étrange où l'utilisation de constructions normales serait très gênante.
Je trouve que leur utilisation dans les cas d'erreur où vous avez besoin d'échapper à une zone en raison d'une défaillance qui ne devrait jamais se produire dans des cas normaux est parfois utile. Mais vous devriez également envisager de placer ce code dans une fonction distincte de sorte que vous puissiez simplement revenir plus tôt au lieu d'utiliser un GOTO ... mais parfois c'est aussi peu pratique.
la source
Complexité Cyclomatique
J'ai vu SonarCube utiliser plusieurs instructions de retour pour déterminer la complexité cyclomatique. Donc, plus les déclarations de retour, plus la complexité cyclomatique est élevée
Changement de type de retour
Les retours multiples signifient que nous devons changer à plusieurs endroits de la fonction lorsque nous décidons de changer notre type de retour.
Sortie multiple
Il est plus difficile de déboguer car la logique doit être soigneusement étudiée en conjonction avec les instructions conditionnelles pour comprendre la cause de la valeur renvoyée.
Solution refactorisée
La solution à plusieurs instructions de retour consiste à les remplacer par un polymorphisme ayant un seul retour après avoir résolu l'objet d'implémentation requis.
la source