Ist es ein guter Stil, explizit in Ruby zurückzukehren?

155

Ich komme aus einem Python-Hintergrund, in dem es immer einen "richtigen Weg" (einen "pythonischen" Weg) gibt, wenn es um Stil geht, und frage mich, ob es für Ruby dasselbe gibt. Ich habe meine eigenen Stilrichtlinien verwendet, denke aber darüber nach, meinen Quellcode freizugeben, und ich möchte, dass er sich an eventuell vorhandene ungeschriebene Regeln hält.

Ist es "The Ruby Way", returnMethoden explizit einzugeben? Ich habe es mit und ohne gesehen, aber gibt es einen richtigen Weg, es zu tun? Gibt es vielleicht einen richtigen Zeitpunkt dafür? Beispielsweise:

def some_func(arg1, arg2, etc)
  # Do some stuff...
  return value # <-- Is the 'return' needed here?
end
Sasha Chedygov
quelle
3
Dies ist eine sehr alte Frage, aber für diejenigen, die ähnliche Fragen haben, ist das Buch Eloquent Ruby sehr zu empfehlen. Es ist weniger ein Styleguide als eine Einführung in das Schreiben von idiomatischem Ruby. Ich wünschte, mehr Leute würden es lesen!
Brian Lieber
Als jemand, der eine große Legacy-App geerbt hat, die in Ruby entwickelt wurde, schätze ich es sehr, dass Sie diese Frage gestellt haben. Dies verdient eine positive Abstimmung. Dies ist in unserer gesamten Legacy-App verstreut. Eine meiner Aufgaben in diesem Team ist es, die Codebasis zu verbessern (schwierig, wenn ich neu bei Ruby bin).
Stevers

Antworten:

226

Alte (und "beantwortete") Frage, aber ich werde meine zwei Cent als Antwort einwerfen.

TL; DR - Sie müssen nicht, aber es kann Ihren Code in einigen Fällen viel klarer machen.

Obwohl die Verwendung einer expliziten Rückgabe möglicherweise "der Ruby-Weg" ist, ist dies für Programmierer, die mit unbekanntem Code arbeiten oder mit dieser Funktion von Ruby nicht vertraut sind, verwirrend.

Es ist ein etwas ausgeklügeltes Beispiel, aber stellen Sie sich vor, Sie hätten eine kleine Funktion wie diese, die der übergebenen Zahl eine hinzufügt und sie einer Instanzvariablen zuweist.

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

War dies eine Funktion, die einen Wert zurückgab, oder nicht? Es ist wirklich schwer zu sagen, was der Entwickler gemeint hat, da beide die Instanzvariable zuweisen UND auch den zugewiesenen Wert zurückgeben.

Angenommen, viel später kommt ein anderer Programmierer (der vielleicht nicht so vertraut damit ist, wie Ruby basierend auf der zuletzt ausgeführten Codezeile zurückgibt) und möchte einige print-Anweisungen für die Protokollierung eingeben, und die Funktion wird zu dieser ...

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

Jetzt ist die Funktion unterbrochen, wenn etwas einen zurückgegebenen Wert erwartet . Wenn nichts einen zurückgegebenen Wert erwartet, ist dies in Ordnung. Wenn irgendwo weiter unten in der Codekette ein Aufruf einen zurückgegebenen Wert erwartet, wird dies eindeutig fehlschlagen, da er nicht das zurückerhält, was er erwartet.

Die eigentliche Frage lautet nun: Hat irgendetwas wirklich einen zurückgegebenen Wert erwartet? Hat das etwas kaputt gemacht oder nicht? Wird es in Zukunft etwas kaputt machen? Wer weiß! Nur eine vollständige Codeüberprüfung aller Anrufe informiert Sie.

Zumindest für mich besteht der Best-Practice-Ansatz darin, entweder sehr deutlich zu machen, dass Sie etwas zurückgeben, wenn es wichtig ist, oder überhaupt nichts zurückzugeben, wenn dies nicht der Fall ist.

Im Fall unserer kleinen Demofunktion würde sie also so geschrieben sein, vorausgesetzt, wir wollten, dass sie einen Wert zurückgibt ...

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

Und es wäre jedem Programmierer sehr klar, dass es einen Wert zurückgibt, und es ist für sie viel schwieriger, ihn zu brechen, ohne es zu merken.

