Est-il bon de revenir explicitement en Ruby?

155

Venant d'un arrière-plan Python, où il y a toujours une «bonne façon de le faire» (une façon «pythonique») en matière de style, je me demande si la même chose existe pour Ruby. J'ai utilisé mes propres règles de style, mais je pense publier mon code source et j'aimerais qu'il adhère à toutes les règles non écrites qui pourraient exister.

Est-ce "The Ruby Way" de taper explicitement returndans les méthodes? Je l'ai vu faire avec et sans, mais y a-t-il une bonne façon de le faire? Y a-t-il peut-être un bon moment pour le faire? Par exemple:

def some_func(arg1, arg2, etc)
  # Do some stuff...
  return value # <-- Is the 'return' needed here?
end
Sasha Chedygov
la source
3
C'est une question très ancienne, mais pour ceux qui ont des questions similaires, le livre Eloquent Ruby est fortement recommandé. Ce n'est pas autant un guide de style qu'une introduction à l'écriture idiomatique de Ruby. J'aurais aimé que plus de gens le lisent!
Brian Dear
En tant que personne qui a hérité d'une grande application héritée développée en Ruby, j'apprécie beaucoup que vous ayez posé cette question. Cela mérite un vote positif. Ceci est jonché dans notre ancienne application. Un de mes travaux dans cette équipe est d'améliorer la base de code (difficile quand on est nouveau sur Ruby).
Stevers

Réponses:

226

Ancienne question (et "répondu"), mais je vais jeter mes deux cents en guise de réponse.

TL; DR - Vous n'êtes pas obligé, mais cela peut rendre votre code beaucoup plus clair dans certains cas.

Bien que ne pas utiliser de retour explicite puisse être «à la manière de Ruby», il est déroutant pour les programmeurs travaillant avec du code inconnu ou peu familier avec cette fonctionnalité de Ruby.

C'est un exemple un peu artificiel, mais imaginez avoir une petite fonction comme celle-ci, qui ajoute un au nombre passé et l'assigne à une variable d'instance.

def plus_one_to_y(x)
    @y = x + 1
end

Était-ce censé être une fonction qui retournait une valeur, ou non? Il est vraiment difficile de dire ce que le développeur voulait dire, car il attribue à la fois la variable d'instance et renvoie également la valeur attribuée.

Supposons que beaucoup plus tard, un autre programmeur (peut-être pas très familier avec la façon dont Ruby effectue des retours basés sur la dernière ligne de code exécutée) arrive et veut mettre dans quelques instructions d'impression pour la journalisation, et la fonction devient ceci ...

def plus_one_to_y(x)
    @y = x + 1
    puts "In plus_one_to_y"
end

Maintenant, la fonction est interrompue si quelque chose attend une valeur renvoyée . Si rien n'attend une valeur renvoyée, c'est bien. Clairement, si quelque part plus bas dans la chaîne de code, quelque chose qui appelle ceci attend une valeur renvoyée, il échouera car il ne récupère pas ce qu'il attend.

La vraie question est maintenant la suivante: est-ce que quelque chose s'attendait vraiment à une valeur renvoyée? Cela a-t-il cassé quelque chose ou pas? Va-t-il casser quelque chose dans le futur? Qui sait! Seul un examen complet du code de tous les appels vous le permettra.

Donc, pour moi au moins, la meilleure pratique consiste soit à être très explicite que vous renvoyez quelque chose si cela compte, soit à ne rien renvoyer du tout quand ce n'est pas le cas.

Donc, dans le cas de notre petite fonction de démonstration, en supposant que nous voulions qu'elle renvoie une valeur, elle s'écrirait comme ceci ...

def plus_one_to_y(x)
    @y = x + 1
    puts "In plus_one_to_y"
    return @y
end

Et il serait très clair pour tout programmeur qu'il renvoie une valeur, et beaucoup plus difficile pour eux de la casser sans s'en rendre compte.

Alternativement, on pourrait l'écrire comme ça et laisser de côté l'instruction return ...

def plus_one_to_y(x)
    @y = x + 1
    puts "In plus_one_to_y"
    @y
end

Mais pourquoi se donner la peine d'omettre le mot retour? Pourquoi ne pas simplement le mettre là-dedans et expliquer à 100% ce qui se passe? Cela n'aura littéralement aucun impact sur la capacité de votre code à fonctionner.

