do..end vs niềng răng xoăn cho các khối trong Ruby


154

Tôi có một đồng nghiệp đang tích cực cố gắng thuyết phục tôi rằng tôi không nên sử dụng do..end và thay vào đó sử dụng dấu ngoặc nhọn để xác định các khối đa dòng trong Ruby.

Tôi chắc chắn trong trại chỉ sử dụng niềng răng xoăn cho một lớp lót ngắn và làm..tất cả mọi thứ khác. Nhưng tôi nghĩ rằng tôi sẽ tiếp cận cộng đồng lớn hơn để có được một số giải pháp.

Vậy đó là cái gì, và tại sao? (Ví dụ về một số mã nên)

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

hoặc là

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

Cá nhân, chỉ cần nhìn vào các câu trả lời ở trên cho tôi, nhưng tôi muốn mở nó ra cho cộng đồng lớn hơn.


3
Chỉ tự hỏi nhưng đối số đồng nghiệp của bạn là gì khi sử dụng niềng răng? Có vẻ giống như một sở thích cá nhân hơn là một điều logic.
Daniel Lee

6
Nếu bạn muốn một cuộc thảo luận làm cho nó một wiki cộng đồng. Đối với câu hỏi của bạn: Đó chỉ là phong cách cá nhân. Tôi thích các niềng răng xoăn vì chúng trông nerdy hơn.
Halfdan

7
Đó không phải là một điều ưu tiên, có những lý do cú pháp để sử dụng cái này ngoài lý do phong cách. Một số câu trả lời giải thích tại sao. Không sử dụng đúng có thể gây ra các lỗi rất tinh vi khó tìm nếu một lựa chọn "phong cách" khác được thực hiện để không bao giờ sử dụng dấu ngoặc đơn cho các phương thức.
Tin Man

1
"Điều kiện cạnh" có thói quen xấu là lén lút với những người không biết về họ. Mã hóa phòng thủ có nghĩa là rất nhiều thứ, bao gồm cả việc quyết định sử dụng các kiểu mã hóa để giảm thiểu cơ hội gặp phải các trường hợp. BẠN có thể nhận thức được, nhưng anh chàng hai người sau bạn có thể không phải là sau khi kiến ​​thức bộ lạc bị lãng quên. Nó có xu hướng xảy ra trong môi trường công ty không may.
Tin Man

1
@tinman Những loại điều kiện cạnh được xử lý bởi TDD. Đây là lý do tại sao Rubist có thể viết mã như thế này và nghỉ ngơi cả đêm khi biết những lỗi như vậy không tồn tại trong mã của họ. Khi phát triển với TDD hoặc BDD và một lỗi như vậy được ưu tiên là màu đỏ hiển thị trên màn hình là điều khiến chúng ta nhớ đến "kiến thức bộ lạc". Thông thường, giải pháp là thêm một vài parens ở đâu đó hoàn toàn có thể chấp nhận được trong các quy ước tiêu chuẩn. :)
Blake Taylor

Câu trả lời:


246

Quy ước chung là sử dụng do..end cho các khối nhiều dòng và dấu ngoặc nhọn cho các khối dòng đơn, nhưng cũng có một sự khác biệt giữa hai khối có thể được minh họa bằng ví dụ này:

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

Điều này có nghĩa là {} có quyền ưu tiên cao hơn so với do..end, vì vậy hãy ghi nhớ điều đó khi quyết định những gì bạn muốn sử dụng.

PS: Một ví dụ nữa cần ghi nhớ trong khi bạn phát triển sở thích của mình.

Các mã sau đây:

task :rake => pre_rake_task do
  something
end

Thực sự có nghĩa là:

task(:rake => pre_rake_task){ something }

Và mã này:

task :rake => pre_rake_task {
  something
}

Thực sự có nghĩa là:

task :rake => (pre_rake_task { something })

Vì vậy, để có được định nghĩa thực tế mà bạn muốn, với dấu ngoặc nhọn, bạn phải làm:

task(:rake => pre_rake_task) {
  something
}