Alternativ könnte man es so schreiben und die return-Anweisung weglassen ...

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

Aber warum sollte man sich die Mühe machen, das Wort Rückkehr wegzulassen? Warum nicht einfach dort hineinstecken und 100% klar machen, was passiert? Dies hat buchstäblich keinen Einfluss auf die Leistungsfähigkeit Ihres Codes.

Tim Holt
quelle
6
Interessanter Punkt. Ich glaube, ich bin tatsächlich auf eine ähnliche Situation gestoßen, als ich diese Frage gestellt habe, bei der der ursprüngliche Entwickler den impliziten Rückgabewert in seinem Code verwendet hat, und es war sehr schwierig zu verstehen, was los war. Danke für deine Antwort!
Sasha Chedygov
6
Ich fand die Frage beim Googeln über implizite Rückgaben, da ich gerade davon verbrannt worden war. Ich hatte am Ende einer Funktion, die implizit etwas zurückgab, eine Logik hinzugefügt. Die meisten Aufrufe kümmerten sich nicht um den zurückgegebenen Wert (oder überprüften ihn), aber einer tat es - und es schlug fehl. Zum Glück zeigte es sich zumindest in einigen Funktionstests.
Tim Holt
16
Obwohl dies zutrifft, ist es wichtig, dass Code lesbar ist. Meiner Meinung nach ist es eine schlechte Praxis, ein bekanntes Merkmal einer Programmiersprache zugunsten der Ausführlichkeit zu meiden. Wenn ein Entwickler nicht mit Ruby vertraut ist, sollte er sich vielleicht vertraut machen, bevor er den Code berührt. Ich finde es ein bisschen albern, die Unordnung meines Codes nur für den zukünftigen Fall erhöhen zu müssen, dass ein Nicht-Ruby-Entwickler später etwas tun muss. Wir sollten eine Sprache nicht auf den kleinsten gemeinsamen Nenner reduzieren. Ein Teil der Schönheit von Ruby ist, dass wir nicht 5 Codezeilen verwenden müssen, um etwas zu sagen, das nur 3 dauern sollte.
Brian Dear
25
Wenn das Wort returndem Code eine semantische Bedeutung hinzufügt, warum nicht? Es ist kaum sehr ausführlich - wir sprechen nicht 5 Zeilen gegen 3, nur noch ein Wort. Ich würde es vorziehen, returnzumindest in Funktionen (möglicherweise nicht in Blöcken) zu sehen, um auszudrücken, was der Code tatsächlich tut. Manchmal müssen Sie die Mid-Funktion zurückgeben - lassen Sie das letzte returnSchlüsselwort in der letzten Zeile nicht aus, nur weil Sie können. Ich bevorzuge überhaupt keine Ausführlichkeit, aber ich bevorzuge Konsistenz.
mindplay.dk
6
Dies ist eine großartige Antwort. Es besteht jedoch weiterhin das Risiko, dass Sie eine Ruby-Methode schreiben, die eine Prozedur sein soll (auch als Funktionsrückgabeeinheit bezeichnet), z. B. side_effect()und ein anderer Entwickler beschließt, den impliziten Rückgabewert Ihrer Prozedur (ab) zu verwenden. Um dies zu beheben, können Sie Ihre Prozeduren explizit vornehmen return nil.
Alex Dean
66

Nein. Ein guter Ruby-Stil verwendet im Allgemeinen nur explizite Rückgaben für eine frühzeitige Rückgabe . Ruby legt großen Wert auf Code-Minimalismus / implizite Magie.

Das heißt, wenn eine explizite Rückgabe die Dinge klarer oder leichter lesbar machen würde, würde dies nichts schaden.

Ben Hughes
quelle
116
Was ist der Sinn des Code-Minimalismus, wenn er zu Verwirrungsmaximalismus führt?
Jononomo
@ JonCrowell Es sollte stattdessen zu Dokumentationsmaximalismus führen.
Jazzpi
29
@jazzpi Wenn Sie Ihren Code stark dokumentieren müssen, damit er verstanden wird, üben Sie keinen Minimalismus, sondern Code-Golf .
Schwern
2
Es ist nicht verwirrend, die endgültige Rückgabe auszuschließen, es ist verwirrend, sie einzuschließen - als Ruby-Entwickler hat sie returnbereits eine bedeutende semantische Bedeutung als EARLY EXIT für die Funktion.
Devon Parsons
14
@DevonParsons Wie um alles in der Welt könnte jemand einen returnin der letzten Zeile als EARLY EXIT verwechseln ?
DarthPaghius
31

