Hash#each
가 항상 람다에 2개의 요소 배열을 일관되게 제공합니다Symbol#to_proc
는 람다와 일관된 서명 메타데이터를 반환합니다OpenStruct
는 필드를 게으르게 평가하지 않습니다Regexp
및Range
인스턴스가 동결됨- Ruby 3.0.2에서 테이블 테스트 실패
- 메서드가 스터빙된 경우 DeprecationToolkit에서 사용되지 않는 것으로 간주되지 않습니다
irb
및rails console
에서의 테스트- RSpec의
with
인수 매처가 간략한 해시 구문에 실패합니다
Ruby 3 주의사항들
이 섹션에서는 Ruby 3 지원 작업 중 발견된 여러 문제들을 문서화하였으며, 이러한 문제들은 미묘한 버그나 이해하기 어려운 테스트 실패를 유발했습니다. 정기적으로 루비 코드를 작성하는 모든 GitLab 기여자들은 이러한 문제에 친숙해지는 것을 권장합니다.
루비 3 언어와 표준 라이브러리의 전체 변경 사항 목록을 보려면 루비 변경 사항을 참조하세요.
Hash#each
가 항상 람다에 2개의 요소 배열을 일관되게 제공합니다
다음 코드 스니펫을 고려해 보세요.
def foo(a, b)
p [a, b]
end
def bar(a, b = 2)
p [a, b]
end
foo_lambda = method(:foo).to_proc
bar_lambda = method(:bar).to_proc
{ a: 1 }.each(&foo_lambda)
{ a: 1 }.each(&bar_lambda)
루비 2.7에서 이 프로그램의 출력은 필요한 인수의 수에 따라 해시 항목을 람다에 제공하는 방식이 다르다는 것을 시사합니다.
# Ruby 2.7
{ a: 1 }.each(&foo_lambda) # 출력: [:a, 1]
{ a: 1 }.each(&bar_lambda) # 출력: [[:a, 1], 2]
루비 3은 이 동작을 일관되게 만들고 항상 해시 항목을 단일 [키, 값]
배열로 제공하려고 시도합니다.
# Ruby 3.0
{ a: 1 }.each(&foo_lambda) # `foo': wrong number of arguments (given 1, expected 2) (ArgumentError)
{ a: 1 }.each(&bar_lambda) # 출력: [[:a, 1], 2]
2.7와 3.0 양쪽에서 작동하는 코드를 작성하려면 다음 옵션을 고려하세요:
- 항상 람다 본문을 블록으로 전달:
{ a: 1 }.each { |a, b| p [a, b] }
. - 람다 인수를 해체:
{ a: 1 }.each(&->((a, b)) { p [a, b] })
.
우리는 항상 블록을 명시적으로 전달하고 블록 매개변수로 두 개의 필수 인수를 선호합니다.
더 자세한 정보는 루비 이슈 12706를 참조하세요.
Symbol#to_proc
는 람다와 일관된 서명 메타데이터를 반환합니다
루비에서 일반적으로 사용되는 관용구 중 하나는 &:<symbol>
을 사용하여 Proc
객체를 얻고 이를 고차 함수에 전달하는 것입니다.
[1, 2, 3].each(&:to_s)
루비는 &:<symbol>
을 Symbol#to_proc
로 변환합니다. 여기에서는 메서드 _수신자_를 첫 번째 인수(여기서는 Integer
)로 호출하고 모든 메서드 _인수_를 나머지 인수로 호출합니다.
이 동작은 루비 2.7과 3.0에서 동일하게 작동합니다. 루비 3가 다른 점은 이 Proc
객체를 캡처하고 그 호출 서명을 검사할 때 입니다.
이는 DSL을 작성하거나 메타 프로그래밍의 다른 형태를 사용할 때 자주 발생합니다.
p = :foo.to_proc # 일반적으로 `&:foo`를 통해 이루어집니다
# Ruby 2.7: 출력 [[:rest]] (-1)
# Ruby 3.0: 출력 [[:req], [:rest]] (-2)
puts "#{p.parameters} (#{p.arity})"
루비 2.7은 이 Proc
객체에 대해 필수 매개변수가 없고 선택적 매개변수가 하나라고 보고하는 반면, 루비 3은 이 Proc
객체에 대해 한 개의 필수 매개변수와 하나의 선택적 매개변수가 있다고 보고합니다. 루비 2.7은 잘못되었습니다: 첫 번째 인수는 항상 전달해야 하며, 이는 Proc
객체가 나타내는 메서드의 수신자이며, 메서드는 수신자 없이 호출될 수 없습니다.
루비 3은 이를 수정하였으며, Proc
객체의 인수 수나 매개변수 목록을 테스트하는 코드는 이제 실패할 수 있으며 업데이트 되어야 합니다.
더 자세한 정보는 루비 이슈 16260를 참조하세요.
OpenStruct
는 필드를 게으르게 평가하지 않습니다
루비 3에서 OpenStruct
구현이 일부 재작성되어 동작이 변경되었습니다. 루비 2.7에서 OpenStruct
는 메서드에 처음 액세스할 때 메서드를 게으르게 정의합니다. 그에 반해 루비 3.0에서는 초기화기에서 이러한 메서드를 즉시 정의하며, 이는 OpenStruct
에서 상속하고 이러한 메서드를 재정의하는 클래스들을 망가뜨릴 수 있습니다.
이러한 이유로 OpenStruct
를 상속하지 마십시오. 이상적으로는 아예 사용하지 않는 것이 좋습니다. OpenStruct
은 문제가 있다고 여겨집니다. 새 코드를 작성할 때는 더 단순하지만 유연성은 떨어지는 Struct
를 대신 사용하세요.
Regexp
및 Range
인스턴스가 동결됨
루비 3에서는 더 이상 Regexp
또는 Range
인스턴스를 명시적으로 동결할 필요가 없습니다. 루비 3는 생성 시 자동으로 이들을 동결합니다.
이에 미묘한 부작용이 있습니다. 이러한 유형에 대한 메서드 호출을 踰 성하는 테스트는 이제 오류로 실패하게 됩니다. 왜냐하면 RSpec은 동결된 객체에 대해 메서드 호출을 스텁할 수 없기 때문입니다.
# Ruby 2.7: 작동
# Ruby 3.0: 오류: "can't modify frozen object"
allow(subject.function_returning_range).to receive(:max).and_return(42)
영향을 받는 테스트를 다시 작성하여 동결된 객체에 대한 메서드 호출을 스텁하는 것을 피하세요. 위의 예제는 다음과 같이 다시 작성할 수 있습니다:
# 모든 루비 버전에서 작동
allow(subject).to receive(:function_returning_range).and_return(1..42)
Ruby 3.0.2에서 테이블 테스트 실패
Ruby 3.0.2에는 정수 값으로 이루어진 테이블 값이 있는 테이블 테스트가 실패하는 알려진 버그가 있습니다. 그 원인은 이슈 337614에 문서화되어 있습니다. 이 문제는 루비에서 수정되었으며 수정 사항은 루비 3.0.3에 포함될 예정입니다.
이 문제는 미패치된 Ruby 3.0.2를 실행하는 사용자에게만 영향을 미치며, 이는 루비를 수동으로 설치하거나 asdf
와 같은 도구를 사용할 때 발생할 수 있습니다. gitlab-development-kit (GDK)
사용자들도 이 문제의 영향을 받습니다.
빌드 이미지는 해당 버그를 해결하는 패치 세트를 포함하고 있으므로 영향을 받지 않습니다.
메서드가 스터빙된 경우 DeprecationToolkit에서 사용되지 않는 것으로 간주되지 않습니다
우리는 deprecation_toolkit
을 Ruby 2에서 사용되지 않는 기능을 사용할 때 빠르게 실패하도록 의존하고 Ruby 3에서 제거된 것을 사용하고 있습니다.
Ruby 2에서 Ruby 3으로의 전환 중에 발생하는 일반적인 문제는 Ruby 3.0에서 위치 및 키워드 인수의 분리와 관련이 있습니다.
불행하게도 테스트에서 이러한 메서드를 스터빙한 경우, 사용되지 않는 것으로 간주되지 않습니다.
우리는 deprecation_toolkit
을 통해 이 경고에 대한 자동 검출을 실행하지만, 이는 Kernel#warn
이 경고를 발생시키는 것을 전제로 하므로 이 호출을 스터빙하면 deprecation_toolkit
은 경고를 볼 수 없게 됩니다.
구현을 스터빙하면 해당 경고가 제거되어 우리는 이를 감지할 수 없으므로 빌드는 성공적으로 완료됩니다.
자세한 내용은 issue 364099를 참조하십시오.
irb
및 rails console
에서의 테스트
또 다른 함정은 irb
/rails c
에서 테스트하면 사용되지 않는 경고가 억제됩니다.
왜냐하면 irb
에서 Ruby 2.7.x에는 버그가 있어 사용되지 않는 경고가 표시되지 않기 때문입니다.
코드를 작성하고 코드 리뷰를 수행할 때 f({k: v})
형식의 메서드 호출에 특별한 주의를 기울이세요.
Ruby 2에서는 f
가 Hash
또는 키워드 인수를 취할 때 유효하지만, Ruby 3에서는 f
가 Hash
를 취할 때만 유효합니다.
Ruby 3의 규정에 따르면, f
가 키워드 인수를 취할 경우 다음 중 하나로 변경되어야 합니다:
f(**{k: v})
f(k: v)
RSpec의 with
인수 매처가 간략한 해시 구문에 실패합니다
Ruby 3에서는 키워드 인수(“kwargs”)가 Ruby 3의 핵심 개념이기 때문에 키워드 인수는 더 이상 내부 Hash
인스턴스로 변환되지 않습니다. 이로 인해 RSpec 메서드 인수 매처가 위치 옵션 해시 대신 kwargs를 취하는 경우에 실패합니다:
def m(options={}); end
expect(subject).to receive(:m).with(a: 42)
Ruby 3에서 이 기대값은 다음과 같은 오류가 발생합니다:
Failure/Error:
#<subject> received :m with unexpected arguments
expected: ({:a=>42})
got: ({:a=>42})
이는 RSpec이 여기에서 kwargs 인수 매처를 사용하고 있지만 메서드가 해시를 취하기 때문에 발생합니다.
이는 a: 42
가 먼저 해시로 변환되어 Ruby 2에서 작동하며 RSpec은 해시 인수 매처를 사용하게 됩니다.
해결책은 간략한 구문을 사용하지 않고 메서드가 옵션 해시를 취할 때마다 실제 Hash
를 전달하는 것입니다:
# 키-값 쌍 주위의 중괄호에 유의하십시오.
expect(subject).to receive(:m).with({ a: 42 })
자세한 정보는 RSpec의 공식 이슈 보고서를 참조하십시오.