Có thể sử dụng niềng răng cho các tham số là điều bạn muốn làm dù sao đi nữa, nhưng nếu bạn không nên sử dụng thì tốt nhất là nên sử dụng trong những trường hợp này để tránh nhầm lẫn này.


44
Tôi bỏ phiếu cho việc luôn sử dụng dấu ngoặc đơn xung quanh các tham số của phương thức để vượt qua toàn bộ vấn đề.
Tin Man

@ The Tin Man: Bạn thậm chí sử dụng dấu ngoặc đơn trong Rake?
Andrew Grimm

9
Đó là một thói quen lập trình trường học cũ đối với tôi. Nếu một ngôn ngữ hỗ trợ dấu ngoặc đơn, tôi sử dụng chúng.
Tin Man

8
+1 để chỉ ra vấn đề ưu tiên. Đôi khi tôi nhận được ngoại lệ bất ngờ khi tôi thử và chuyển đổi làm; kết thúc với {}
idrinkpabst

1
Tại sao puts [1,2,3].map do |k| k+1; endchỉ in liệt kê tức là một điều. Không đặt hai thông số ở đây là [1,2,3].mapdo |k| k+1; end?
ben

55

Từ lập trình Ruby :

Niềng răng có độ ưu tiên cao; làm có độ ưu tiên thấp. Nếu lời gọi phương thức có các tham số không được đặt trong dấu ngoặc đơn, dạng dấu ngoặc của một khối sẽ liên kết với tham số cuối cùng, chứ không phải với lệnh gọi tổng thể. Các hình thức làm sẽ liên kết với lời mời.

Mã vậy

f param {do_something()}

Liên kết khối với parambiến trong khi mã

f param do do_something() end

Liên kết khối với chức năng f .

Tuy nhiên đây không phải là vấn đề nếu bạn đặt các đối số hàm trong ngoặc đơn.


7
+1 "Tuy nhiên, đây không phải là vấn đề nếu bạn đặt các đối số hàm trong ngoặc đơn.". Tôi làm điều đó như một vấn đề của thói quen, thay vì dựa vào cơ hội và ý thích của người phiên dịch. :-)
Tin Man

4
@theTinMan, nếu trình thông dịch của bạn sử dụng cơ hội trong trình phân tích cú pháp của nó, bạn cần một trình thông dịch mới.
Paul Draper

@PaulDraper - thực sự, nhưng không phải tất cả các ngôn ngữ đều đồng ý về vấn đề này. Nhưng (tôi nghĩ) khá hiếm khi parens không "làm công việc của họ" vì vậy sử dụng parens giúp bạn không phải nhớ các quyết định "ngẫu nhiên" do các nhà thiết kế ngôn ngữ thực hiện - ngay cả khi chúng được thực thi một cách trung thực bởi người thực hiện trình biên dịch và phiên dịch .
DLU

15

Có một vài quan điểm về vấn đề này, đó thực sự là vấn đề sở thích cá nhân. Nhiều rubyists có cách tiếp cận bạn làm. Tuy nhiên, hai kiểu phổ biến khác là luôn sử dụng cái này hoặc cái kia hoặc sử dụng {}cho các khối trả về giá trị và do ... endcho các khối được thực thi cho các hiệu ứng phụ.


2
Đó không chỉ là vấn đề sở thích cá nhân. Liên kết các tham số có thể gây ra các lỗi tinh vi nếu tham số phương thức không được đặt trong ngoặc đơn.
Tin Man

1
Tôi hiện không thực hiện tùy chọn cuối cùng, nhưng tôi là 1-ing khi đề cập rằng một số người thực hiện phương pháp đó.
Andrew Grimm

Avdi Grimm ủng hộ cho điều này trong một bài đăng blog: devblog.avdi.org/2011/07/26/...
alxndr

8

Có một lợi ích chính cho niềng răng xoăn - nhiều biên tập viên có thời gian phù hợp với NHIỀU dễ dàng hơn, làm cho một số loại gỡ lỗi dễ dàng hơn nhiều. Trong khi đó, từ khóa "làm ... kết thúc" khá khó để so khớp, đặc biệt là vì "kết thúc" cũng khớp với "nếu" s.