Ich persönlich benutze das returnSchlüsselwort, um zwischen funktionalen Methoden zu unterscheiden , dh Methoden, die hauptsächlich wegen ihres Rückgabewerts ausgeführt werden, und prozeduralen Methoden , die hauptsächlich wegen ihrer Nebenwirkungen ausgeführt werden. Bei Methoden, bei denen der Rückgabewert wichtig ist, erhalten Sie ein zusätzliches returnSchlüsselwort, um auf den Rückgabewert aufmerksam zu machen.

Ich benutze die gleiche Unterscheidung beim Aufrufen von Methoden: Funktionsmethoden erhalten Klammern, prozedurale Methoden nicht.

Und zu guter Letzt verwende ich diese Unterscheidung auch bei Blöcken: Funktionsblöcke erhalten geschweifte Klammern, prozedurale Blöcke (dh Blöcke, die etwas "tun") erhalten do/ end.

Ich versuche jedoch, nicht religiös zu sein: Mit Blöcken, geschweiften Klammern und do/ oder endunterschiedlicher Priorität, und anstatt explizite Klammern hinzuzufügen, um einen Ausdruck eindeutig zu machen, wechsle ich einfach zum anderen Stil. Gleiches gilt für den Methodenaufruf: Wenn das Hinzufügen von Klammern um die Parameterliste den Code lesbarer macht, mache ich das auch dann, wenn die betreffende Methode prozeduraler Natur ist.

Jörg W Mittag
quelle
1
Im Moment lasse ich eine Rückgabe in einem "Einzeiler" aus, ähnlich wie Sie es tun, und ich mache alles andere gleich. Gut zu wissen, dass ich nicht ganz in meinem Stil bin. :)
Sasha Chedygov
@Jorg: Verwenden Sie frühe Rückgaben in Ihrem Code? Verursacht dies Verwirrung mit Ihrem Verfahrens- / Funktionsschema?
Andrew Grimm
1
@ Andrew Grimm: Ja und ja. Ich denke daran, es umzukehren. Die überwiegende Mehrheit meiner Methoden ist referenziell transparent / rein / funktional / wie auch immer Sie es nennen möchten, daher ist es sinnvoll, dort die kürzere Form zu verwenden. Und Methoden, die im funktionalen Stil geschrieben wurden, haben in der Regel keine frühen Ergebnisse, da dies den Begriff "Schritt" impliziert, der nicht sehr funktional ist. Obwohl ich keine Renditen in der Mitte mag. Ich benutze sie entweder am Anfang als Schutz oder am Ende, um den Kontrollfluss zu vereinfachen.
Jörg W Mittag
Tolle Antwort, aber es ist tatsächlich besser, prozedurale Methoden mit () und funktionale Methoden aufzurufen, ohne - siehe meine Antwort unten, warum
Alex Dean
13

Eigentlich ist es wichtig zu unterscheiden zwischen:

  1. Funktionen - Methoden, die für ihren Rückgabewert ausgeführt werden
  2. Prozeduren - Methoden, die wegen ihrer Nebenwirkungen ausgeführt werden

Ruby hat keine native Methode, um diese zu unterscheiden. Dies macht Sie anfällig für das Schreiben einer Prozedur side_effect()und einen anderen Entwickler, der beschließt, den impliziten Rückgabewert Ihrer Prozedur zu missbrauchen (im Grunde genommen als unreine Funktion zu behandeln).

Um dies zu beheben, nehmen Sie ein Blatt aus Scala und Haskells Buch und lassen Sie Ihre Verfahren explizit zurückgeben nil(auch bekannt als Unitoder ()in anderen Sprachen).

Wenn Sie dies befolgen, wird die Verwendung einer expliziten returnSyntax oder nicht nur eine Frage des persönlichen Stils.

