Rubyのブロックのdo..endと中括弧


154

Rubyで複数行ブロックを定義するためにdo..endを使用せず、代わりに中括弧を使用するように積極的に説得しようとしている同僚がいます。

私は固い中括弧を短いワンライナーにのみ使用し、他のすべてに使用することに固執しています。しかし、私は何らかの解決策を得るために、より大きなコミュニティに手を差し伸べると思いました。

それはどちらですか、そしてなぜですか?(いくつかのshouldaコードの例)

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

または

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

個人的には、上記を見るだけで私の質問に答えますが、私はこれをより大きなコミュニティに開放したいと思いました。


3
不思議に思いますが、ブレースを使用するための同僚の主張は何ですか?論理的なものというより、個人的な好みのようです。
ダニエルリー

6
ディスカッションが必要な場合は、コミュニティWikiにしてください。あなたの質問へ:それは単なる個人的なスタイルです。彼らはよりオタクに見えるので、私は中かっこを好む。
ハーフダン2011

7
これは好みではありません。文体上の理由に加えて、一方を他方に使用する構文上の理由があります。いくつかの回答が理由を説明しています。正しいものを使用しないと、非常に微妙なバグが発生する可能性があり、メソッドに折り返し括弧を使用しないように別の「スタイル」の選択を行った場合は、見つけるのが難しくなります。
ブリキ男

1
「エッジコンディション」は、それを知らない人に忍び寄るという悪い癖があります。防御的にコーディングするとは、ケースに遭遇する可能性を最小限に抑えるコーディングスタイルの使用を決定するなど、多くのことを意味します。あなたは気づいているかもしれませんが、部族の知識が忘れられた後ではないかもしれません。残念ながら企業環境で発生する傾向があります。
ティンマン

1
@tinmanこれらのタイプのエッジ条件はTDDによって処理されます。これが、Rubistがこのようなコードを記述し、そのようなエラーがコードに存在しないことを知って一晩休むことができる理由です。TDDまたはBDDを使用して開発し、そのような優先順位の間違いが発生した場合、画面に表示される赤は「部族の知識」を思い出させるものです。通常の解決策は、いくつかの括弧をどこかに追加することです。これは、標準的な規則の範囲内で完全に受け入れられます。:)
ブレイクテイラー

回答:


246

一般的な規則は、複数行のブロックにはdo..endを使用し、単一行のブロックには中括弧を使用することですが、この例で示すことができる2つの違いもあります。

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

これは、{}がdo..endよりも優先されることを意味します。そのため、何を使用するかを決定するときは、そのことを覚えておいてください。

PS:好みを開発するときに覚えておくべきもう1つの例。

次のコード:

task :rake => pre_rake_task do
  something
end

本当に意味します:

task(:rake => pre_rake_task){ something }

そしてこのコード:

task :rake => pre_rake_task {
  something
}

本当に意味します:

task :rake => (pre_rake_task { something })

したがって、中括弧を使用して希望する実際の定義を取得するには、次のようにする必要があります。

task(:rake => pre_rake_task) {
  something
}

パラメータに中括弧を使用することはとにかくしたいかもしれませんが、そうでない場合は、この混乱を避けるためにdo..endを使用することをお勧めします。


44
問題全体を回避するために、メソッドパラメータを常に括弧で囲むことに投票します。
ティンマン

@The Tin Man:Rakeでかっこも使用しますか?
Andrew Grimm

9
それは私にとって古い学校のプログラミング習慣です。言語が括弧をサポートしている場合は、それらを使用します。
ティンマン'10

8
優先順位の問題を指摘するための+1。doを変換しようとすると、予期しない例外が発生することがあります。{}まで終了
idrinkpabst 2013

1
puts [1,2,3].map do |k| k+1; end列挙子、つまり1つのものだけを出力するのはなぜですか。プット、すなわちここでは2つの引数が渡されていない[1,2,3].mapdo |k| k+1; end
2014

55

プログラミングRubyから:

ブレースは優先順位が高いです。優先順位は低くなります。メソッドの呼び出しに括弧で囲まれていないパラメーターがある場合、ブロックのブレース形式は、呼び出し全体ではなく、最後のパラメーターにバインドされます。doフォームは呼び出しにバインドします。

したがって、コード

f param {do_something()}

paramコードをブロックしながら変数にバインドします

f param do do_something() end

ブロックを関数にバインドしますf

ただし、関数の引数を括弧で囲んでも問題はありません。


7
+1「ただし、関数の引数を括弧で囲んでも問題はありません。」私はそれを、偶然や通訳の気まぐれに頼るのではなく、習慣の問題として行います。:-)
ティンマン

4
@theTinMan、インタープリターがパーサーでチャンスを使用する場合、新しいインタープリターが必要です。
Paul Draper

