루비에서 블록에 대한 do..end vs 중괄호


154

나는 do..end를 사용해서는 안된다는 것을 적극적으로 확신시키는 동료가 있습니다. 루비에서 여러 줄 블록을 정의하기 위해 중괄호를 대신 사용하십시오.

나는 짧은 원 라이너에 곱슬 중괄호 만 사용하고 다른 모든 것을 위해 끝내야합니다. 그러나 나는 결의를 얻기 위해 더 큰 공동체에 손을 뻗을 것이라고 생각했다.

그래서 그것은 무엇이며 왜 그렇습니까? (일부 머스트 코드의 예)

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
궁금한 점이 있지만 중괄호 사용에 대한 동료의 주장은 무엇입니까? 논리적 인 것보다 개인적인 취향처럼 보입니다.
Daniel Lee

6
토론을 원한다면 커뮤니티 위키로 만드십시오. 귀하의 질문에 : 그것은 단지 개인적인 스타일입니다. 그들이 더 못 생겨 보이기 때문에 중괄호를 선호합니다.
halfdan

7
선호하는 것은 아니며 문체 적 이유 외에도 다른 것을 사용하는 구문상의 이유가 있습니다. 몇 가지 답변이 그 이유를 설명합니다. 올바른 것을 사용하지 않으면 매우 미묘한 버그가 발생하여 다른 "스타일"을 선택하여 메소드에 랩핑 괄호를 사용하지 않는 경우 찾기가 어렵습니다.
Tin Man

1
"가장자리 조건"은 자신에 대해 모르는 사람들을 몰래 버리는 습관이 있습니다. 방어 적 코딩이란 사례에 부딪 칠 가능성을 최소화하는 코딩 스타일을 사용하기로 결정하는 것을 포함하여 많은 것을 의미합니다. 당신은 알고 있을지 모르지만, 두 사람이 부족 지식을 잊어 버린 후가 아닐 수도 있습니다. 불행히도 회사 환경에서 발생하는 경향이 있습니다.
Tin Man

1
@tinman 이러한 유형의 에지 조건은 TDD에 의해 처리됩니다. 이것이 Rubist가 이와 같은 코드를 작성하고 그러한 오류가 코드에 존재하지 않는다는 것을 알고 밤새 휴식을 취할 수있는 이유입니다. TDD 또는 BDD로 개발할 때 이러한 실수가 우선적으로 발생하면 화면에 표시된 빨간색이 "부족 지식"을 상기시킵니다. 일반적으로 해결책은 표준 규칙 내에서 완전히 수용 가능한 몇 개의 파렌을 추가하는 것입니다. :)
Blake Taylor

답변:


246

일반적인 규칙은 여러 줄 블록에 do..end를 사용하고 한 줄 블록에 중괄호를 사용하는 것입니다. 그러나이 예에서 설명 할 수있는 둘 사이에는 차이가 있습니다.

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보다 우선 순위가 높으므로 사용하려는 것을 결정할 때 명심하십시오.

추신 : 선호도를 개발하는 동안 명심해야 할 또 하나의 예입니다.

다음 코드 :

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
메서드 매개 변수 주위에 항상 괄호를 사용하여 전체 문제를 회피하는 것에 투표합니다.
Tin Man

@ The Tin Man : 레이크에서도 괄호를 사용합니까?
앤드류 그림

9
그것은 구식 프로그래밍 습관입니다. 언어가 괄호를 지원하면 사용합니다.
Tin Man

8
우선 순위 문제를 지적하면 +1입니다. 내가 시도하고 변환 할 때 때때로 예기치 않은 예외가 발생합니다. {}까지 종료
idrinkpabst

1
puts [1,2,3].map do |k| k+1; end열거 자, 즉 한 가지만 인쇄하는 이유는 무엇입니까? 풋 즉 여기에 두 개의 인수를 전달되지 않습니다 [1,2,3].mapdo |k| k+1; end?
ben

55

에서 루비 프로그래밍 :

중괄호는 우선 순위가 높습니다. 우선 순위가 낮습니다. 메소드 호출에 괄호로 묶이지 않은 매개 변수가있는 경우 블록의 중괄호 양식은 전체 호출이 아닌 마지막 매개 변수에 바인드됩니다. do 양식은 호출에 바인딩됩니다.

그래서 코드

f param {do_something()}

param코드를 작성하는 동안 블록을 변수에 바인딩

f param do do_something() end

블록을 함수에 바인딩합니다 f.

그러나 함수 인수를 괄호로 묶는 경우 문제가되지 않습니다.


7
+1 "그러나 함수 인수를 괄호로 묶으면 이것이 문제가되지 않습니다.". 나는 이것을 통역사의 기회와 변덕에 의존하기보다는 습관의 문제로한다. :-)
Tin Man

