do..end vs kurung kurawal untuk blok di Ruby

155

Saya memiliki rekan kerja yang secara aktif berusaha meyakinkan saya bahwa saya tidak boleh menggunakan do..end dan sebagai gantinya menggunakan kurung kurawal untuk mendefinisikan blok multiline di Ruby.

Saya tegas di kamp hanya menggunakan kurung kurawal untuk one-liners pendek dan lakukan. Tapi saya pikir saya akan menjangkau komunitas yang lebih besar untuk mendapatkan resolusi.

Jadi yang mana, dan mengapa? (Contoh beberapa kode mestinya)

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

atau

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

Secara pribadi, hanya melihat jawaban di atas untuk saya, tetapi saya ingin membukanya untuk komunitas yang lebih luas.

Blake Taylor
sumber
3
Hanya ingin tahu tetapi apa argumen rekan kerja Anda untuk menggunakan kawat gigi? Tampaknya lebih seperti preferensi pribadi daripada hal yang logis.
Daniel Lee
6
Jika Anda ingin diskusi, buatlah wiki komunitas. Untuk pertanyaan Anda: Ini hanya gaya pribadi. Saya lebih suka kurung kurawal karena mereka terlihat lebih kutu buku.
halfdan
7
Itu bukan hal preferensi, ada alasan sintaksis untuk menggunakan satu di atas yang lain selain alasan gaya. Beberapa jawaban menjelaskan mengapa. Gagal menggunakan yang benar dapat menyebabkan bug yang sangat halus yang sulit ditemukan jika pilihan "gaya" lain dibuat untuk tidak pernah menggunakan tanda kurung pembungkus untuk metode.
the Tin Man
1
"Kondisi tepi" memiliki kebiasaan buruk untuk menyelinap ke orang yang tidak tahu tentang mereka. Pengkodean secara defensif berarti banyak hal, termasuk memutuskan untuk menggunakan gaya pengkodean yang meminimalkan kemungkinan pernah mengalami kasus. ANDA mungkin sadar, tetapi lelaki dua orang setelah Anda mungkin tidak setelah pengetahuan kesukuan telah dilupakan. Sayangnya, itu cenderung terjadi di lingkungan perusahaan.
the Tin Man
1
@tinman Jenis kondisi tepi ini ditangani oleh TDD. Inilah sebabnya Rubist dapat menulis kode seperti ini dan mendapatkan istirahat malam penuh mengetahui kesalahan seperti itu tidak ada dalam kode mereka. Ketika berkembang dengan TDD atau BDD dan kesalahan seperti itu diutamakan dibuat merah ditampilkan di layar adalah apa yang mengingatkan kita tentang "pengetahuan suku". Biasanya solusinya adalah menambahkan beberapa parens di suatu tempat yang benar-benar dapat diterima dalam konvensi standar. :)
Blake Taylor

Jawaban:

247

Konvensi umum adalah menggunakan do..end untuk blok multi-line dan kurung kurawal untuk blok baris tunggal, tetapi ada juga perbedaan antara keduanya yang dapat diilustrasikan dengan contoh ini:

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

Ini berarti bahwa {} memiliki prioritas lebih tinggi daripada do..end, jadi ingatlah ketika memutuskan apa yang ingin Anda gunakan.

PS: Satu contoh lagi yang perlu diingat saat Anda mengembangkan preferensi Anda.

Kode berikut:

task :rake => pre_rake_task do
  something
end

sangat berarti:

task(:rake => pre_rake_task){ something }

Dan kode ini:

task :rake => pre_rake_task {
  something
}

sangat berarti:

task :rake => (pre_rake_task { something })

Jadi untuk mendapatkan definisi aktual yang Anda inginkan, dengan kurung kurawal, Anda harus melakukan:

task(:rake => pre_rake_task) {
  something
}

Mungkin menggunakan kawat gigi untuk parameter adalah sesuatu yang ingin Anda lakukan, tetapi jika Anda tidak melakukannya, mungkin yang terbaik adalah menggunakan lakukan.

Pan Thomakos
sumber
44
Saya memilih untuk selalu menggunakan tanda kurung di sekitar parameter metode untuk menghindari seluruh masalah.
the Tin Man
@ the Tin Man: Anda bahkan menggunakan tanda kurung di Rake?
Andrew Grimm
9
Ini kebiasaan pemrograman sekolah lama bagi saya. Jika suatu bahasa mendukung tanda kurung, saya menggunakannya.
the Tin Man
8
+1 untuk menunjukkan masalah prioritas. Terkadang saya mendapatkan pengecualian yang tidak terduga ketika saya mencoba dan melakukan konversi; akhiri {}
idrinkpabst
1
Kenapa puts [1,2,3].map do |k| k+1; endhanya mencetak enumerator yaitu satu hal. Bukankah menempatkan dua argumen lewat sini yaitu [1,2,3].mapdan do |k| k+1; end?
ben
55

