do..end vs nawiasy klamrowe dla bloków w Rubim

154

Mam współpracownika, który aktywnie próbuje mnie przekonać, że nie powinienem używać do..end, a zamiast tego używać nawiasów klamrowych do definiowania bloków wielowierszowych w Rubim.

Jestem mocno w obozie, że używam kręconych szelek tylko do krótkich jednolinijkowych i kończę na wszystkim innym. Ale pomyślałem, że dotrę do większej społeczności, aby uzyskać jakieś rozwiązanie.

Więc co to jest i dlaczego? (Przykład jakiegoś kodu powinnya)

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

lub

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

Osobiście samo spojrzenie na powyższe daje mi odpowiedź na pytanie, ale chciałem otworzyć to dla większej społeczności.

Blake Taylor
źródło
3
Zastanawiasz się, ale jakie są argumenty twoich współpracowników za używaniem nawiasów klamrowych? Wydaje się bardziej osobistą preferencją niż logiką.
Daniel Lee,
6
Jeśli chcesz dyskusji, zrób z niej wiki społeczności. Na twoje pytanie: to tylko osobisty styl. Wolę kręcone szelki, ponieważ wyglądają bardziej nerdowo.
halfdan
7
Nie jest to kwestia preferencji, istnieją również przyczyny składniowe, aby używać jednego nad drugim, oprócz powodów stylistycznych. Kilka odpowiedzi wyjaśnia dlaczego. Niewłaściwe użycie może spowodować bardzo subtelne błędy, które są trudne do znalezienia, jeśli wybierze się inny „stylistyczny”, aby nigdy nie używać nawiasów zawijających dla metod.
Tin Man,
1
„Warunki skrajne” mają zły nawyk podkradania się do ludzi, którzy o nich nie wiedzą. Kodowanie defensywne oznacza wiele rzeczy, w tym decydowanie się na użycie stylów kodowania, które minimalizują prawdopodobieństwo natknięcia się na przypadki. MOŻESZ być świadomy, ale ten facet, dwie osoby po tobie, może nie być po tym, jak zapomniano o wiedzy plemiennej. Niestety zdarza się to zwykle w środowiskach korporacyjnych.
Tin Man
1
@tinman Te typy warunków brzegowych są obsługiwane przez TDD. Dlatego Rubist może napisać taki kod i odpocząć przez całą noc, wiedząc, że w ich kodzie nie ma takich błędów. Podczas programowania z TDD lub BDD i popełnienia takiego błędu w pierwszeństwie, czerwony pokazany na ekranie przypomina nam o „wiedzy plemiennej”. Zwykle rozwiązaniem jest dodanie gdzieś kilku parenów, co jest całkowicie akceptowalne w ramach standardowych konwencji. :)
Blake Taylor

Odpowiedzi:

246

Ogólną konwencją jest użycie do..end dla bloków wieloliniowych i nawiasów klamrowych dla bloków jednowierszowych, ale istnieje również różnica między nimi, którą można zilustrować na tym przykładzie:

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

Oznacza to, że {} ma wyższy priorytet niż do..end, więc miej to na uwadze, decydując, czego chcesz użyć.

PS: Jeszcze jeden przykład, o którym należy pamiętać, rozwijając swoje preferencje.

Poniższy kod:

task :rake => pre_rake_task do
  something
end

naprawdę oznacza:

task(:rake => pre_rake_task){ something }

A ten kod:

task :rake => pre_rake_task {
  something
}

naprawdę oznacza:

task :rake => (pre_rake_task { something })

Aby uzyskać właściwą definicję, używając nawiasów klamrowych, musisz zrobić:

task(:rake => pre_rake_task) {
  something
}

Być może używanie nawiasów klamrowych jako parametrów jest czymś, co i tak chcesz zrobić, ale jeśli nie, prawdopodobnie najlepiej jest użyć do..end w takich przypadkach, aby uniknąć tego zamieszania.

Pan Thomakos
źródło
44
Głosuję za zawsze używaniem nawiasów wokół parametrów metody, aby ominąć cały problem.
Tin Man,
@the Tin Man: Używasz nawet nawiasów w Rake?
Andrew Grimm,
9
To dla mnie stary nawyk programowania. Jeśli język obsługuje nawiasy, używam ich.
Tin Man,
8
+1 za zwrócenie uwagi na kwestię pierwszeństwa. Czasami pojawiają się nieoczekiwane wyjątki, kiedy próbuję przekonwertować; koniec do {}
idrinkpabst
1
Dlaczego puts [1,2,3].map do |k| k+1; endwypisuje tylko moduł wyliczający, czyli jedno. Czy nie przekazuje tutaj dwóch argumentów, a mianowicie [1,2,3].mapi do |k| k+1; end?
ben
55