4
@theTinMan, 통역사가 파서에서 우연을 사용할 경우 새로운 통역사가 필요합니다.
Paul Draper

@PaulDraper-실제로 모든 언어가이 문제에 동의하는 것은 아닙니다. 그러나 (필자가 생각한다) 파 렌스가 "직무를 수행하지"않는 것은 매우 드물기 때문에 파 렌스를 사용하면 언어 디자이너가 내린 "무작위"결정을 기억하지 않아도된다. .
dlu

15

이것에 대한 몇 가지 견해가 있습니다. 그것은 실제로 개인적인 취향의 문제입니다. 많은 루비 스트들이 당신에게 접근합니다. 그러나 일반적인 두 가지 스타일은 항상 하나 또는 다른 스타일을 사용하거나 {}값을 반환 do ... end하는 블록 과 부작용을 위해 실행되는 블록에 사용하는 것입니다.


2
개인적인 취향의 문제는 아닙니다. 메소드 매개 변수가 괄호로 묶이지 않으면 매개 변수 바인딩으로 미묘한 버그가 발생할 수 있습니다.
Tin Man

1
나는 현재 마지막 옵션을하고 있지 않지만 일부 사람들은 그 접근법을 취한다고 언급하여 +1하고 있습니다.
앤드류 그림

Avdi Grimm은 블로그 게시물에서 이것을 옹호합니다 : devblog.avdi.org/2011/07/26/…
alxndr

8

중괄호에는 한 가지 주요 이점이 있습니다. 많은 편집자가 편집하기 훨씬 쉬운 시간을 가지므로 특정 유형의 디버깅이 훨씬 쉬워집니다. 한편, "do ... end"라는 키워드는 일치시키기가 매우 어렵습니다. 특히 "end"도 "if"와 일치하기 때문입니다.


예를 들어 RubyMine, do ... end기본적으로 키워드를 강조 표시
ck3g

1
중괄호를 선호하지만 편집자가 2/3 문자열과 단일 문자를 찾는 데 어려움을 겪을 이유가 없습니다. 대부분의 편집자가 이미 중괄호와 일치한다는 주장이 있지만 do ... end언어 별 논리가 필요한 특수한 경우입니다.
Chinoto Vokro

7

내가 본 가장 일반적인 규칙 (가장 최근에는 Eloquent Ruby에서 )은 다음과 같습니다.

  • 여러 줄 블록이면 do / end를 사용하십시오.
  • 단일 행 블록 인 경우 {}를 사용하십시오.

7

할 일 / 종료 투표


컨벤션은 여러 줄 do .. end{ ... }한 줄짜리를위한 것입니다.

그러나 나는 do .. end더 낫습니다. 따라서 하나의 라이너가 있으면 do .. end어쨌든 사용 하지만 세 줄로 do / end에 평소와 같이 형식을 지정합니다. 이것은 모든 사람을 행복하게 만듭니다.

  10.times do 
    puts ...
  end

한 가지 문제 { }는 시가 모드 적대적이라는 것입니다 (전체 메소드 호출이 아니라 마지막 매개 변수에 단단히 바인딩되므로 메소드 구문을 포함해야합니다). 그것들은 문장 그룹이 아니며 가독성을 위해 해시 상수와 충돌합니다.

또한 { }C 프로그램에서 충분히 보았습니다 . 평소와 같이 루비의 방식이 더 좋습니다. if블록 에는 정확히 한 가지 유형이 있으며 , 되돌아 가서 명령문을 복합 명령문으로 변환 할 필요가 없습니다.


2
"C 프로그램에서 {}를 충분히 보았습니다"그리고 Perl. 루비의 선 (zen)과 같은 코딩 스타일을 옹호하기 위해 여러 문장을 +1했습니다. :-)
Tin Man

1
엄밀히 말하면, 또 다른 "if"구문이 있습니다. 접미사 "if"( do_stuff if x==1)는 정규 구문으로 변환하기가 귀찮습니다. 그러나 그것은 또 다른 토론입니다.
Kelvin

prefix if, postfix if, postfix unless(일종의 ifif-not) 및로 한 줄 ifthen있습니다. 루비 방식은 C가 제공하는 것보다 매우 간단하고 엄격한 규칙을 따르는 것보다 항상 똑같은 것을 실제로 표현할 수있는 방법을 많이 가지고 있습니다.
Mecki

2
C에서 {}를 좋아한다면 Ruby에서도 사용해야한다는 의미입니까?
Warren Dew

6

영향력있는 루비 스트 부부는 리턴 값을 사용할 때는 중괄호를 사용하고 그렇지 않은 경우에는 행 / 종료를 제안합니다.

http://talklikeaduck.denhaven2.com/2007/10/02/ruby-blocks-do-or-brace (보관소에서)

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

이것은 일반적으로 좋은 습관처럼 보입니다.