Tim Holt
la source
6
Point intéressant. Je pense que je me suis retrouvé dans une situation similaire lorsque j'ai posé cette question, où le développeur d'origine a utilisé la valeur de retour implicite dans son code, et il était très difficile de comprendre ce qui se passait. Merci pour votre réponse!
Sasha Chedygov
6
J'ai trouvé la question en googlant sur les retours implicites, car je venais d'être brûlé par cela. J'avais ajouté une logique à la fin d'une fonction qui retournait implicitement quelque chose. La plupart des appels ne se soucient pas (ou ne vérifient) pas la valeur renvoyée, mais l'un d'entre eux le fait - et cela a échoué. Heureusement, au moins, il est apparu dans certains tests fonctionnels.
Tim Holt
16
Bien que cela soit vrai, il est important que le code soit lisible, éviter une fonctionnalité bien connue d'un langage de programmation au profit de la verbosité est, à mon avis, une mauvaise pratique. Si un développeur n'est pas familier avec Ruby, il devrait peut-être se familiariser avant de toucher au code. Je trouve un peu idiot de devoir augmenter l'encombrement de mon code juste pour le futur événement qu'un développeur non Ruby doit faire plus tard. Nous ne devrions pas réduire une langue au plus petit dénominateur commun. Une partie de la beauté de Ruby est que nous n'avons pas besoin d'utiliser 5 lignes de code pour dire quelque chose qui ne devrait prendre que 3.
Brian Dear
25
Si le mot returnajoute une signification sémantique au code, pourquoi pas? Ce n'est guère très verbeux - nous ne parlons pas de 5 lignes contre 3, juste un mot de plus. Je préférerais voir returnau moins dans les fonctions (peut-être pas dans des blocs) afin d'exprimer ce que fait réellement le code. Parfois, vous devez retourner à mi-fonction - ne laissez pas le dernier returnmot-clé sur la dernière ligne simplement parce que vous le pouvez. Je ne suis pas du tout favorable à la verbosité, mais je privilégie la cohérence.
mindplay.dk
6
C'est une excellente réponse. Mais cela laisse toujours le risque que vous écriviez une méthode Ruby ayant l'intention d'être une procédure (aka fonction retournant l'unité) par exemple side_effect()et qu'un autre développeur décide d'utiliser (ab) la valeur de retour implicite de votre procédure. Pour résoudre ce problème, vous pouvez définir vos procédures explicitement return nil.
Alex Dean
66

Non. Le bon style Ruby n'utilise généralement qu'un retour explicite pour un retour anticipé . Ruby est grand sur le minimalisme du code / la magie implicite.

Cela dit, si un retour explicite rendrait les choses plus claires ou plus faciles à lire, cela ne nuira à rien.

Ben Hughes
la source
116
Quel est l'intérêt du minimalisme de code s'il aboutit à un maximalisme de confusion?
jononomo
@JonCrowell Cela devrait plutôt aboutir à un maximalisme de la documentation.
jazzpi
29
@jazzpi Si vous devez documenter fortement votre code pour qu'il soit bien compris, vous ne pratiquez pas le minimalisme, vous pratiquez le golf de code .
Schwern
2
Il n'est pas déroutant d'exclure le retour final, il est déroutant de l'INCLURE - en tant que développeur Ruby, a returndéjà une signification sémantique significative en tant que SORTIE ANTICIPÉE de la fonction.
Devon Parsons
14
@DevonParsons Comment diable quelqu'un pourrait-il confondre un returnsur la dernière ligne avec une sortie anticipée de toute façon?
DarthPaghius
31

J'utilise personnellement le returnmot - clé pour faire la distinction entre ce que j'appelle les méthodes fonctionnelles , c'est-à-dire les méthodes qui sont exécutées principalement pour leur valeur de retour, et les méthodes procédurales qui sont exécutées principalement pour leurs effets secondaires. Ainsi, les méthodes dans lesquelles la valeur de retour est importante, obtiennent un returnmot clé supplémentaire pour attirer l'attention sur la valeur de retour.

J'utilise la même distinction lors de l' appel de méthodes: les méthodes fonctionnelles obtiennent des parenthèses, les méthodes procédurales non.

Et enfin, j'utilise aussi cette distinction avec les blocs: les blocs fonctionnels obtiennent des accolades, les blocs procéduraux (c'est-à-dire les blocs qui "font" quelque chose) obtiennent do/ end.

Cependant, j'essaie de ne pas être religieux à ce sujet: avec des blocs, des accolades et do/ endont une priorité différente, et plutôt que d'ajouter des parenthèses explicites pour lever l'ambiguïté d'une expression, je passe simplement à l'autre style. Il en va de même pour l'appel de méthode: si l'ajout de parenthèses autour de la liste de paramètres rend le code plus lisible, je le fais, même si la méthode en question est de nature procédurale.