Ví dụ, RubyMine, đánh dấu do ... endcác từ khóa theo mặc định
ck3g

1
Mặc dù tôi thích niềng răng hơn nhưng tôi thấy không có lý do gì mà một biên tập viên gặp khó khăn khi tìm chuỗi 2/3 ký tự so với một ký tự. Mặc dù có lập luận rằng hầu hết các biên tập viên đã khớp với niềng răng, trong khi đó do ... endlà trường hợp đặc biệt đòi hỏi logic ngôn ngữ cụ thể.
Chinoto Vokro 8/2/2016

7

Quy tắc phổ biến nhất tôi từng thấy (gần đây nhất trong Eloquent Ruby ) là:

  • Nếu đó là khối nhiều dòng, hãy sử dụng do / end
  • Nếu đó là một khối dòng đơn, hãy sử dụng {}

7

Tôi đang bỏ phiếu cho / kết thúc


Quy ước do .. enddành cho multiline và { ... }cho một lớp lót.

Nhưng tôi thích do .. endtốt hơn, vì vậy khi tôi có một lớp lót, tôi vẫn sử dụng do .. endnhưng định dạng nó như bình thường cho do / end trong ba dòng. Điều này làm cho tất cả mọi người hạnh phúc.

  10.times do 
    puts ...
  end

Một vấn đề với { } nó là chế độ thù địch ở chế độ thơ (vì chúng liên kết chặt chẽ với tham số cuối cùng chứ không phải toàn bộ lệnh gọi phương thức, vì vậy bạn phải bao gồm các phương thức parens) và theo tôi, trông tôi không đẹp lắm. Chúng không phải là nhóm tuyên bố và chúng xung đột với các hằng số băm để dễ đọc.

Thêm vào đó, tôi đã thấy đủ { }trong các chương trình C. Cách của Ruby, như thường lệ, là tốt hơn. Có chính xác một loại ifkhối và bạn không bao giờ phải quay lại và chuyển đổi một câu lệnh thành câu lệnh ghép.


2
"Tôi đã thấy đủ {} trong các chương trình C" ... và Perl. Và, +1 cho một số tuyên bố trong đó và ủng hộ các kiểu mã hóa giống như zen của Ruby. :-)
Tin Man

1
Nói đúng ra, có một cú pháp "if" khác - hậu tố "if" ( do_stuff if x==1), đây là loại gây khó chịu khi chuyển đổi thành cú pháp thông thường. Nhưng đó là một cuộc thảo luận khác.
Kelvin

Có tiền tố if, hậu tố if, hậu tố unless(cũng là một loại if, một if-không) dòng và một ifvới then. Cách của Ruby là có rất nhiều cách để thể hiện trong thực tế một thứ luôn luôn giống nhau, tệ hơn nhiều so với những gì C đang cung cấp tuân theo các quy tắc rất đơn giản, nghiêm ngặt.
Mecki

2
Vậy nếu chúng ta thích {} trong C, điều đó có nghĩa là chúng ta cũng nên sử dụng chúng trong Ruby?
Warren Dew

6

Một vài người chơi ruby ​​có ảnh hưởng đề nghị sử dụng niềng răng khi bạn sử dụng giá trị trả về và làm / kết thúc khi bạn không.

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

http://onestepback.org/index.cgi/Tech/Ruby/BraceVsDoEnd.rdoc (trên archive.org)

Điều này có vẻ như là một thực hành tốt nói chung.

Tôi sẽ sửa đổi nguyên tắc này một chút để nói rằng bạn nên tránh sử dụng do / end trên một dòng vì khó đọc hơn.

Bạn phải cẩn thận hơn khi sử dụng dấu ngoặc nhọn vì nó sẽ liên kết với tham số cuối cùng của phương thức thay vì toàn bộ lệnh gọi phương thức. Chỉ cần thêm dấu ngoặc đơn để tránh điều đó.


2