Od programowania Ruby :

Szelki mają wysoki priorytet; do ma niski priorytet. Jeśli wywołanie metody ma parametry, które nie są ujęte w nawiasy, forma nawiasu klamrowego bloku zostanie powiązana z ostatnim parametrem, a nie z ogólnym wywołaniem. Formularz do zostanie powiązany z wywołaniem.

Więc kod

f param {do_something()}

Wiąże blok ze paramzmienną, podczas gdy kod

f param do do_something() end

Wiąże blok z funkcją f.

Jednak nie stanowi to problemu, jeśli argumenty funkcji zostaną umieszczone w nawiasach.

David Brown
źródło
7
+1 „Jednak nie stanowi to problemu, jeśli argumenty funkcji zostaną umieszczone w nawiasach”. Robię to z przyzwyczajenia, zamiast polegać na przypadku i kaprysach tłumacza. :-)
Tin Man
4
@theTinMan, jeśli twój interpreter wykorzystuje szansę w swoim parserze, potrzebujesz nowego interpretera.
Paul Draper
@PaulDraper - rzeczywiście, ale nie wszystkie języki zgadzają się w tej kwestii. Ale (myślę) rzadko zdarza się, aby paren „nie wykonywał swojej pracy”, więc uniwersalne używanie paren pozwala uniknąć konieczności zapamiętywania „przypadkowych” decyzji podjętych przez projektantów języka - nawet jeśli są one wiernie wykonywane przez implementatorów kompilatorów i interpreterów .
dlu
15

Jest kilka punktów widzenia na ten temat, to naprawdę kwestia osobistych preferencji. Wielu rubinów przyjmuje twoje podejście. Jednak dwa inne wspólne style to zawsze używanie jednego lub drugiego lub używanie {}do bloków zwracających wartości oraz do ... enddo bloków wykonywanych z powodu efektów ubocznych.

GSto
źródło
2
Nie jest to tylko kwestia osobistych preferencji. Wiązanie parametrów może powodować subtelne błędy, jeśli parametry metody nie są zawarte w nawiasach.
Tin Man,
1
Obecnie nie robię ostatniej opcji, ale daję +1 za wspomnienie, że niektórzy ludzie stosują takie podejście.
Andrew Grimm,
Avdi Grimm opowiada się za tym w poście na blogu: devblog.avdi.org/2011/07/26/…
alxndr
8

Jest jedna główna korzyść z nawiasów klamrowych - wielu redaktorów ma DUŻO łatwiejszy czas na ich dopasowanie, co znacznie ułatwia niektóre typy debugowania. Tymczasem słowo kluczowe „do… end” jest nieco trudniejsze do dopasowania, zwłaszcza że „end” również pasuje do „if” s.

GlyphGryph
źródło
Na przykład RubyMine do ... enddomyślnie zaznacza słowa kluczowe
ck3g,
1
Chociaż wolę nawiasy klamrowe, nie widzę powodu, dla którego redaktor miałby problemy ze znalezieniem ciągu 2/3 znaków zamiast pojedynczego znaku. Chociaż istnieje argument, że większość redaktorów już dopasowuje nawiasy klamrowe, podczas gdy do ... endjest to specjalny przypadek, który wymaga logiki specyficznej dla języka.
Chinoto Vokro,
7

Najczęstszą regułą, jaką widziałem (ostatnio w Eloquent Ruby ) jest:

  • Jeśli jest to blok wieloliniowy, użyj do / end
  • Jeśli jest to blok jednowierszowy, użyj {}
Peter Brown
źródło
7

Głosuję na zakończenie / zakończenie


Konwencja dotyczy linii do .. endwielowierszowych i { ... }jednoliniowych.

Ale lubię do .. endbardziej, więc kiedy mam jedną linijkę, i do .. endtak używam, ale formatuję ją jak zwykle, aby wykonać / zakończyć w trzech wierszach. To sprawia, że ​​wszyscy są szczęśliwi.

  10.times do 
    puts ...
  end

Jednym z problemów { }jest to, że jest on wrogo nastawiony do trybu poezji (ponieważ wiążą się ściśle z ostatnim parametrem, a nie z całym wywołaniem metody, więc musisz uwzględnić parametry metody) i po prostu, moim zdaniem, nie wyglądają tak ładnie. Nie są grupami instrukcji i zderzają się ze stałymi hash dla czytelności.

