do..end vs lockige Klammern für Blöcke in Ruby

154

Ich habe einen Kollegen, der aktiv versucht, mich davon zu überzeugen, dass ich do..end nicht verwenden und stattdessen geschweifte Klammern zum Definieren mehrzeiliger Blöcke in Ruby verwenden sollte.

Ich bin fest im Lager, nur geschweifte Klammern für kurze Einzeiler zu verwenden und für alles andere zu tun. Aber ich dachte, ich würde mich an die größere Gemeinschaft wenden, um eine Lösung zu finden.

Also was ist es und warum? (Beispiel eines Sollte-Codes)

context do
  setup { do_some_setup() }
  should "do somthing" do
    # some more code...
  end
end

oder

context {
  setup { do_some_setup() }
  should("do somthing") {
    # some more code...
  }
}

Persönlich beantwortet ein Blick auf das Obige die Frage für mich, aber ich wollte dies für die größere Gemeinschaft öffnen.

Blake Taylor
quelle
3
Ich frage mich nur, aber was sind die Argumente Ihrer Mitarbeiter für die Verwendung von Zahnspangen? Scheint eher eine persönliche Präferenz als eine logische Sache zu sein.
Daniel Lee
6
Wenn Sie eine Diskussion wünschen, machen Sie es zu einem Community-Wiki. Auf Ihre Frage: Es ist nur persönlicher Stil. Ich bevorzuge die geschweiften Klammern, da sie nerdiger aussehen.
Halfdan
7
Es ist keine Präferenzsache, es gibt syntaktische Gründe für die Verwendung übereinander neben stilistischen Gründen. Einige der Antworten erklären, warum. Wenn Sie nicht das richtige verwenden, kann dies zu sehr subtilen Fehlern führen, die schwer zu finden sind, wenn eine andere "stilistische" Entscheidung getroffen wird, niemals umschließende Klammern für Methoden zu verwenden.
der Blechmann
1
"Randbedingungen" haben die schlechte Angewohnheit, sich an Leute zu schleichen, die nichts über sie wissen. Defensives Codieren bedeutet eine Menge Dinge, einschließlich der Entscheidung, Codierungsstile zu verwenden, die die Wahrscheinlichkeit minimieren, jemals in die Fälle zu geraten. DU magst es dir bewusst sein, aber der Typ, den zwei Leute nach dir haben, ist vielleicht nicht, nachdem das Stammeswissen vergessen wurde. Dies tritt leider häufig in Unternehmensumgebungen auf.
der Blechmann
1
@tinman Diese Arten von Randbedingungen werden von TDD behandelt. Aus diesem Grund kann Rubist Code wie diesen schreiben und eine volle Nachtruhe erhalten, da solche Fehler in ihrem Code nicht vorhanden sind. Bei der Entwicklung mit TDD oder BDD und einem solchen Vorrangfehler erinnert uns das auf dem Bildschirm angezeigte Rot an das "Stammeswissen". Normalerweise besteht die Lösung darin, irgendwo ein paar Parens hinzuzufügen, was innerhalb der Standardkonventionen völlig akzeptabel ist. :)
Blake Taylor

Antworten:

246

Die allgemeine Konvention besteht darin, do..end für mehrzeilige Blöcke und geschweifte Klammern für einzeilige Blöcke zu verwenden. Es gibt jedoch auch einen Unterschied zwischen den beiden, der anhand dieses Beispiels veranschaulicht werden kann:

puts [1,2,3].map{ |k| k+1 }
2
3
4
=> nil
puts [1,2,3].map do |k| k+1; end
#<Enumerator:0x0000010a06d140>
=> nil

Dies bedeutet, dass {} eine höhere Priorität hat als do..end. Denken Sie also daran, wenn Sie entscheiden, was Sie verwenden möchten.

PS: Ein weiteres Beispiel, das Sie berücksichtigen sollten, wenn Sie Ihre Vorlieben entwickeln.

Der folgende Code:

task :rake => pre_rake_task do
  something
end

bedeutet wirklich:

task(:rake => pre_rake_task){ something }

Und dieser Code:

task :rake => pre_rake_task {
  something
}

bedeutet wirklich:

task :rake => (pre_rake_task { something })

Um die gewünschte Definition mit geschweiften Klammern zu erhalten, müssen Sie Folgendes tun:

task(:rake => pre_rake_task) {
  something
}

Vielleicht möchten Sie sowieso Klammern für Parameter verwenden, aber wenn Sie dies nicht tun, ist es wahrscheinlich am besten, do..end in diesen Fällen zu verwenden, um diese Verwirrung zu vermeiden.