Dari Programming Ruby :

Kawat gigi memiliki prioritas tinggi; do memiliki prioritas rendah. Jika pemanggilan metode memiliki parameter yang tidak tertutup dalam tanda kurung, bentuk kurung blok akan mengikat ke parameter terakhir, bukan ke doa keseluruhan. Formulir do akan mengikat doa.

Jadi kodenya

f param {do_something()}

Mengikat blok ke paramvariabel saat kode

f param do do_something() end

Mengikat blok ke fungsi f .

Namun ini bukan masalah jika Anda menyertakan argumen fungsi dalam tanda kurung.

David Brown
sumber
7
+1 "Namun ini bukan masalah jika Anda menyertakan argumen fungsi dalam tanda kurung.". Saya melakukan itu sebagai kebiasaan, daripada mengandalkan kesempatan dan kemauan penerjemah. :-)
the Tin Man
4
@ theTinMan, jika juru bahasa Anda menggunakan peluang dalam pengurai, Anda memerlukan juru bahasa baru.
Paul Draper
@ PaulDraper - memang, tetapi tidak semua bahasa menyetujui masalah ini. Tetapi (saya pikir) sangat jarang parens untuk tidak "melakukan pekerjaan mereka" sehingga menggunakan parens secara universal menyelamatkan Anda dari keharusan mengingat keputusan "acak" yang dibuat oleh perancang bahasa - bahkan jika mereka dengan setia dieksekusi oleh para pelaksana kompiler dan juru bahasa. .
dlu
15

Ada beberapa sudut pandang tentang ini, ini benar-benar masalah preferensi pribadi. Banyak rubyist mengambil pendekatan yang Anda lakukan. Namun, dua gaya lain yang umum adalah selalu menggunakan satu atau yang lain, atau menggunakan {}untuk blok yang mengembalikan nilai, dan do ... enduntuk blok yang dieksekusi untuk efek samping.

GSto
sumber
2
Ini bukan masalah preferensi pribadi saja. Mengikat parameter dapat menyebabkan bug halus jika param metode tidak dimasukkan dalam tanda kurung.
the Tin Man
1
Saat ini saya tidak melakukan opsi terakhir, tetapi saya memberi +1 untuk menyebutkan bahwa beberapa orang mengambil pendekatan itu.
Andrew Grimm
Avdi Grimm mengadvokasi ini dalam sebuah posting blog: devblog.avdi.org/2011/07/26/…
alxndr
8

Ada satu manfaat utama untuk kurung kurawal - banyak editor memiliki waktu yang jauh lebih mudah untuk mencocokkannya, membuat jenis debug tertentu lebih mudah. Sementara itu, kata kunci "do ... end" agak sedikit lebih sulit untuk dicocokkan, terutama karena "end" juga cocok "jika" s.

GlyphGryph
sumber
RubyMine misalnya, sorot do ... endkata kunci secara default
ck3g
1
Meskipun saya lebih suka kawat gigi, saya tidak melihat alasan bahwa editor akan kesulitan menemukan string karakter 2/3 versus karakter tunggal. Meskipun ada argumen bahwa sebagian besar editor sudah cocok dengan kawat gigi, sedangkan do ... endkasus khusus yang membutuhkan logika spesifik bahasa.
Chinoto Vokro
7

Aturan paling umum yang pernah saya lihat (paling baru di Eloquent Ruby ) adalah:

  • Jika ini adalah blok multi-line, gunakan do / end
  • Jika itu adalah satu baris blok, gunakan {}
Peter Brown
sumber
7

Saya memilih untuk melakukan / mengakhiri


Konvensi ini do .. enduntuk multiline dan { ... }one-liners.

Tapi saya suka yang do .. endlebih baik, jadi ketika saya memiliki satu liner, saya do .. endtetap menggunakan tetapi memformatnya seperti biasa untuk melakukan / berakhir dalam tiga baris. Ini membuat semua orang senang.

  10.times do 
    puts ...
  end

Salah satu masalah { }adalah bahwa itu adalah mode puisi-permusuhan (karena mereka mengikat dengan ketat ke parameter terakhir dan bukan seluruh pemanggilan metode, jadi Anda harus memasukkan metode parens) dan mereka hanya, menurut saya, tidak terlihat baik. Mereka bukan kelompok pernyataan dan mereka berbenturan dengan konstanta hash untuk dibaca.