Poza tym widziałem wystarczająco dużo { }w programach C. Sposób Rubiego, jak zwykle, jest lepszy. Jest dokładnie jeden typ ifbloku i nigdy nie musisz cofać się i konwertować instrukcji na instrukcję złożoną.

DigitalRoss
źródło
2
„Widziałem dość {} w programach C” ... i Perl. I +1 za kilka stwierdzeń i za popieranie stylów kodowania w stylu zen w języku Ruby. :-)
Tin Man
1
Ściśle mówiąc, istnieje inna składnia „if” - przyrostek „if” ( do_stuff if x==1), który jest trochę denerwujący przy konwersji do zwykłej składni. Ale to inna dyskusja.
Kelvin
Istnieje prefiks if, postfiks if, postfiks unless(także rodzaj if, jeśli nie) i jedna linia ifz then. Droga Rubiego polega na posiadaniu wielu sposobów wyrażenia czegoś, co w rzeczywistości jest zawsze to samo, znacznie gorsze niż to, co oferuje C, które podlega bardzo prostym, ścisłym regułom.
Mecki,
2
Więc jeśli podoba nam się {} w C, to znaczy, że powinniśmy ich używać także w Rubim?
Warren Dew
6

Kilku wpływowych rubistów sugeruje używanie nawiasów klamrowych, gdy używasz wartości zwracanej, i robienie / kończenie, gdy tego nie robisz.

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

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

Ogólnie wydaje się to dobrą praktyką.

Zmodyfikowałbym nieco tę zasadę, aby powiedzieć, że powinieneś unikać używania do / end w pojedynczym wierszu, ponieważ jest trudniejszy do odczytania.

Musisz być bardziej ostrożny używając nawiasów klamrowych, ponieważ będzie on wiązał się z końcowym parametrem metody zamiast z całym wywołaniem metody. Po prostu dodaj nawiasy, aby tego uniknąć.

kelwin
źródło
2

Właściwie to osobiste preferencje, ale powiedziawszy to, przez ostatnie 3 lata moich doświadczeń z rubinem nauczyłem się, że rubin ma swój styl.

Jednym z przykładów może być metoda logiczna, której możesz użyć, jeśli wychodzisz z tła JAVA

def isExpired
  #some code
end 

zwróć uwagę na przypadek wielbłąda i najczęściej przedrostek „jest”, aby zidentyfikować go jako metodę boolowską.

Ale w świecie rubinów byłaby ta sama metoda

def expired?
  #code
end

więc osobiście uważam, że lepiej jest pójść na „rubinową drogę” (ale wiem, że zrozumienie zajmuje mi trochę czasu (zajęło mi to około 1 roku: D)).

Wreszcie pójdę z

do 
  #code
end

Bloki.

sameera207
źródło
2

Umieściłem kolejną odpowiedź, chociaż wskazano już dużą różnicę (pierwszeństwo / wiązanie), a to może powodować trudne do znalezienia problemy (zwrócił na to uwagę Blaszany Człowiek i inni). Myślę, że mój przykład pokazuje problem z niezbyt zwykłym fragmentem kodu, nawet doświadczeni programiści nie czytają jak w niedzielę:

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

Potem zrobiłem kod upiększający ...

#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

jeśli zmienisz {}tutaj na do/endto otrzymasz błąd, ta metoda translatenie istnieje ...

Dlaczego tak się dzieje, wskazuje się tutaj więcej niż jeden - pierwszeństwo. Ale gdzie tu założyć aparat? (@ The Tin Man: zawsze używam szelek, tak jak ty, ale tutaj ... przeoczony)

więc każda odpowiedź jak

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

jest po prostu błędne, jeśli jest używane bez „ALE Miej oko na nawiasy klamrowe / pierwszeństwo!”

jeszcze raz:

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

i

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

(co kiedykolwiek wynik rozszerzenia robi z blokiem ...)

Więc jeśli chcesz użyć do / end, użyj tego:

#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
półbito
źródło
1

Sprowadza się do osobistych uprzedzeń, wolę nawiasy klamrowe zamiast bloku do / końca, ponieważ jest on bardziej zrozumiały dla większej liczby programistów, ponieważ większość języków tła używa ich zamiast konwencji do / end. Mając to na uwadze, prawdziwym kluczem jest osiągnięcie porozumienia w twoim sklepie, jeśli do / end jest używane przez 6/10 programistów, to WSZYSCY powinni ich używać, jeśli 6/10 używa nawiasów klamrowych, trzymaj się tego paradygmatu.