Thật ra đó là một sở thích cá nhân, nhưng đã nói rằng, trong 3 năm qua những trải nghiệm về viên ruby ​​của tôi, điều tôi đã học được là ruby ​​có phong cách của nó.

Một ví dụ sẽ là, nếu bạn đang đến từ nền JAVA, cho phương thức boolean bạn có thể sử dụng

def isExpired
  #some code
end 

chú ý trường hợp lạc đà và thường là tiền tố 'là' để xác định nó là phương thức boolean.

Nhưng trong thế giới ruby, phương pháp tương tự sẽ là

def expired?
  #code
end

Vì vậy, cá nhân tôi nghĩ rằng, tốt hơn là nên đi theo 'cách ruby' (Nhưng tôi biết phải mất một thời gian để người ta hiểu (tôi mất khoảng 1 năm: D)).

Cuối cùng, tôi sẽ đi với

do 
  #code
end

khối.


2

Tôi đặt một câu trả lời khác, mặc dù sự khác biệt lớn đã được chỉ ra (tính bắt buộc / ràng buộc) và điều đó có thể gây ra vấn đề khó tìm (Người đàn ông Tin và những người khác đã chỉ ra điều đó). Tôi nghĩ rằng ví dụ của tôi cho thấy vấn đề với một đoạn mã không thông thường, ngay cả các lập trình viên có kinh nghiệm cũng không đọc như thời gian chủ nhật:

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

Sau đó, tôi đã làm một số mã làm đẹp ...

#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

Nếu bạn thay đổi {}ở đây, do/endbạn sẽ gặp lỗi, phương pháp đótranslate đó không tồn tại ...

Tại sao điều này xảy ra được chỉ ra ở đây nhiều hơn một - ưu tiên. Nhưng đặt niềng răng ở đâu? (@the Tin Man: Tôi luôn sử dụng niềng răng, giống như bạn, nhưng ở đây ... giám sát)

vì vậy mọi câu trả lời như

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

chỉ là sai nếu sử dụng mà không có "NHƯNG Hãy để mắt đến niềng răng / ưu tiên!"

lần nữa:

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

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

(kết quả của việc mở rộng sẽ làm gì với khối ...)

Vì vậy, nếu bạn muốn sử dụng do / end, hãy sử dụng:

#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

1

Xuất phát từ xu hướng cá nhân, tôi thích các dấu ngoặc nhọn hơn khối do / end vì nó dễ hiểu hơn đối với số lượng nhà phát triển cao hơn do phần lớn các ngôn ngữ nền sử dụng chúng trong quy ước do / end. Như đã nói, chìa khóa thực sự là đi đến một thỏa thuận trong cửa hàng của bạn, nếu 6/10 nhà phát triển sử dụng do / end sử dụng thì MỌI NGƯỜI nên sử dụng chúng, nếu 6/10 sử dụng dấu ngoặc nhọn, thì hãy tuân theo mô hình đó.

Đó là tất cả về việc tạo ra một mô hình để toàn bộ nhóm có thể xác định các cấu trúc mã nhanh hơn.


3
Nó là nhiều hơn ưu tiên. Sự ràng buộc giữa hai người là khác nhau và có thể gây ra những vấn đề khó chẩn đoán. Một số câu trả lời chi tiết vấn đề.
Tin Man

4
Về mặt những người có nền tảng ngôn ngữ khác - tôi nghĩ rằng sự khác biệt về "mức độ dễ hiểu" là rất nhỏ trong trường hợp này để không đảm bảo xem xét. (Đó không phải là downvote của tôi btw.)
Kelvin

1

Có một sự khác biệt tinh tế giữa chúng, nhưng {} liên kết chặt chẽ hơn do / end.


0

Phong cách cá nhân của tôi là nhấn mạnh sự dễ đọc hơn các quy tắc cứng nhắc của {... }so với do... endsự lựa chọn, khi sự lựa chọn đó là có thể. Ý tưởng của tôi về khả năng đọc là như sau:

[ 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

Trong cú pháp phức tạp hơn, chẳng hạn như các khối lồng nhau nhiều dòng, tôi cố gắng xen kẽ { ... }do... các enddấu phân cách cho hầu hết kết quả tự nhiên, ví dụ.

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
}