Weitere Unterscheidung zwischen Funktionen und Verfahren:

  1. Kopieren Sie Jörg W Mittag's schöne Idee, Funktionsblöcke mit geschweiften Klammern und Verfahrensblöcke mit zu schreiben do/end
  2. Wenn Sie Prozeduren aufrufen, verwenden Sie (), während dies beim Aufrufen von Funktionen nicht der Fall ist

Beachten Sie, dass Jörg W Mittag tatsächlich den umgekehrten Fall befürwortet hat - ()s für Prozeduren zu vermeiden -, aber dies ist nicht ratsam, da Sie möchten, dass nebeneffektive Methodenaufrufe klar von Variablen unterschieden werden können, insbesondere wenn arity 0 ist. Siehe den Scala-Styleguide zum Methodenaufruf für Einzelheiten.

Alex Dean
quelle
Wenn ich den zurückgegebenen Wert von benötige function_aund function_azum Aufrufen geschrieben wurde (und nur durch Aufrufen arbeiten kann) procedure_b, ist das dann function_awirklich eine Funktion? Es gibt einen Wert zurück, der Ihre Anforderungen an Funktionen erfüllt, hat jedoch einen Nebeneffekt und erfüllt die Anforderungen von Prozeduren.
Devon Parsons
2
Sie haben Recht, Devon - function_akönnte einen Baum von procedure_...Anrufen enthalten, ebenso wie procedure_aeinen Baum von function_...Anrufen. Wie Scala, aber anders als Haskell kann in Ruby nicht garantiert werden, dass eine Funktion keine Nebenwirkungen hat.
Alex Dean
8

Der Styleguide besagt, dass Sie returnIhre letzte Anweisung nicht verwenden sollten. Sie können es weiterhin verwenden, wenn es nicht das letzte ist . Dies ist eine der Konventionen, die die Community strikt befolgt. Dies sollten Sie auch tun, wenn Sie vorhaben, mit anderen Personen zusammenzuarbeiten, die Ruby verwenden.


Abgesehen davon ist das Hauptargument für die Verwendung von expliziten returns, dass es für Leute aus anderen Sprachen verwirrend ist.

  • Erstens ist dies nicht nur Ruby vorbehalten. Zum Beispiel hat Perl auch implizite Rückgaben.
  • Zweitens kommt die Mehrheit der Menschen, für die dies gilt, aus algolischen Sprachen. Die meisten davon sind viel "niedriger" als Ruby, daher müssen Sie mehr Code schreiben, um etwas zu erledigen.

Eine gängige Heuristik für die Methodenlänge (ohne Getter / Setter) in Java ist ein Bildschirm . In diesem Fall wird die Methodendefinition möglicherweise nicht angezeigt und / oder Sie haben bereits vergessen, von wo Sie zurückgekehrt sind.

Auf der anderen Seite ist es in Ruby am besten, sich an Methoden zu halten, die weniger als 10 Zeilen lang sind . Angesichts dessen würde man sich fragen, warum er ~ 10% mehr Aussagen schreiben muss, wenn sie eindeutig impliziert sind.


Da Ruby keine ungültigen Methoden hat und alles so viel präziser ist, fügen Sie nur Overhead für keinen der Vorteile hinzu, wenn Sie explizite returns verwenden.

