Ma langue principale est typée de manière statique (Java). En Java, vous devez renvoyer un type unique de chaque méthode. Par exemple, vous ne pouvez pas avoir une méthode qui retourne conditionnellement un String
ou retourne conditionnellement un Integer
. Mais en JavaScript, par exemple, c'est très possible.
Dans un langage typé statiquement, je comprends pourquoi c'est une mauvaise idée. Si toutes les méthodes sont renvoyées Object
(le parent commun dont toutes les classes héritent), vous et le compilateur n'avez aucune idée de ce à quoi vous faites face. Vous devrez découvrir toutes vos erreurs au moment de l'exécution.
Mais dans un langage typé dynamiquement, il peut ne pas y avoir de compilateur. Dans un langage typé dynamiquement, il n’est pas évident pour moi qu’une fonction renvoyant plusieurs types soit une mauvaise idée. Mon expérience dans les langages statiques me permet d'éviter d'écrire de telles fonctions, mais je crains de ne pas être attentive à une fonctionnalité qui pourrait rendre mon code plus propre d'une manière que je ne peux pas voir.
Edit : Je vais supprimer mon exemple (jusqu'à ce que je puisse penser à un meilleur). Je pense que cela incite les gens à répondre à un point que je n'essaie pas de dire.
la source
(coerce var 'string)
rendements astring
ou(concatenate 'string this that the-other-thing)
similaires. J'ai écrit des choses commeThingLoader.getThingById (Class<extends FindableThing> klass, long id)
aussi bien. Et, là, je pourrais seulement renvoyer quelque chose qui sous-classe ce que vous avez demandé:loader.getThingById (SubclassA.class, 14)
peut renvoyer unSubclassB
qui s'étendSubclassA
...Réponses:
Contrairement à d’autres réponses, il existe des cas où le retour de différents types est acceptable.
Exemple 1
Dans certains langages à typage statique, cela implique des surcharges. Nous pouvons donc considérer qu'il existe plusieurs méthodes, chacune renvoyant le type prédéfini et fixe. Dans les langages dynamiques, il peut s'agir de la même fonction, implémentée sous la forme:
Même fonction, différents types de valeur de retour.
Exemple 2
Imaginez que vous obteniez une réponse d'un composant OpenID / OAuth. Certains fournisseurs OpenID / OAuth peuvent contenir plus d'informations, telles que l'âge de la personne.
D'autres auraient le minimum, s'agirait-il d'une adresse e-mail ou d'un pseudonyme?
Encore une fois, même fonction, résultats différents.
Ici, l'avantage de renvoyer différents types est particulièrement important dans un contexte où vous ne vous souciez pas des types et des interfaces, mais des objets réellement contenus. Par exemple, imaginons qu'un site Web contienne un langage mature. Ensuite, le
findCurrent()
peut être utilisé comme ceci:Refactoriser cela en code où chaque fournisseur aura sa propre fonction qui retournera un type fixe bien défini dégraderait non seulement la base de code et entraînerait la duplication de code, mais n'apporterait aucun avantage. On peut finir par faire des horreurs comme:
la source
sum
exemple.sum :: Num a => [a] -> a
. Vous pouvez résumer une liste de tout ce qui est un nombre. Contrairement à javascript, si vous essayez de résumer quelque chose qui n'est pas un nombre, l'erreur sera interceptée lors de la compilation.Iterator[A]
possède une méthodedef sum[B >: A](implicit num: Numeric[B]): B
qui permet à nouveau de faire la somme de tout type de nombre et qui est vérifiée au moment de la compilation.+
chaînes (en implémentantNum
, ce qui est une idée terrible en termes de conception mais légale) ou inventer un autre opérateur / fonction surchargé par des entiers et des chaînes. respectivement. Haskell a un polymorphisme ad hoc via des classes de types. Une liste contenant à la fois des entiers et des chaînes est beaucoup plus dure (éventuellement impossible sans extensions de langage) mais assez différente.null
valeurs statiques .En général, c’est une mauvaise idée pour les mêmes raisons que l’équivalent moral dans un langage à typage statique est une mauvaise idée: vous ne savez pas quel type concret est renvoyé, vous n’avez donc aucune idée de ce que vous pouvez faire du résultat (à part le peu de choses qui peuvent être faites avec n'importe quelle valeur). Dans un système de types statiques, vous avez des annotations de types de retour vérifiées par le compilateur, mais dans un langage dynamique, les mêmes connaissances existent toujours: elles sont simplement informelles et stockées dans le cerveau et la documentation plutôt que dans le code source.
Dans de nombreux cas, cependant, il existe une rime et une raison pour lesquelles le type est renvoyé et l’effet est similaire à la surcharge ou au polymorphisme paramétrique dans les systèmes de type statique. En d'autres termes, le type de résultat est prévisible, ce n'est tout simplement pas aussi simple à exprimer.
Mais notez qu’il peut y avoir d’autres raisons pour lesquelles une fonction spécifique est mal conçue: Par exemple, une
sum
fonction renvoyant la valeur false sur des entrées non valides est une mauvaise idée, car cette valeur est inutile et source d’erreurs (0 <-> fausse confusion).la source
NaN
"contrepoint". NaN est en fait une entrée valide pour tout calcul en virgule flottante. Vous pouvez faire n'importe quoi avec ceNaN
que vous pourriez faire avec un nombre réel. Certes, le résultat final dudit calcul ne vous sera peut-être pas très utile, mais il ne provoquera pas d'étranges erreurs d'exécution et vous n'avez pas besoin de le vérifier tout le temps - vous pouvez simplement vérifier une fois à la fin d'un processus. série de calculs. NaN n'est pas un type différent , c'est simplement une valeur spéciale , un peu comme un objet null .NaN
comme l'objet null, tend à se cacher lorsque des erreurs se produisent, uniquement pour qu'elles apparaissent plus tard. Par exemple, votre programme risque d’exploser s’il seNaN
glisse dans une boucle conditionnelle.NaN
valeur est (a) n'est pas réellement un type de retour différent et (b) a une sémantique en virgule flottante bien définie et que, par conséquent, ne peut pas être considéré comme un analogue d'une valeur de retour de type variable. Ce n'est pas si différent de zéro en arithmétique entière, vraiment; Si un zéro se glisse "accidentellement" dans vos calculs, vous risquez souvent de vous retrouver avec un résultat nul ou une erreur de division par zéro. Les valeurs "infini" définies par la virgule flottante IEEE sont-elles également mauvaises?Dans les langages dynamiques, vous ne devriez pas demander si vous renvoyez des types différents mais des objets avec différentes API . Comme la plupart des langages dynamiques ne s’intéressent pas vraiment aux types, utilisez différentes versions de la saisie de code .
Lors du retour de différents types ont un sens
Par exemple, cette méthode a du sens:
Parce que le fichier et une liste de chaînes sont (en Python) des itérables qui renvoient une chaîne. Types très différents, même API (à moins que quelqu'un essaie d'appeler des méthodes de fichier sur une liste, mais l'histoire est différente).
Vous pouvez retourner conditionnellement
list
outuple
(tuple
est une liste immuable en python).Formellement même en train de faire:
ou:
renvoie différents types, car python
None
et Javascriptnull
sont des types à part entière.Tous ces cas d'utilisation auraient leur contrepartie dans des langages statiques, la fonction ne ferait que renvoyer une interface appropriée.
Quand renvoyer des objets avec une API différente de manière conditionnelle est une bonne idée
Quant à savoir si le retour de différentes API est une bonne idée, dans la plupart des cas, cela n’a aucun sens. @MainMa a déclaré que seul un exemple judicieux qui me vient à l’esprit est le suivant : lorsque votre API peut fournir des quantités de détails variables, il peut être judicieux de renvoyer plus de détails, le cas échéant.
la source
do_file
retourne un itérable de chaînes dans les deux sens.iter()
la liste et le fichier pour vous assurer que le résultat ne peut être utilisé comme itérateur que dans les deux cas.None or something
est une excellente idée pour l'optimisation des performances réalisée par des outils tels que PyPy, Numba, Pyston et autres. C’est l’un des pythons qui rend python pas si vite.Votre question me donne envie de pleurer un peu. Pas pour l'exemple d'utilisation que vous avez fourni, mais parce que quelqu'un va involontairement aller trop loin dans cette approche. C'est un pas en avant d'un code ridiculement incontrôlable.
Le cas d'utilisation de la condition d'erreur est logique, et le modèle nul (tout doit être un modèle) dans les langages à typage statique fait le même type de chose. Votre appel de fonction retourne un
object
ou il retournenull
.Mais il n'y a qu'un pas à dire : « Je vais l' utiliser pour créer un modèle d'usine » et le retour soit
foo
oubar
oubaz
selon l'humeur de la fonction. Déboguer ceci deviendra un cauchemar quand l'appelant s'attend à ce qu'ilfoo
soit donnébar
.Donc, je ne pense pas que vous soyez fermé d'esprit. Vous faites preuve de la plus grande prudence lorsque vous utilisez les fonctionnalités de la langue.
Divulgation: mon expérience est dans les langages à typage statique et j'ai généralement travaillé pour des équipes plus grandes et variées où le besoin de code maintenable était assez élevé. Donc, ma perspective est probablement biaisée aussi.
la source
L'utilisation de génériques en Java vous permet de renvoyer un type différent tout en conservant la sécurité du type statique. Vous spécifiez simplement le type que vous voulez renvoyer dans le paramètre de type générique de l'appel de fonction.
La question de savoir si vous pouvez utiliser une approche similaire en Javascript est bien sûr une question ouverte. Puisque le Javascript est un langage typé dynamiquement, retourner un
object
semble être le choix évident.Si vous souhaitez savoir où un scénario de retour dynamique peut fonctionner lorsque vous avez l'habitude de travailler dans un langage à typage statique, envisagez de rechercher le
dynamic
mot clé en C #. Rob Conery a réussi à écrire un mappeur relationnel-objet dans 400 lignes de code à l'aide dudynamic
mot - clé.Bien entendu, la
dynamic
seule chose à faire est d’envelopper uneobject
variable avec une sécurité de type exécution.la source
Je pense que c'est une mauvaise idée de retourner différents types sous condition. Pour moi, cela se produit fréquemment si la fonction peut renvoyer une ou plusieurs valeurs. Si une seule valeur doit être renvoyée, il peut sembler raisonnable de la renvoyer plutôt que de la placer dans un tableau afin d'éviter d'avoir à la décompresser dans la fonction appelante. Toutefois, cela (et la plupart des autres exemples) oblige l'appelant à distinguer les deux types et à les gérer. La fonction sera plus facile à raisonner si elle retourne toujours le même type.
la source
La "mauvaise pratique" existe, que votre langue soit typée de manière statique ou non. Le langage statique fait plus pour vous éloigner de ces pratiques, et vous pourriez trouver plus d'utilisateurs qui se plaignent de "mauvaise pratique" dans un langage statique parce que c'est un langage plus formel. Cependant, les problèmes sous-jacents existent dans un langage dynamique et vous pouvez déterminer s'ils sont justifiés.
Voici la partie contestable de ce que vous proposez. Si je ne sais pas quel type est renvoyé, je ne peux pas utiliser la valeur de retour immédiatement. Je dois "découvrir" quelque chose à ce sujet.
Souvent, ce type de changement de code est simplement une mauvaise pratique. Cela rend le code plus difficile à lire. Dans cet exemple, vous voyez pourquoi le lancement et la capture de cas d'exception sont populaires. En d'autres termes: si votre fonction ne peut pas faire ce qu'elle dit, elle ne devrait pas revenir avec succès . Si j'appelle votre fonction, je veux faire ceci:
Comme la première ligne est renvoyée avec succès, je suppose que celle-ci
total
contient en réalité la somme du tableau. S'il ne revient pas correctement, nous passons à une autre page de mon programme.Prenons un autre exemple qui ne concerne pas uniquement la propagation d’erreurs. Peut-être que sum_of_array essaie d'être intelligent et de retourner une chaîne lisible par l'homme dans certains cas, comme "C'est ma combinaison de casier!" si et seulement si le tableau est [11,7,19]. J'ai du mal à penser à un bon exemple. Quoi qu'il en soit, le même problème s'applique. Vous devez inspecter la valeur de retour avant de pouvoir faire quoi que ce soit avec elle:
Vous pourriez faire valoir qu'il serait utile que la fonction renvoie un entier ou un float, par exemple:
Mais ces résultats ne sont pas différents, en ce qui vous concerne. Vous allez les traiter tous les deux comme des chiffres, et c'est tout ce qui compte pour vous. Donc, sum_of_array retourne le type de nombre. C'est ce que le polymorphisme est à propos.
Il y a donc des pratiques que vous pourriez enfreindre si votre fonction peut renvoyer plusieurs types. Les connaître vous aidera à déterminer si votre fonction particulière doit toujours renvoyer plusieurs types.
la source
En réalité, il n’est pas rare de renvoyer différents types, même dans un langage à typage statique. C'est pourquoi nous avons des types d'union, par exemple.
En fait, les méthodes en Java renvoient presque toujours l’un des quatre types: un type d’objet
null
ou une exception, ou elles ne le sont jamais.Dans de nombreuses langues, les conditions d'erreur sont modélisées comme des sous-routines renvoyant un type de résultat ou un type d'erreur. Par exemple, en Scala:
Ceci est un exemple stupide, bien sûr. Le type de retour signifie "soit renvoyer une chaîne, soit un nombre décimal". Par convention, le type de gauche est le type d'erreur (dans ce cas, une chaîne avec le message d'erreur) et le type de droite est le type de résultat.
Ceci est similaire aux exceptions, sauf que les exceptions sont également des constructions de flux de contrôle. En fait, leur pouvoir expressif est équivalent à
GOTO
.la source
Unit
(une méthode n'est pas obligée de revenir du tout). Certes, vous n'utilisez pas lereturn
mot - clé, mais le résultat provient néanmoins de la méthode. À quelques exceptions près, il est même explicitement mentionné dans la signature de type.GOTO
, en réalité).Aucune réponse n'a encore mentionné les principes SOLID. En particulier, vous devez suivre le principe de substitution de Liskov, à savoir que toute classe recevant un type autre que le type attendu peut toujours travailler avec ce qu'elle obtient, sans rien faire pour tester le type retourné.
Donc, si vous jetez des propriétés supplémentaires sur un objet, ou si vous encapsulez une fonction retournée avec une sorte de décorateur qui accomplit tout ce que la fonction d'origine était censée accomplir, vous êtes bon, tant qu'aucun code n'appelant votre fonction ne repose sur cette comportement, dans n'importe quel chemin de code.
Au lieu de renvoyer une chaîne ou un entier, un meilleur exemple pourrait être qu'il retourne un système d'arrosage ou un chat. C'est bien si tout le code d'appel est appelé, c'est d'appeler functionInQuestion.hiss (). Effectivement, vous avez une interface implicite attendue par le code appelant, et un langage à typage dynamique ne vous obligera pas à rendre l'interface explicite.
Malheureusement, vos collègues le feront probablement, vous devrez donc probablement faire le même travail dans votre documentation, sauf qu'il n'existe pas de moyen de le faire universellement accepté, concis, analysable par machine - comme il en existe quand vous définissez une interface. dans une langue qui les a.
la source
Le seul endroit où je me vois envoyer différents types concerne les entrées non valides ou les "exceptions de pauvres" où la condition "exceptionnelle" n'est pas très exceptionnelle. Par exemple, à partir de mon référentiel de fonctions utilitaires PHP, voici un exemple abrégé:
La fonction renvoie nominalement un BOOLEAN, cependant elle renvoie NULL si une entrée est invalide. Notez que depuis PHP 5.3, toutes les fonctions internes à PHP se comportent également de cette manière . De plus, certaines fonctions internes à PHP renvoient FALSE ou INT en entrée nominale, voir:
la source
null
, parce que (a) elle échoue bruyamment , elle est donc plus susceptible d'être fixée sur la phase de développement / test, l’application entière (dans mon contrôleur frontal) et enregistrez-la simplement (j’envoie généralement des e-mails à l’équipe de développement), afin qu’elle puisse être corrigée ultérieurement. Et je déteste réellement que la bibliothèque standard PHP utilise principalement l'approche "return null" - elle rend le code PHP plus sujet aux erreurs, à moins que vous ne vérifiiez toutisset()
, ce qui est trop lourd.null
en cas d'erreur, veuillez l'indiquer clairement dans un docblock (par exemple@return boolean|null
). Ainsi, si je rencontrais votre code un jour, je n'aurais pas à vérifier le corps de la fonction / méthode.Je ne pense pas que ce soit une mauvaise idée! Contrairement à cette opinion très répandue, et déjà soulignée par Robert Harvey, les langages à typage statique comme Java ont introduit Generics exactement pour des situations comme celle que vous demandez. En fait, Java essaie de conserver (dans la mesure du possible) la sécurité typographique au moment de la compilation, et parfois, Generics évite la duplication de code, pourquoi? Parce que vous pouvez écrire la même méthode ou la même classe qui gère / renvoie des types différents . Je vais juste donner un très bref exemple pour montrer cette idée:
Java 1.4
Java 1.5+
Dans un langage typé dynamique, étant donné que vous n'avez aucune sécurité à la compilation lors de la compilation, vous êtes absolument libre d'écrire le même code qui fonctionne pour plusieurs types. Étant donné que même dans un langage à typage statique, des génériques ont été introduits pour résoudre ce problème, il est clair que l'écriture d'une fonction qui renvoie différents types dans un langage dynamique n'est pas une mauvaise idée.
la source
Développer un logiciel est fondamentalement un art et un art de gérer la complexité. Vous essayez de restreindre le système aux points où vous pouvez vous le permettre et de limiter les options à d’autres points. L'interface de Function est un contrat qui permet de gérer la complexité du code en limitant les connaissances requises pour travailler avec n'importe quel élément de code. En renvoyant différents types, vous développez de manière significative l'interface de la fonction en y ajoutant toutes les interfaces de types différents que vous renvoyez ET en ajoutant des règles non évidentes indiquant l'interface à renvoyer.
la source
Perl l'utilise beaucoup car ce que fait une fonction dépend de son contexte . Par exemple, une fonction peut renvoyer un tableau si elle est utilisée dans un contexte de liste ou la longueur du tableau si elle est utilisée à un endroit où une valeur scalaire est attendue. Dans le didacticiel correspondant au premier résultat relatif à "contexte Perl" , procédez comme suit:
Alors @now est une variable de tableau (c'est ce que signifie @) et contiendra un tableau comme (40, 51, 20, 9, 0, 109, 5, 8, 0).
Si, au lieu de cela, vous appelez la fonction de manière à ce que son résultat soit un scalaire, avec ($ les variables sont des scalaires):
alors cela fait quelque chose de complètement différent: $ now sera quelque chose comme "Fri Jan 9 20:51:40 2009".
Un autre exemple auquel je peux penser est l’implémentation d’API REST, où le format de ce qui est renvoyé dépend de ce que le client veut. Par exemple, HTML ou JSON ou XML. Bien que techniquement ce soient tous des flux d'octets, l'idée est similaire.
la source
String
. Maintenant, les gens demandent de la documentation sur le type de retour. Les outils java peuvent le générer automatiquement si je retourne une classe, mais parce que j'ai choisi,String
je ne peux pas générer la documentation automatiquement. Avec le recul / la moralité de l’histoire, j’aimerais retourner un type par méthode.Dans Dynamic-Land, tout est axé sur la frappe de canards. La chose la plus responsable exposée / confrontée au public consiste à envelopper des types potentiellement différents dans un wrapper qui leur donne la même interface.
Il peut parfois être judicieux de casser différents types sans utiliser de wrapper générique, mais honnêtement, sept ans après la rédaction de JS, ce n’est pas quelque chose que j’ai trouvé raisonnable ou pratique de le faire très souvent. C'est ce que je ferais principalement dans le contexte d'environnements fermés, comme l'intérieur d'un objet où les choses sont en train de se mettre en place. Mais ce n’est pas quelque chose que j’ai fait assez souvent que l’on se méfie des exemples.
Surtout, je vous conseillerais de cesser de penser aux types autant. Vous travaillez avec des types lorsque vous en avez besoin dans un langage dynamique. Pas plus. C'est le point entier. Ne vérifiez pas le type de chaque argument. C'est quelque chose que je ne serais tenté de faire que dans un environnement où la même méthode pourrait donner des résultats incohérents de manière non évidente (donc ne faites absolument pas ce genre de chose). Mais ce n'est pas le type qui compte, c'est la façon dont vous me donnez le produit que vous donnez.
la source