@PaulDraper-確かに、しかしすべての言語がこの問題に同意するわけではありません。しかし、(私が思うに)括弧が「自分の仕事をしない」ことはかなりまれなので、括弧を使用すると、コンパイラやインタープリタの実装者によって忠実に実行されたとしても、言語設計者が行った「ランダム」な決定を覚えておく必要がなくなります。 。
dlu

15

これにはいくつかの見方がありますが、それは本当に個人的な好みの問題です。多くのルビイストはあなたがするアプローチをとります。ただし、一般的な他の2つのスタイルは、常にどちらか一方を使用するか、{}値を返すブロックと、do ... end副作用のために実行されるブロックに使用することです。


2
それは個人的な好みだけの問題ではありません。パラメータのバインドは、メソッドのパラメータが括弧で囲まれていない場合、微妙なバグを引き起こす可能性があります。
ティンマン

1
私は現在、最後のオプションを実行していませんが、一部の人々がそのアプローチを取っていることを言及して+1しています。
Andrew Grimm

ブログの記事で、このためAvdiグリムの支持者:devblog.avdi.org/2011/07/26/...
alxndr

8

中かっこには大きな利点が1つあります。多くのエディターでは、中かっこを一致させる時間がはるかに簡単になり、特定の種類のデバッグがはるかに簡単になります。一方、キーワード「do ... end」は、特に「end」も「if」にも一致するため、一致させるのがかなり難しくなります。


たとえばRubyMineは、do ... endデフォルトでキーワードを強調表示します
ck3g

1
中かっこが好きですが、エディターが1文字ではなく2/3文字の文字列を見つけるのに問題があるという理由はわかりません。ほとんどのエディターはすでに中括弧に一致するという議論がありますが、do ... end言語固有のロジックを必要とする特殊なケースです。
Chinoto Vokro、2016


7

do / endに投票しています


規約はdo .. end、マルチラインと{ ... }ワンライナーです。

しかし、私はdo .. endより良いので、ライナーが1つの場合は、do .. endとにかくますが、通常のようにフォーマットして、3行で終了/終了します。これは皆を幸せにします。

  10.times do 
    puts ...
  end

一つの問題 { }は、それがpoetry-mode-hostile(メソッド呼び出し全体ではなく最後のパラメーターに強くバインドされるため、メソッドの括弧を含める必要があるため)であり、私の考えでは、見栄えがよくないことです。それらはステートメントグループではなく、読みやすくするためにハッシュ定数と競合します。

さらに、{ }Cプログラムで十分な数を見てきました。Rubyの方法は、いつものように、より優れています。ifブロックのタイプは1つだけで、ステートメントに戻ってステートメントを複合ステートメントに変換する必要はありません。


2
「Cプログラムで{}を十分に見た」...そしてPerl。そして、その中のいくつかのステートメントと、Rubyの禅のようなコーディングスタイルを支持するための+1。:-)
ティンマン

1
厳密に言えば、もう1つの「if」構文があります-接尾辞「if」(do_stuff if x==1)は、通常の構文に変換するのが面倒です。しかし、それは別の議論です。
ケルビン

prefix if、postfix if、postfix unless(一種のif、if-not)と1行のifがありthenます。Rubyの方法は、実際には常に同じことを表現する方法をたくさん用意することであり、Cが提供しているものよりもはるかに単純で厳密な規則に従っています。
Mecki

2
Cで{}が気に入ったら、Rubyでも使用する必要があるということですか。
ウォーレンデュー

6

影響力のある2人のルビイストは、戻り値を使用する場合は中括弧を使用し、使用しない場合は終了することを推奨しています。

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

http://onestepback.org/index.cgi/Tech/Ruby/BraceVsDoEnd.rdoc

これは一般的には良い習慣のようです。

この原則を少し変更して、1行でdo / endを使用することは避けてください。読みにくいからです。

中括弧はメソッド呼び出し全体ではなくメソッドの最終パラメーターにバインドされるため、中括弧の使用には注意が必要です。それを避けるために括弧を追加するだけです。


2

実際には個人的な好みですが、過去3年間のルビーの経験から、私が学んだことはルビーにはそのスタイルがあるということです。

たとえば、JAVAのバックグラウンドから来ている場合は、次のようなブール値のメソッドを使用します。

def isExpired
  #some code
end 

ラクダのケースに注意してください。ほとんどの場合、ブールメソッドとして識別するための「is」接頭辞です。

しかし、ルビーの世界では、同じ方法が

def expired?
  #code
end

だから私は個人的に、「ルビーな方法」で行くほうがいいと思います(しかし、理解するのに少し時間がかかることを知っています(1年ほどかかりました:D))。

最後に、私は一緒に行きます

do 
  #code
end

ブロック。


2