나는이 원칙을 조금 수정하여 읽기가 어렵 기 때문에 한 줄에 do / end를 사용하지 않아야한다고 말하고 싶습니다.

전체 메서드 호출 대신 메서드의 최종 매개 변수에 바인딩되므로 중괄호를 사용하면 더 신중해야합니다. 이를 피하려면 괄호를 추가하십시오.


2

사실 그것은 개인적인 취향이지만, 지난 3 년 동안 나의 루비 경험에서 내가 배운 것은 루비가 그 스타일을 가지고 있다는 것입니다.

예를 들어, JAVA background에서 오는 경우 부울 메소드의 경우 다음을 사용할 수 있습니다.

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이 존재하지 않습니다 ...

왜 이런 일이 발생하는지는 둘 이상의 우선 순위로 지적됩니다. 그러나 여기서 중괄호를 어디에 둘 것인가? (@ 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

(확장의 결과가 블록으로 무엇을하는지 ...)

따라서 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 규칙을 통해 사용합니다. 실제로 핵심은 상점 내에서 계약을 맺는 것입니다 .6 / 10 개발자가 6/10 개발자보다 do / end를 사용하는 경우 6/10 중괄호를 사용하는 경우 해당 패러다임을 고수하십시오.

팀 전체가 코드 구조를 더 빨리 식별 할 수 있도록 패턴을 만드는 것이 전부입니다.


3
그것은 선호 이상입니다. 둘 사이의 바인딩은 다르며 진단하기 어려운 문제를 일으킬 수 있습니다. 여러 답변이 문제를 자세히 설명합니다.
Tin Man

4
다른 언어 배경을 가진 사람들의 관점에서 볼 때,이 경우 "이해 가능성"의 차이가 너무 작아서 고려할 필요가 없다고 생각합니다. (그것은 나의 다운 보트가 아닙니다.)
Kelvin

1

그들 사이에는 미묘한 차이가 있지만 {}는 do / end보다 더 밀접하게 바인딩됩니다.


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
파이썬에서 오면 Ruby를 건너 뛰고 Wisp를 대신 배워야 할 것입니다.
Cees Timmerman

나는 Ruby가장 실용적 언어 라고 믿었 습니다. 스크립팅 언어를 말해야합니까? 오늘, 당신도 똑같이 배우는 것이 좋을 것 같습니다 Brainfuck.
보리스 스티키

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

즉, 코드 블록을 받아들입니다. doend 사이에 있는 모든 것이 yield로 실행됩니다. 그런 다음 결과가 다시 배열로 수집되어 완전히 새로운 객체를 반환합니다.

따라서 do-end 블록 또는 {} 가 발생할 때마다 코드 블록이 매개 변수로 전달되어 내부에서 실행된다는 마인드 맵을 작성하십시오.


-3

세 번째 옵션이 있습니다. 들여 쓰기에서 자체 줄에 "종료"를 추론하는 전처리기를 작성하십시오. 간결한 코드를 선호하는 심층 사상가들은 옳습니다.

더 나은 방법은 루비를 해킹하여 플래그입니다.

물론, "싸움을 고르세요"가장 간단한 해결책은 일련의 끝이 모두 같은 줄에 나타나는 스타일 규칙을 채택하고 구문 색상을 알려주는 것입니다. 쉽게 편집 할 수 있도록 편집기 스크립트를 사용하여 이러한 시퀀스를 확장 / 축소 할 수 있습니다.

내 루비 코드 라인의 20 ~ 25 %는 내 들여 쓰기 규칙에 의해 사소하게 추론 된 자체 라인에서 "종료"되었습니다. 루비는 가장 큰 성공을 거둔 lisp와 같은 언어입니다. 사람들이 이것에 대해 이의를 제기 할 때, 모든 괄호가 어디에 있는지 묻는다면, 나는 그들에게 7 줄의 불필요한 "끝"으로 끝나는 루비 함수를 보여줍니다.

나는 대부분의 괄호를 추론하기 위해 lisp 전처리기를 사용하여 몇 년 동안 코드를 작성했습니다 : A bar '|' 행 끝에서 자동 폐쇄 된 그룹을 열었고 달러 기호 '$'는 빈 자리 표시 자 역할을했으며 그룹을 유추하는 데 도움이되는 기호가 없었습니다. 이것은 물론 종교적 전쟁 영토입니다. 괄호가없는 리스프 / 구성표는 모든 언어 중에서 가장 시적입니다. 그래도 구문 색상 표시를 사용하여 괄호를 간단히 조이는 것이 더 쉽습니다.

나는 여전히 Haskell의 전처리기로 코드를 작성하고 heredocs를 추가하고 모든 플러시 라인을 주석으로 기본 설정하고 모든 것을 코드로 들여 쓰기합니다. 언어에 관계없이 주석 문자를 싫어합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.