ndnenkov
quelle
1
"Dies ist eine der Konventionen, die die Community strikt befolgt." Ich habe die Erfahrung gemacht, dass die Leute dies nicht strikt befolgen, es sei denn, sie sind sehr erfahrene Ruby-Entwickler.
Tim Holt
1
@ TimHolt Ich muss damals sehr viel Glück gehabt haben. (:
ndnenkov
1
@ndn stimme voll und ganz zu. Wichtig ist, dass es eine gute Idee ist, zu überdenken, was Sie erreichen möchten, wenn eine Methode (oder Klasse) 5/100 Zeilen überschreitet. Wirklich Sandis Regeln sind ein kognitiver Rahmen für das Nachdenken über Code im Gegensatz zu einem willkürlichen Dogma. Der Großteil des Codes, auf den ich stoße, ist nicht problematisch, da er künstlich eingeschränkt ist - es ist im Allgemeinen das Gegenteil - Klassen mit mehreren Verantwortlichkeiten, Methoden, die länger sind als viele Klassen. Im Wesentlichen "springen viele Haie" - mischen Verantwortlichkeiten.
Brian Lieber
1
Manchmal ist Konvention immer noch eine schlechte Idee. Es ist eine Stilkonvention, die auf Magie beruht und es schwierig macht, Rückgabetypen zu erraten. bietet keinen anderen Vorteil als das Speichern von 7 Tastenanschlägen und ist mit fast allen anderen Sprachen inkongruent. Wenn Ruby jemals einen Konsolidierungs- und Vereinfachungsdurchlauf im Python 3-Stil durchführt, würde ich hoffen, dass implizit alles das erste auf dem Hackklotz ist.
Shayne
2
Außer klar, es macht die Dinge nicht einfacher zu lesen! Magie und Verständlichkeit sind nicht das Richtige.
Shayne
4

Ich stimme Ben Hughes zu und stimme Tim Holt nicht zu, da in der Frage die endgültige Vorgehensweise von Python erwähnt wird und gefragt wird, ob Ruby einen ähnlichen Standard hat.

Es tut.

Dies ist ein so bekanntes Merkmal der Sprache, dass jeder, von dem erwartet wird, dass er ein Problem in Ruby debuggt, vernünftigerweise davon erfahren sollte.

Devon Parsons
quelle
3
Ich stimme Ihnen theoretisch zu, aber in der Praxis machen Programmierer Fehler und bringen es durcheinander. In diesem Fall wissen wir alle, dass Sie "==" in einer Bedingung und nicht "=" verwenden, aber wir ALLE haben diesen Fehler schon einmal gemacht und ihn erst später erkannt.
Tim Holt
1
@ TimHolt Ich spreche keinen bestimmten Anwendungsfall an, sondern beantworte nur die Frage. Es ist ausdrücklich ein schlechter Stil, wie von der Community gewählt, das returnSchlüsselwort zu verwenden, wenn dies nicht erforderlich ist.
Devon Parsons
3
Meinetwegen. Ich sage, dass der Stil, den Sie erwähnen, zu Fehlern führen kann, wenn Sie nicht vorsichtig sind. Aufgrund meiner Erfahrung befürworte ich persönlich einen anderen Stil.
Tim Holt
1
@TimHolt Diese Frage wurde übrigens schon lange vor dem Schreiben des Styleguides gestellt, sodass die nicht übereinstimmenden Antworten nicht unbedingt falsch, sondern nur veraltet sind. Ich bin hier allerdings irgendwie gestolpert und habe den Link, den ich bereitgestellt habe, nicht gesehen, also dachte ich, jemand anderes könnte auch hier landen.
Devon Parsons
1
Es ist kein offizieller "Standard". Der von rubocop verwendete Ruby-Styleguide, der oben mit der Behauptung von Devon Parsons verknüpft ist, wurde von einem nicht zum Kern gehörenden Ruby-Hacker erstellt. Der Kommentar von Devon ist also faktisch einfach falsch. Natürlich können Sie es NOCH verwenden, aber es ist kein Standard.
Shevy
1

Es ist eine Frage des Stils, den Sie größtenteils bevorzugen. Sie müssen das Schlüsselwort return verwenden, wenn Sie von irgendwo in der Mitte einer Methode zurückkehren möchten.

Praveen Angyan
quelle
0

Wie Ben sagte. Die Tatsache, dass 'der Rückgabewert einer Ruby-Methode der Rückgabewert der letzten Anweisung im Funktionskörper ist', führt dazu, dass das Schlüsselwort return in den meisten Ruby-Methoden selten verwendet wird.

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
quelle
4
Sie könnten auch "return nil if failed_some_early_check" schreiben, wenn Ihre Absicht noch klar ist
Mike Woodhouse
@Gishu Ich habe mich tatsächlich über die if-Anweisung beschwert, nicht über den Methodennamen.
Andrew Grimm
@ Andrew .. oh das fehlende Ende :). Verstanden. behoben, um auch den anderen Kommentar von Mike zu beruhigen.
Gishu
Warum nicht if failed_check then nil else @list end? Das ist wirklich funktional .
cdunn2001
@ cdunn2001 Nicht klar, warum, wenn dann sonst funktionsfähig ist. Der Grund für diesen Stil ist, dass er die Verschachtelung über frühe Exits reduziert. IMHO auch besser lesbar.
Gishu