Chodzi o stworzenie wzorca, aby zespół jako całość mógł szybciej identyfikować struktury kodu.

Jake Kalstad
źródło
3
To więcej niż preferencja. Wiązanie między nimi jest inne i może powodować problemy trudne do zdiagnozowania. Kilka odpowiedzi szczegółowo przedstawia problem.
Tin Man,
4
Jeśli chodzi o osoby o innym pochodzeniu językowym - myślę, że różnica w „zrozumiałości” jest w tym przypadku tak mała, że ​​nie wymaga rozważenia. (To nie był mój głos przeciwny przy okazji.)
Kelvin
1

Jest między nimi subtelna różnica, ale {} wiąże mocniej niż do / end.

Shankar Raju
źródło
0

Mój osobisty styl to kładzenie nacisku na czytelność ponad sztywne zasady {... }vs do... endwyboru, kiedy taki wybór jest możliwy. Mój pomysł na czytelność jest następujący:

[ 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

W bardziej złożonej składni, takiej jak wielowierszowe zagnieżdżone bloki, staram się przeplatać {... }i do... endograniczniki, aby uzyskać jak najbardziej naturalny wynik, np.

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
}

Chociaż brak sztywnych reguł może skutkować nieznacznie różnymi wyborami dla różnych programistów, uważam, że optymalizacja dla czytelności dla każdego przypadku, choć subiektywna, jest korzyścią netto w stosunku do przestrzegania sztywnych reguł.

Boris Stitnicky
źródło
1
Pochodząc z Pythona, może powinienem pominąć Rubiego i nauczyć się Wisp.
Cees Timmerman
Kiedyś uważałem, że Rubyto najlepszy pragmatyczny język. Powinienem powiedzieć język skryptowy. Dzisiaj myślę, że równie dobrze byś się uczył Brainfuck.
Boris Stitnicky
0

Wziąłem tutaj przykład najczęściej głosowanej odpowiedzi. Mówić,

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

Jeśli sprawdzisz jaką wewnętrzną implementację w array.rb, nagłówek metody map mówi :

Invokes the given block once for each element of self.

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

tj. akceptuje blok kodu - cokolwiek jest pomiędzy do i end jest wykonywane jako yield. Następnie wynik zostanie ponownie zebrany jako tablica, a więc zwróci zupełnie nowy obiekt.

Więc zawsze, gdy napotkasz blok do-end lub {} , po prostu miej mapę myśli, że blok kodu jest przekazywany jako parametr, który będzie wykonywany wewnętrznie.

Vasanth Saminathan
źródło
-3

Jest trzecia opcja: Napisz preprocesor, który na podstawie wcięcia będzie wywnioskował „koniec” w swoim własnym wierszu. Głęboko myślący, którzy wolą zwięzły kod, mają rację.

Jeszcze lepiej, zhakuj rubin, więc to jest flaga.

Oczywiście najprostszym rozwiązaniem typu „wybierz swoje walki” jest przyjęcie konwencji stylistycznej, zgodnie z którą sekwencja końcówek występuje w tej samej linii i nauczenie kolorowania składni, aby je wyciszyć. Aby ułatwić edycję, można użyć skryptów edytora, aby rozwinąć / zwinąć te sekwencje.

Od 20% do 25% moich wierszy kodu ruby ​​kończy się w osobnym wierszu, wszystko to w trywialny sposób wywnioskowane przez moje konwencje wcięć. Ruby to język podobny do seplenienia, który odniósł największy sukces. Kiedy ludzie kwestionują to, pytając, gdzie są te wszystkie upiorne nawiasy, pokazuję im funkcję rubinową kończącą się siedmioma liniami zbędnego „końca”.

Kodowałem przez lata, używając preprocesora lisp, aby wywnioskować większość nawiasów: Pasek '|' otworzył grupę, która automatycznie zamknęła się na końcu wiersza, a znak dolara „$” służył jako pusty symbol zastępczy, w którym w przeciwnym razie nie byłoby symboli pomagających w określeniu grup. To oczywiście terytorium wojny religijnej. Lisp / schemat bez nawiasów jest najbardziej poetyckim ze wszystkich języków. Mimo to łatwiej jest po prostu wyciszyć nawiasy za pomocą kolorowania składni.

Nadal koduję z preprocesorem dla Haskella, aby dodać heredoc i domyślnie wszystkie puste wiersze jako komentarze, wszystko wcięte jako kod. Nie lubię komentować postaci, niezależnie od języka.

Syzygies
źródło