Pan Thomakos
quelle
44
Ich stimme dafür, immer Klammern um Methodenparameter zu verwenden, um das ganze Problem zu umgehen.
der Blechmann
@the Tin Man: Du verwendest sogar Klammern in Rake?
Andrew Grimm
9
Es ist eine alte Programmiergewohnheit für mich. Wenn eine Sprache Klammern unterstützt, verwende ich sie.
der Blechmann
8
+1 für den Hinweis auf das Vorrangproblem. Ich bekomme manchmal unerwartete Ausnahmen, wenn ich versuche, do zu konvertieren. Ende bis {}
idrinkpabst
1
Warum puts [1,2,3].map do |k| k+1; enddruckt nur der Enumerator dh eine Sache. Wird hier nicht zwei Argumente übergeben, nämlich [1,2,3].mapund do |k| k+1; end?
Ben
55

Aus der Programmierung von Ruby :

Zahnspangen haben einen hohen Stellenwert. do hat eine niedrige Priorität. Wenn der Methodenaufruf Parameter enthält, die nicht in Klammern stehen, wird die Klammerform eines Blocks an den letzten Parameter und nicht an den Gesamtaufruf gebunden. Das do-Formular wird an den Aufruf gebunden.

Also der Code

f param {do_something()}

Bindet den Block paramwährend des Codes an die Variable

f param do do_something() end

Bindet den Block an die Funktion f.

Dies ist jedoch kein Problem, wenn Sie Funktionsargumente in Klammern setzen.

David Brown
quelle
7
+1 "Dies ist jedoch kein Problem, wenn Sie Funktionsargumente in Klammern setzen." Ich mache das aus Gewohnheit, anstatt mich auf den Zufall und die Laune des Dolmetschers zu verlassen. :-)
der Blechmann
4
@theTinMan, wenn Ihr Interpreter den Zufall in seinem Parser verwendet, benötigen Sie einen neuen Interpreter.
Paul Draper
@PaulDraper - in der Tat, aber nicht alle Sprachen sind sich in diesem Punkt einig. Aber (ich denke) es ist ziemlich selten, dass Parens ihre Arbeit nicht "erledigen". Wenn Sie also Parens verwenden, müssen Sie sich nicht an die "zufälligen" Entscheidungen von Sprachdesignern erinnern - selbst wenn sie von den Implementierern von Compilern und Interpreten treu ausgeführt werden .
dlu
15

Es gibt ein paar Gesichtspunkte dazu, es ist wirklich eine Frage der persönlichen Präferenz. Viele Rubinisten gehen so vor, wie Sie es tun. Zwei andere übliche Stile sind jedoch, immer den einen oder anderen zu verwenden oder {}für Blöcke, die Werte zurückgeben, und do ... endfür Blöcke, die für Nebenwirkungen ausgeführt werden.

GSto
quelle
2
Es ist nicht nur eine Frage der persönlichen Präferenz. Das Binden von Parametern kann zu subtilen Fehlern führen, wenn Methodenparameter nicht in Klammern angegeben sind.
der Blechmann
1
Ich mache derzeit nicht die letzte Option, aber ich erwähne, dass einige Leute diesen Ansatz verfolgen.
Andrew Grimm
Avdi Grimm befürwortet dies in einem Blog-Beitrag: devblog.avdi.org/2011/07/26/…
alxndr
8

Geschweifte Klammern haben einen großen Vorteil: Viele Editoren haben eine VIEL einfachere Möglichkeit, sie abzugleichen, was bestimmte Arten des Debuggens erheblich vereinfacht. In der Zwischenzeit ist das Schlüsselwort "do ... end" etwas schwieriger zu finden, zumal "end" auch mit "if" übereinstimmen.

GlyphGryph
quelle
RubyMine zum Beispiel, markieren Sie do ... endSchlüsselwörter standardmäßig
ck3g
1
Obwohl ich geschweifte Klammern bevorzuge, sehe ich keinen Grund, warum ein Editor Probleme haben würde, eine 2/3-Zeichenfolge gegenüber einem einzelnen Zeichen zu finden. Es gibt jedoch das Argument, dass die meisten Editoren bereits mit geschweiften Klammern übereinstimmen, während dies do ... endein Sonderfall ist, der eine sprachspezifische Logik erfordert.
Chinoto Vokro
7