Jörg W Mittag
la source
1
À l'heure actuelle, j'omets un retour dans les "one-liners", ce qui est similaire à ce que vous faites, et je fais tout le reste de la même manière. Bon à savoir, je ne suis pas complètement dans mon style. :)
Sasha Chedygov
@Jorg: Utilisez-vous des retours anticipés dans votre code? Cela crée-t-il une confusion avec votre schéma procédural / fonctionnel?
Andrew Grimm
1
@Andrew Grimm: Oui et oui. Je pense à l'inverser. La grande majorité de mes méthodes est référentiellement transparente / pure / fonctionnelle / comme vous voulez l'appeler de toute façon, il est donc logique d'utiliser la forme la plus courte. Et les méthodes écrites dans un style fonctionnel ont tendance à ne pas avoir de retours précoces, car cela implique la notion de "pas", qui n'est pas très fonctionnelle. Bien que je n'aime pas les retours au milieu. Je les utilise soit au début comme gardes, soit à la fin pour simplifier le flux de contrôle.
Jörg W Mittag
Excellente réponse, mais il est en fait préférable d'appeler des méthodes procédurales avec () et des méthodes fonctionnelles sans - voir ma réponse ci-dessous pour savoir pourquoi
Alex Dean
13

En fait, l'important est de faire la distinction entre:

  1. Fonctions - méthodes exécutées pour leur valeur de retour
  2. Procédures - méthodes exécutées pour leurs effets secondaires

Ruby n'a pas de moyen natif de les distinguer - ce qui vous rend vulnérable à l'écriture d'une procédure side_effect()et qu'un autre développeur décide d'abuser de la valeur de retour implicite de votre procédure (en la traitant essentiellement comme une fonction impure).