Plus, saya sudah cukup melihat { }dalam program C. Cara Ruby, seperti biasa, lebih baik. Hanya ada satu jenis ifblok, dan Anda tidak perlu kembali dan mengubah pernyataan menjadi pernyataan gabungan.

DigitalRoss
sumber
2
"Aku sudah cukup melihat {} dalam program C" ... dan Perl. Dan, +1 untuk beberapa pernyataan di sana dan untuk menganjurkan gaya pengkodean seperti zen seperti Ruby. :-)
the Tin Man
1
Sebenarnya, ada sintaks "jika" lain - postfix "jika" ( do_stuff if x==1), yang agak menyebalkan untuk dikonversi ke sintaksis biasa. Tapi itu diskusi lain.
Kelvin
Ada awalan if, postfix if, postfix unless(juga semacam if, jika-tidak) dan satu baris ifdengan then. Cara Ruby adalah memiliki banyak cara untuk mengekspresikan sesuatu yang selalu sama, jauh lebih buruk daripada apa yang ditawarkan C yang mengikuti aturan yang sangat sederhana dan ketat.
Mecki
2
Jadi jika kita menyukai {} dalam C, itu artinya kita juga harus menggunakannya di Ruby?
Warren Dew
6

Beberapa ruby ​​berpengaruh menyarankan untuk menggunakan kawat gigi ketika Anda menggunakan nilai kembali, dan lakukan / akhiri ketika Anda tidak.

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

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

Ini sepertinya praktik yang baik secara umum.

Saya akan sedikit memodifikasi prinsip ini untuk mengatakan bahwa Anda harus menghindari penggunaan do / end pada satu baris karena lebih sulit untuk dibaca.

Anda harus lebih berhati-hati menggunakan kawat gigi karena itu akan mengikat ke param akhir metode daripada seluruh pemanggilan metode. Cukup tambahkan tanda kurung untuk menghindari itu.

Kelvin
sumber
2

Sebenarnya ini adalah pilihan pribadi, tetapi setelah mengatakan itu, selama 3 tahun terakhir pengalaman rubi saya apa yang saya pelajari adalah bahwa ruby ​​memiliki gayanya.

Salah satu contohnya adalah, jika Anda datang dari latar belakang JAVA, untuk metode boolean yang mungkin Anda gunakan

def isExpired
  #some code
end 

perhatikan kasus unta dan paling sering 'adalah' awalan untuk mengidentifikasinya sebagai metode boolean.

Tetapi di dunia ruby, metode yang sama akan terjadi

def expired?
  #code
end

jadi saya pribadi berpikir, lebih baik menggunakan 'ruby way' (Tapi saya tahu perlu waktu bagi seseorang untuk mengerti (butuh sekitar 1 tahun: D)).

Akhirnya, saya akan pergi bersama

do 
  #code
end

blok.

sameera207
sumber
2

Saya memberikan jawaban lain, meskipun perbedaan besar sudah ditunjukkan (prcedence / binding), dan itu dapat menyebabkan sulit untuk menemukan masalah (Manusia Timah, dan yang lainnya menunjukkan itu). Saya pikir contoh saya menunjukkan masalah dengan cuplikan kode yang tidak biasa, bahkan programmer yang berpengalaman tidak membaca seperti hari Minggu:

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

Lalu saya melakukan beberapa kode mempercantik ...

#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

jika Anda mengubah di {}sini untuk do/endAnda akan mendapatkan kesalahan, metode translateitu tidak ada ...

Mengapa ini terjadi ditunjukkan di sini lebih dari satu - diutamakan. Tapi di mana harus memasang kawat gigi di sini? (@ the Tin Man: Saya selalu menggunakan kawat gigi, seperti Anda, tetapi di sini ... diawasi)

jadi setiap jawaban suka

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

hanya salah jika digunakan tanpa "TAPI Mengawasi kawat gigi / didahulukan!"

lagi:

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

dan

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

(Apa pun hasil dari perpanjangan tidak dengan blok ...)

Jadi, jika Anda ingin menggunakan do / end use this:

#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
setengah bit
sumber
1

Berkenaan dengan bias pribadi, saya lebih suka kurung kurawal daripada blok do / end karena lebih dapat dimengerti oleh sejumlah pengembang yang lebih tinggi karena mayoritas bahasa latar belakang menggunakannya pada konvensi do / end. Dengan itu dikatakan kunci sebenarnya adalah mencapai kesepakatan di dalam toko Anda, jika do / end digunakan oleh 6/10 pengembang daripada SEMUA ORANG harus menggunakannya, jika 6/10 menggunakan kurung kurawal, maka berpegang pada paradigma itu.