Die häufigste Regel, die ich gesehen habe (zuletzt in Eloquent Ruby ), lautet:

  • Wenn es sich um einen mehrzeiligen Block handelt, verwenden Sie do / end
  • Wenn es sich um einen einzeiligen Block handelt, verwenden Sie {}
Peter Brown
quelle
7

Ich stimme für do / end


Die Konvention gilt do .. endfür mehrzeilige und { ... }einzeilige.

Aber ich mag es do .. endbesser, also wenn ich einen Einzeiler habe, benutze ich ihn do .. endtrotzdem, aber formatiere ihn wie gewohnt für do / end in drei Zeilen. Das macht alle glücklich.

  10.times do 
    puts ...
  end

Ein Problem dabei { }ist, dass es dem Poetry-Modus feindlich gegenübersteht (weil sie eng an den letzten Parameter und nicht an den gesamten Methodenaufruf gebunden sind, also müssen Sie Methoden-Parens einschließen) und meiner Meinung nach einfach nicht so gut aussehen. Sie sind keine Anweisungsgruppen und stoßen aus Gründen der Lesbarkeit mit Hash-Konstanten zusammen.

Außerdem habe ich genug von { }C-Programmen gesehen. Rubys Weg ist wie immer besser. Es gibt genau einen ifBlocktyp, und Sie müssen niemals zurückgehen und eine Anweisung in eine zusammengesetzte Anweisung konvertieren.

DigitalRoss
quelle
2
"Ich habe genug von {} in C-Programmen gesehen" ... und Perl. Und +1 für mehrere Aussagen und für die Befürwortung von Rubys zenartigen Codierungsstilen. :-)
der Blechmann
1
Genau genommen gibt es eine andere "if" -Syntax - das Postfix "if" ( do_stuff if x==1), dessen Konvertierung in die reguläre Syntax etwas ärgerlich ist. Aber das ist eine andere Diskussion.
Kelvin
Es gibt Präfix if, Postfix if, Postfix unless(auch eine Art if, ein Wenn-nicht) und eine Zeile ifmit then. Der Ruby-Weg besteht darin, viele Möglichkeiten zu haben, um tatsächlich etwas auszudrücken, das immer dasselbe ist, viel schlimmer als das, was C anbietet, das sehr einfachen, strengen Regeln folgt.
Mecki
2
Wenn uns das {} in C gefällt, heißt das, dass wir es auch in Ruby verwenden sollten?
Warren Dew
6

Einige einflussreiche Rubyisten schlagen vor, geschweifte Klammern zu verwenden, wenn Sie den Rückgabewert verwenden, und do / end, wenn Sie dies nicht tun.

http://talklikeaduck.denhaven2.com/2007/10/02/ruby-blocks-do-or-brace (auf archive.org)

http://onestepback.org/index.cgi/Tech/Ruby/BraceVsDoEnd.rdoc (auf archive.org)

Dies scheint im Allgemeinen eine gute Praxis zu sein.

Ich würde dieses Prinzip ein wenig ändern, um zu sagen, dass Sie die Verwendung von do / end in einer einzelnen Zeile vermeiden sollten, da es schwieriger zu lesen ist.

Sie müssen bei der Verwendung von geschweiften Klammern vorsichtiger sein, da diese anstelle des gesamten Methodenaufrufs an den endgültigen Parameter einer Methode gebunden werden. Fügen Sie einfach Klammern hinzu, um dies zu vermeiden.

Kelvin
quelle
2

Eigentlich ist es eine persönliche Präferenz, aber nachdem ich gesagt habe, dass ich in den letzten 3 Jahren meiner Rubinerfahrungen gelernt habe, dass Rubin seinen Stil hat.

Ein Beispiel wäre, wenn Sie aus einem JAVA-Hintergrund kommen, eine boolesche Methode, die Sie möglicherweise verwenden

def isExpired
  #some code
end 

Beachten Sie den Kamelfall und meistens das Präfix 'is', um ihn als boolesche Methode zu identifizieren.

Aber in der Rubinwelt wäre die gleiche Methode

def expired?
  #code
end

Ich persönlich denke, es ist besser, mit 'Ruby Way' zu arbeiten (aber ich weiß, dass es einige Zeit dauert, bis man es versteht (es hat ungefähr 1 Jahr gedauert: D)).

Schließlich würde ich mit gehen

do 
  #code
end

Blöcke.

sameera207
quelle
2