大きな違いはすでに指摘されていますが(優先度/バインディング)、別の回答を示しました。これにより、問題を見つけるのが困難になる可能性があります(ティンマンなどが指摘しました)。私の例は、あまり一般的ではないコードスニペットの問題を示していると思います。

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

次に、コードを美化しました...

#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

{}ここに変更do/endすると、エラーが発生し、そのメソッドtranslateは存在しません...

なぜこれが起こるのかは、ここで複数指摘されています-優先順位。しかし、ここにブレースをどこに置くのですか?(@The Tin Man:私はいつものように中括弧を使用しますが、ここでは...監視されています)

だからすべての答えは

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

「しかし、中括弧/優先順位に注意してください!」なしで使用され場合は、間違っています

再び:

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

そして

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

(extendの結果がブロックで何をするか...)

したがって、do / endを使用する場合は、次のように使用します。

#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

個人的な偏見に帰着します。大多数のバックグラウンド言語がdo / end規則よりも使用するため、多くの開発者にとって理解しやすいので、私はdo / endブロックよりも中括弧を好んでいます。そうは言っても、本当の鍵はショップ内で合意に達することです。do/ endが6/10の開発者によって使用されている場合、EVERYONEが使用するよりも、6/10が中括弧を使用している場合は、そのパラダイムに固執してください。

チーム全体がコード構造をより迅速に識別できるように、パターンを作成することがすべてです。


3
好み以上です。2つの間のバインディングは異なり、診断が難しい問題を引き起こす可能性があります。いくつかの回答が問題の詳細を示しています。
ティンマン

4
他の言語の背景を持つ人々の観点から-この場合、「理解可能性」の違いは非常に小さいので、検討する必要はありません。(それは私の反対投票ではありませんでした。)
ケルビン


0

私の個人的なスタイルは、{... }do...のend選択という厳格なルールよりも可読性を強調することです。私の読みやすさの考え方は次のとおりです。

[ 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

複数行のネストされたブロックなどのより複雑な構文では、{... }およびdo... end区切り文字を散在させて、最も自然な結果を得るようにします。

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
}

厳密なルールの欠如は、プログラマーごとにわずかに異なる選択肢を生み出す可能性がありますが、読みやすさのケースバイケースの最適化は、主観的ではありますが、厳密なルールの順守に対する正味の利益だと思います。


1
Pythonから来ているので、RubyをスキップしてWispを学ぶべきかもしれません。
Cees Timmerman、2017

Rubyはかつてそこにある最高の実用的な言語だと信じていました。スクリプト言語と言えばいいですか。今日、あなたも同様に学習を終えるだろうと思いますBrainfuck
Boris Stitnicky 2017

0

私はここで最も投票された答えの例を取り上げました。いう、

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

array.rbの内部実装を確認すると、mapメソッドのヘッダーに次のように記述されています

Invokes the given block once for each element of self.

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

すなわち、それは、コードのブロックを受け入れる-の間には何でもやる端が歩留まりとして実行されます。その後、結果は再び配列として収集されるため、完全に新しいオブジェクトが返されます。

したがって、do-endブロックまたは{}に遭遇したときは常に、コードのブロックがパラメーターとして渡され、内部で実行されるというマインドマップを用意してください。


-3

3つ目のオプションがあります。インデントから独自の行で「終了」を推測するプリプロセッサを記述します。簡潔なコードを好む深い思想家はたまたま正しい。

さらに良いことに、ルビーをハックしてフラグを立てます。

もちろん、「戦いを選ぶ」最も簡単な解決策は、一連の端がすべて同じ行に表示されるというスタイルの規則を採用し、構文の色付けでそれらをミュートすることです。編集を簡単にするために、エディタースクリプトを使用してこれらのシーケンスを展開/折りたたむことができます。

私のルビコード行の20%から25%は、自分の行で「終了」しており、すべてインデントの慣例によって簡単に推論されています。Rubyは、最も成功したlispのような言語です。すべての恐ろしい括弧がどこにあるかを尋ねる人々がこれに異議を唱えるとき、私は彼らに余分な「終わり」の7行で終わるルビ関数を示します。

ほとんどの括弧を推測するために、lispプリプロセッサーを使用して何年もコードを作成しました:バー '|' 行の終わりで自動クローズするグループを開き、ドル記号「$」が空のプレースホルダーとして機能しました。空のプレースホルダーには、グループ化を推測するのに役立つ記号はありません。これはもちろん宗教戦争の領土です。括弧なしのLisp / schemeは、すべての言語の中で最も詩的です。それでも、構文の色分けを使用して括弧を静かに消す方が簡単です。

私はまだHaskellのプリプロセッサを使用してコーディングし、ヒアドキュメントを追加し、デフォルトですべてのフラッシュ行をコメントとして、すべてコードとしてインデントしています。言語に関係なく、コメント文字は嫌いです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.