Pour résoudre cela, prenez une feuille du livre de Scala et Haskell et faites revenir explicitement vos procéduresnil (aka Unitou ()dans d'autres langues).

Si vous suivez ceci, utiliser returnou non une syntaxe explicite devient simplement une question de style personnel.

Pour distinguer davantage les fonctions et les procédures:

  1. Copiez la belle idée de Jörg W Mittag d'écrire des blocs fonctionnels avec des accolades et des blocs procéduraux avec do/end
  2. Lorsque vous appelez des procédures, utilisez () , tandis que lorsque vous appelez des fonctions, ne

Notez que Jörg W Mittag préconisait en fait l'inverse - éviter les ()s pour les procédures - mais ce n'est pas conseillé car vous voulez que les invocations de méthodes à effet secondaire soient clairement distinguables des variables, en particulier lorsque l'arité est égale à 0. Voir le guide de style Scala sur l'invocation de méthode pour détails.

Alex Dean
la source
Si j'ai besoin de la valeur renvoyée de function_a, et que j'ai function_aété écrite pour appeler (et que je ne peux fonctionner qu'en appelant) procedure_b, est-ce function_avraiment une fonction? Il renvoie une valeur, répondant à vos exigences en matière de fonctions, mais il a un effet secondaire, répondant aux exigences des procédures.
Devon Parsons
2
Vous avez raison Devon - function_apourrait contenir un arbre d' procedure_...appels, comme en fait procedure_apourrait contenir un arbre d' function_...appels. Comme Scala mais contrairement à Haskell, il ne peut y avoir aucune garantie dans Ruby qu'une fonction n'a pas d'effet secondaire.
Alex Dean
8

Le guide de style indique que vous ne devriez pas utiliser returnsur votre dernière déclaration. Vous pouvez toujours l'utiliser si ce n'est pas le dernier . C'est l'une des conventions que la communauté suit strictement, et vous devriez le faire si vous prévoyez de collaborer avec quiconque utilise Ruby.


Cela étant dit, le principal argument en faveur de l'utilisation de returns explicites est qu'il est déroutant pour les personnes venant d'autres langues.

  • Premièrement, ce n'est pas entièrement exclusif à Ruby. Par exemple, Perl a également des retours implicites.
  • Deuxièmement, la majorité des personnes à qui cela s'applique viennent de langues algolaises. La plupart d'entre eux sont bien plus "de niveau inférieur" que Ruby, donc vous devez écrire plus de code pour faire quelque chose.

Une heuristique courante pour la longueur de méthode (à l'exclusion des getters / setters) en java est un écran . Dans ce cas, il se peut que vous ne voyiez pas la définition de la méthode et / ou que vous ayez déjà oublié d'où vous reveniez.

En revanche, en Ruby, il est préférable de s'en tenir à des méthodes de moins de 10 lignes . Compte tenu de cela, on pourrait se demander pourquoi il doit écrire ~ 10% de déclarations supplémentaires, alors qu'elles sont clairement implicites.


Puisque Ruby n'a pas de méthodes vides et que tout est beaucoup plus concis, vous ajoutez simplement des frais généraux pour aucun des avantages si vous optez pour des returns explicites .

ndnenkov
la source
1
"C'est l'une des conventions que la communauté suit strictement" Mon expérience est que non, les gens ne le suivent strictement que s'ils sont des développeurs Ruby très expérimentés.
Tim Holt
1
@TimHolt J'ai dû être très chanceux alors. (:
ndnenkov
1
@ndn entièrement d'accord. L'important est que lorsqu'une méthode (ou une classe) commence à dépasser 5/100 lignes, c'est une bonne idée de reconsidérer ce que vous essayez d'accomplir. En réalité, les règles de Sandi sont un cadre cognitif pour penser le code par opposition à un dogme arbitraire. La majorité du code que je rencontre n'est pas problématique car il est artificiellement contraint - c'est généralement le contraire - des classes avec des responsabilités multiples, des méthodes plus longues que de nombreuses classes. Essentiellement trop de gens «sautent le requin» - mêlant les responsabilités.
Brian Dear
1
Parfois, la convention est encore une mauvaise idée. C'est une convention stylistique qui repose sur la magie, qui rend les types de retour difficiles à deviner. n'offre aucun avantage autre que l'enregistrement de 7 frappes et n'est pas compatible avec presque toutes les autres langues. Si Ruby fait une passe de consolidation et de simplification de style Python 3, j'espère que tout ce qui est implicite est le premier sur le bloc de hachage.
Shayne
2
Sauf clairement que cela ne facilite pas vraiment la lecture! La magie et la compréhensibilité ne sont pas la même chose.
Shayne
4

Je suis d'accord avec Ben Hughes et en désaccord avec Tim Holt, car la question mentionne la manière définitive dont Python le fait et demande si Ruby a une norme similaire.

Cela fait.

C'est une caractéristique si bien connue du langage que quiconque s'attend à déboguer un problème dans ruby ​​devrait raisonnablement être censé le savoir.

Devon Parsons
la source
3
Je suis d'accord avec vous en théorie, mais en pratique, les programmeurs font des erreurs et font des bêtises. Par exemple, nous savons tous que vous utilisez "==" dans un conditionnel et non "=", mais nous avons TOUS fait cette erreur avant et ne l'avons réalisé que plus tard.
Tim Holt
1
@TimHolt Je n'aborde aucun cas d'utilisation particulier, je réponds juste à la question. Il est explicitement mauvais, tel que choisi par la communauté, d'utiliser le returnmot - clé lorsque cela n'est pas nécessaire.
Devon Parsons
3
C'est suffisant. Je dis que le style que vous mentionnez peut conduire à des erreurs si vous ne faites pas attention. Sur la base de mon expérience, je préconise personnellement un style différent.
Tim Holt
1
@TimHolt En passant, cette question a été posée bien avant la rédaction du guide de style, donc les réponses qui ne sont pas d'accord ne sont pas nécessairement fausses, juste obsolètes. Je suis tombé ici d'une manière ou d'une autre et je n'ai pas vu le lien que j'ai fourni, alors j'ai pensé que quelqu'un d'autre pourrait finir ici aussi.
Devon Parsons
1
Ce n'est pas une "norme" officielle. Le guide de style ruby ​​utilisé par rubocop et lié ci-dessus à la revendication qu'il y a de Devon Parsons, est créé par un pirate informatique non-core. Le commentaire de Devon est donc tout simplement erroné. Bien sûr, vous pouvez TOUJOURS l'utiliser mais ce n'est pas une norme.
shevy
1

C'est une question de style que vous préférez pour la plupart. Vous devez utiliser le mot-clé return si vous souhaitez revenir de quelque part au milieu d'une méthode.

Praveen Angyan
la source
0

Comme Ben l'a dit. Le fait que «la valeur de retour d'une méthode ruby ​​est la valeur de retour de la dernière instruction dans le corps de la fonction» fait que le mot-clé return est rarement utilisé dans la plupart des méthodes ruby.

def some_func_which_returns_a_list( x, y, z)
  return nil if failed_some_early_check


  # function code 

  @list     # returns the list
end
Gishu
la source
4
vous pouvez également écrire "return nil if failed_some_early_check" si votre intention est toujours claire
Mike Woodhouse
@Gishu Je me plaignais en fait de l'instruction if, pas du nom de la méthode.
Andrew Grimm
@Andrew .. oh la fin manquante :). Je l'ai. corrigé pour pacifier l'autre commentaire de Mike.
Gishu
Pourquoi pas if failed_check then nil else @list end? C'est vraiment fonctionnel .
cdunn2001
@ cdunn2001 On ne sait pas pourquoi si alors else est fonctionnel ... la raison de ce style est qu'il réduit l'imbrication via les sorties anticipées. IMHO également plus lisible.
Gishu