Ich habe eine andere Antwort gegeben, obwohl bereits auf den großen Unterschied hingewiesen wurde (Vorrang / Bindung), und dies kann zu schwer zu findenden Problemen führen (der Blechmann und andere haben darauf hingewiesen). Ich denke, mein Beispiel zeigt das Problem mit einem nicht so üblichen Code-Snippet, selbst erfahrene Programmierer lesen nicht wie am Sonntag:

module I18n
    extend Module.new {
        old_translate=I18n.method(:translate)
        define_method(:translate) do |*args|
            InplaceTrans.translate(old_translate, *args)
        end
        alias :t :translate
    }
end

module InplaceTrans
    extend Module.new {
        def translate(old_translate, *args)
            Translator.new.translate(old_translate, *args)
        end
    }
end

Dann habe ich Code verschönert ...

#this code is wrong!
#just made it 'better looking'
module I18n
    extend Module.new do
        old_translate=I18n.method(:translate)
        define_method(:translate) do |*args|
            InplaceTrans.translate(old_translate, *args)
        end
        alias :t :translate
    end
end

Wenn Sie das {}hier in ändern , do/endwird der Fehler angezeigt, diese Methode translateexistiert nicht ...

Warum dies geschieht, wird hier mehr als einmal hervorgehoben. Aber wo soll man hier Zahnspangen anbringen? (@the Tin Man: Ich benutze immer Zahnspangen, wie Sie, aber hier ... beaufsichtigt)

so mag jede antwort

If it's a multi-line block, use do/end
If it's a single line block, use {}

ist einfach falsch, wenn es ohne "ABER Behalte Klammern / Vorrang!"

nochmal:

extend Module.new {} evolves to extend(Module.new {})

und

extend Module.new do/end evolves to extend(Module.new) do/end

(Was auch immer das Ergebnis von Extend mit dem Block macht ...)

Wenn Sie also do / end verwenden möchten, verwenden Sie Folgendes:

#this code is ok!
#just made it 'better looking'?
module I18n
    extend(Module.new do 
        old_translate=I18n.method(:translate)
        define_method(:translate) do |*args|
            InplaceTrans.translate(old_translate, *args)
        end
        alias :t :translate
    end)
end
Halbbit
quelle
1

Aufgrund persönlicher Vorurteile bevorzuge ich geschweifte Klammern gegenüber einem Do / End-Block, da dies für eine höhere Anzahl von Entwicklern verständlicher ist, da die meisten Hintergrundsprachen sie gegenüber der Do / End-Konvention verwenden. Wenn dies gesagt wird, besteht der eigentliche Schlüssel darin, eine Einigung in Ihrem Shop zu erzielen. Wenn do / end von 6/10 Entwicklern verwendet wird, als JEDER sie verwenden sollte. Wenn 6/10 geschweifte Klammern verwenden, halten Sie sich an dieses Paradigma.

Es geht darum, ein Muster zu erstellen, damit das gesamte Team die Codestrukturen schneller identifizieren kann.

Jake Kalstad
quelle
3
Es ist mehr als Präferenz. Die Bindung zwischen den beiden ist unterschiedlich und kann Probleme verursachen, die schwer zu diagnostizieren sind. Einige der Antworten beschreiben das Problem detailliert.
der Tin Man
4
In Bezug auf Menschen mit anderen Sprachhintergründen - ich denke, der Unterschied in der "Verständlichkeit" ist in diesem Fall so gering, dass keine Berücksichtigung erforderlich ist. (Das war übrigens nicht meine Ablehnung.)
Kelvin
1

Es gibt einen subtilen Unterschied zwischen ihnen, aber {} bindet enger als do / end.

Shankar Raju
quelle
0

Mein persönlicher Stil ist es, die Lesbarkeit gegenüber starren Regeln der {... }gegen do... endWahl zu betonen , wenn eine solche Wahl möglich ist. Meine Vorstellung von Lesbarkeit lautet wie folgt:

[ 1, 2, 3 ].map { |e| e + 1 }      # preferred
[ 1, 2, 3 ].map do |e| e + 1 end   # acceptable

[ 1, 2, 3 ].each_with_object [] do |e, o| o << e + 1 end # preferred, reads like a sentence
[ 1, 2, 3 ].each_with_object( [] ) { |e, o| o << e + 1 } # parens make it less readable

Foo = Module.new do     # preferred for a multiline block, other things being equal
  include Comparable
end

Foo = Module.new {      # less preferred
  include Comparable
}

Foo = Module.new { include Comparable }      # preferred for a oneliner
Foo = module.new do include Comparable end   # imo less readable for a oneliner