Ini semua tentang membuat pola sehingga tim secara keseluruhan dapat mengidentifikasi struktur kode lebih cepat.

Jake Kalstad
sumber
3
Ini lebih dari preferensi. Ikatan antara keduanya berbeda dan dapat menyebabkan masalah yang sulit didiagnosis. Beberapa jawaban merinci masalahnya.
the Tin Man
4
Dalam hal orang-orang dari latar belakang bahasa lain - saya pikir perbedaan dalam "pemahaman" sangat kecil dalam hal ini sehingga tidak memerlukan pertimbangan. (Itu bukan downvote saya btw.)
Kelvin
1

Ada perbedaan tipis di antara mereka, tetapi {} mengikat lebih ketat daripada do / end.

Shankar Raju
sumber
0

Gaya pribadi saya adalah untuk menekankan keterbacaan atas aturan kaku {... }vs do... endpilihan, ketika pilihan seperti itu mungkin. Gagasan saya tentang keterbacaan adalah sebagai berikut:

[ 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

Dalam sintaksis yang lebih kompleks, seperti blok bertingkat multiline, saya mencoba untuk menyelingi {... }dan do... endpembatas untuk hasil yang paling alami, misalnya.

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
}

Meskipun kurangnya aturan kaku mungkin menghasilkan pilihan yang sedikit berbeda untuk programmer yang berbeda, saya percaya bahwa optimasi kasus per kasus untuk keterbacaan, meskipun subyektif, adalah keuntungan bersih dari kepatuhan terhadap aturan yang kaku.

Boris Stitnicky
sumber
1
Berasal dari Python, mungkin saya harus melewati Ruby dan belajar Wisp sebagai gantinya.
Cees Timmerman
Saya dulu percaya Rubyadalah bahasa pragmatis terbaik di luar sana. Haruskah saya mengatakan bahasa scripting. Hari ini, saya pikir Anda akan sama-sama belajar dengan baik Brainfuck.
Boris Stitnicky
0

Saya mengambil contoh jawaban yang paling banyak dipilih di sini. Mengatakan,

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

Jika Anda memeriksa apa implementasi internal di array.rb, header metode peta mengatakan :

Invokes the given block once for each element of self.

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

yaitu menerima blok kode - apa pun yang ada di antara do dan end dieksekusi sebagai hasil. Kemudian hasilnya akan dikumpulkan sebagai array lagi, sehingga mengembalikan objek yang sama sekali baru.

Jadi, Setiap kali Anda menemukan blok do-end atau {} , hanya memiliki peta pikiran bahwa blok kode dilewatkan sebagai parameter, yang akan dijalankan secara internal.

Vasanth Saminathan
sumber
-3

Ada opsi ketiga: Tulis preprocessor untuk menyimpulkan "end" pada barisnya sendiri, dari indentasi. Pemikir mendalam yang lebih suka kode ringkas kebetulan benar.

Lebih baik lagi, retas ruby ​​jadi ini adalah bendera.

Tentu saja, solusi paling sederhana "pilih perkelahian" Anda adalah dengan mengadopsi konvensi gaya bahwa semua urutan ujung muncul pada baris yang sama, dan mengajarkan pewarnaan sintaks Anda untuk membisukan mereka. Untuk memudahkan pengeditan, seseorang dapat menggunakan skrip editor untuk memperluas / menciutkan urutan ini.

20% hingga 25% dari baris kode ruby ​​saya adalah "end" pada baris mereka sendiri, semua disimpulkan oleh konvensi indentasi saya. Ruby adalah bahasa mirip-lisp yang telah mencapai kesuksesan terbesar. Ketika orang-orang membantah ini, menanyakan di mana semua tanda kurung yang mengerikan, saya menunjukkan kepada mereka fungsi ruby ​​yang diakhiri dengan tujuh baris "akhir" yang berlebihan.

Saya melakukan kode selama bertahun-tahun menggunakan preparat prosesor untuk menyimpulkan sebagian besar tanda kurung: Bilah '|' membuka grup yang diautoclosed pada akhir baris, dan tanda dolar '$' berfungsi sebagai penampung kosong di mana tidak akan ada simbol untuk membantu menyimpulkan pengelompokan. Ini tentu saja wilayah perang agama. Lisp / skema tanpa tanda kurung adalah yang paling puitis dari semua bahasa. Meski demikian, lebih mudah untuk tenang kurung menggunakan pewarnaan sintaks.

Saya masih kode dengan preprocessor untuk Haskell, untuk menambahkan heredocs, dan ke default semua baris flush sebagai komentar, semuanya diindentasi sebagai kode. Saya tidak suka karakter komentar, apa pun bahasanya.

Syzygies
sumber