Mặc dù việc thiếu các quy tắc cứng nhắc có thể tạo ra các lựa chọn khác nhau cho các lập trình viên khác nhau, tôi tin rằng tối ưu hóa từng trường hợp để dễ đọc, mặc dù chủ quan, là một lợi ích ròng đối với việc tuân thủ các quy tắc cứng nhắc.


1
Đến từ Python, có lẽ tôi nên bỏ qua Ruby và học Wisp thay thế.
Cees Timmerman

Tôi đã từng tin Rubylà ngôn ngữ thực dụng tốt nhất ngoài kia. Tôi có nên nói ngôn ngữ kịch bản. Hôm nay, tôi nghĩ bạn sẽ học tập tốt như nhau Brainfuck.
Boris Stitnicky

0

Tôi lấy một ví dụ về câu trả lời được bình chọn nhiều nhất ở đây. Nói,

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

Nếu bạn kiểm tra việc triển khai nội bộ nào trong mảng.rb, tiêu đề của phương thức bản đồ sẽ nói :

Invokes the given block once for each element of self.

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

tức là nó chấp nhận một khối mã - bất cứ thứ gì ở giữa làmkết thúc đều được thực thi như năng suất. Sau đó, kết quả sẽ được thu thập lại dưới dạng mảng, do đó, nó sẽ trả về một đối tượng hoàn toàn mới.

Vì vậy, bất cứ khi nào bạn gặp khối do-end hoặc {} , chỉ cần có sơ đồ tư duy rằng khối mã đang được truyền dưới dạng tham số, sẽ được thực thi bên trong.


-3

Có một lựa chọn thứ ba: Viết một bộ tiền xử lý để suy ra "kết thúc" trên dòng riêng của nó, từ thụt lề. Các nhà tư tưởng sâu sắc thích mã ngắn gọn xảy ra là đúng.

Tốt hơn nữa, hack ruby ​​vì vậy đây là một lá cờ.

Tất nhiên, giải pháp đơn giản nhất "chọn chiến đấu của bạn" là áp dụng quy ước kiểu mà một chuỗi các kết thúc xuất hiện trên cùng một dòng và dạy màu cú pháp của bạn để tắt tiếng chúng. Để dễ chỉnh sửa, người ta có thể sử dụng các tập lệnh biên tập để mở rộng / thu gọn các chuỗi này.

20% đến 25% dòng mã ruby ​​của tôi là "kết thúc" trên dòng riêng của chúng, tất cả đều được suy luận một cách tầm thường bởi các quy ước thụt đầu dòng của tôi. Ruby là ngôn ngữ giống như ngôn ngữ để đạt được thành công lớn nhất. Khi mọi người tranh cãi về vấn đề này, hỏi tất cả các dấu ngoặc đơn ghê gớm ở đâu, tôi chỉ cho họ một hàm ruby ​​kết thúc bằng bảy dòng "kết thúc" thừa.

Tôi đã viết mã trong nhiều năm bằng cách sử dụng bộ tiền xử lý không thể thiếu để suy ra hầu hết các dấu ngoặc đơn: Một thanh '|' đã mở một nhóm được gắn tự động ở cuối dòng và ký hiệu đô la '$' được dùng như một trình giữ chỗ trống trong đó nếu không sẽ không có biểu tượng để giúp suy ra các nhóm. Tất nhiên đây là lãnh thổ chiến tranh tôn giáo. Lisp / lược đồ không có dấu ngoặc đơn là thơ nhất trong tất cả các ngôn ngữ. Tuy nhiên, sẽ dễ dàng hơn để làm dịu các dấu ngoặc đơn bằng cách sử dụng màu cú pháp.

Tôi vẫn viết mã với bộ tiền xử lý cho Haskell, để thêm heredocs và mặc định tất cả các dòng tuôn ra dưới dạng nhận xét, mọi thứ được thụt vào dưới dạng mã. Tôi không thích nhân vật bình luận, bất cứ ngôn ngữ nào.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.