[ [ 1 ], [ 1, 2 ] ].map { |e| e.map do |e| e + 1 end }  # slightly better
[ [ 1 ], [ 1, 2 ] ].map { |e| e.map { |e| e + 1 } }     # slightly worse

In komplexeren Syntaxen, wie z. B. mehrzeiligen verschachtelten Blöcken, versuche ich, {... }und do... endTrennzeichen für das natürlichste Ergebnis zu verteilen , z.

Foo = Module.new { 
  if true then
    Bar = Module.new {                          # I intersperse {} and keyword delimiters
      def quux
        "quux".tap do |string|                  # I choose not to intersperse here, because
          puts "(#{string.size} characters)"    # for multiline tap, do ... end in this
        end                                     # case still loks more readable to me.
      end
    }
  end
}

Obwohl das Fehlen starrer Regeln für verschiedene Programmierer zu leicht unterschiedlichen Auswahlmöglichkeiten führen kann, glaube ich, dass die Optimierung der Lesbarkeit von Fall zu Fall, obwohl subjektiv, ein Nettogewinn gegenüber der Einhaltung starrer Regeln ist.

Boris Stitnicky
quelle
1
Wenn ich aus Python komme, sollte ich vielleicht Ruby überspringen und stattdessen Wisp lernen.
Cees Timmerman
Früher glaubte ich, es Rubysei die beste pragmatische Sprache da draußen. Soll ich Skriptsprache sagen. Heute denke ich, dass Sie genauso gut lernen können Brainfuck.
Boris Stitnicky
0

Ich habe hier ein Beispiel für die am häufigsten gewählte Antwort genommen. Sagen,

[1,2,3].map do 
  something 
end

Wenn Sie überprüfen, welche interne Implementierung in array.rb vorhanden ist, lautet der Header der Map-Methode :

Invokes the given block once for each element of self.

def map(*several_variants)
    (yield to_enum.next).to_enum.to_a
end

dh es akzeptiert einen Codeblock - was auch immer zwischen do und end liegt, wird als Yield ausgeführt. Dann wird das Ergebnis wieder als Array gesammelt, sodass ein völlig neues Objekt zurückgegeben wird.

Wenn Sie also auf einen Do-End- Block oder {} stoßen , haben Sie einfach eine Mind Map, dass der Codeblock als Parameter übergeben wird, der intern ausgeführt wird.

Vasanth Saminathan
quelle
-3

Es gibt eine dritte Option: Schreiben Sie einen Präprozessor, um aus der Einrückung auf "Ende" in einer eigenen Zeile zu schließen. Die tiefen Denker, die prägnanten Code bevorzugen, haben zufällig Recht.

Besser noch, Hack Ruby, das ist eine Flagge.

Natürlich besteht die einfachste Lösung für "Pick your Fights" darin, die Stilkonvention zu übernehmen, dass eine Folge von Enden alle in derselben Zeile erscheint, und Ihre Syntaxfarbe beizubringen, um sie stummzuschalten. Zur Erleichterung der Bearbeitung könnten Editor-Skripte verwendet werden, um diese Sequenzen zu erweitern / zu reduzieren.

20% bis 25% meiner Ruby-Codezeilen sind "Ende" in ihrer eigenen Zeile, was durch meine Einrückungskonventionen trivial abgeleitet wird. Ruby ist die lispelartige Sprache, die den größten Erfolg erzielt hat. Wenn Leute dies bestreiten und fragen, wo all die schrecklichen Klammern sind, zeige ich ihnen eine Rubinfunktion, die in sieben Zeilen überflüssigen "Endes" endet.

Ich habe jahrelang mit einem Lisp-Präprozessor codiert, um auf die meisten Klammern zu schließen: Ein Balken '|' öffnete eine Gruppe, die am Ende der Zeile automatisch geschlossen wurde, und ein Dollarzeichen '$' diente als leerer Platzhalter, an dem sonst keine Symbole vorhanden wären, um auf die Gruppierungen schließen zu können. Dies ist natürlich religiöses Kriegsgebiet. Lisp / Schema ohne Klammern ist die poetischste aller Sprachen. Es ist jedoch einfacher, Klammern mithilfe der Syntaxfarbe einfach zu beruhigen.

Ich codiere immer noch mit einem Präprozessor für Haskell, um Heredocs hinzuzufügen und alle bündigen Zeilen als Kommentare festzulegen, alles wird als Code eingerückt. Ich mag keine Kommentarzeichen, egal in welcher Sprache.

Syzygies
quelle