Ruby의 Hash#fetch를 기본값으로 사용하는 이유와 방법

RubyTapas 초기의 또 다른 고전이 있습니다. 원래 2012년 10월에 Episode #11으로 게시되었으며 using fetch as an assertion의 에피소드를 보완합니다. 이 에피소드에서는 기본값에 || 연산자를 사용하는 것과 Hash#fetch를 사용하는 것의 차이점을 파헤칩니다.



감독의 논평: 여기에서 약간의 품질 향상을 볼 수 있습니다. 이 시점에서 나는 내 원래 댓글 색상이 어두운 배경에서 거의 읽을 수 없다는 것을 깨닫고 약간 밝게 했습니다.

그러나 내 음성 해설은 여전히 ​​지루하게 들립니다. 그리고 화면에 새 코드를 입력하는 동안 침묵의 긴 부분에 여전히 만족했습니다. 요즘은 항상 동시 설명과 함께 코딩을 하려고 합니다.

원본 스크립트와 코드를 읽어보세요…


previous episode 에서 #fetchHash 메서드를 사용하여 주어진 해시 키가 존재한다고 주장하는 방법을 살펴보았습니다.

auth = {
  'uid' => 12345,
  'info' => {
  }
}

# ...

email_address = auth['info'].fetch('email')
# ~> -:11:in `fetch': key not found: "email" (KeyError)
# ~> from -:11:in `<main>'



그러나 Hash가 발생시키는 KeyError가 유용한 오류 메시지에 대한 충분한 컨텍스트를 제공하지 않는다면 어떻게 될까요?

가져올 키와 함께 #fetch 메서드는 선택적 블록을 받을 수도 있습니다. 이 블록은 키를 찾을 수 없는 경우에만 평가됩니다.

이를 알면 사용자 지정 예외를 발생시키는 블록을 #fetch에 전달할 수 있습니다.

auth['uid'] # => 12345
auth['info'].fetch('email') do 
  raise "Invalid auth data (missing email)."\
        "See https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema"
end
email_address = auth['info'].fetch('email')
# ~> -:10:in `block in <main>': Invalid auth data (missing email).See https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema (RuntimeError)
# ~> from -:8:in `fetch'
# ~> from -:8:in `<main>'



이제 이 코드가 예기치 않게 누락된 키를 만나면 발생한 예외가 문제와 추가 정보를 찾을 수 있는 위치를 모두 설명합니다.

그러나 #fetch에 대한 블록 인수는 단지 오류를 발생시키기 위한 것이 아닙니다. 예외가 발생하지 않으면 #fetch는 블록의 결과 값을 호출자에게 반환합니다. 즉, #fetch는 기본값을 제공하는 데에도 매우 유용합니다. 예를 들어 아무 것도 지정되지 않은 경우 기본 이메일 주소를 제공할 수 있습니다.

email_address = auth['info'].fetch('email'){ '[email protected]' }
email_address # => "[email protected]"



이제 기본값으로 #fetch를 사용하는 것과 기본값으로 || operator을 사용하는 것의 차이점이 무엇인지 궁금하실 것입니다. 이들은 처음에는 동일하게 보일 수 있지만 실제로는 미묘하지만 중요하게 다른 방식으로 작동합니다. 차이점을 살펴보겠습니다.

다음은 기본값으로 || 연산자를 사용하는 예입니다. 이 코드는 옵션 해시를 수신하고 :logger 키를 사용하여 로거 개체를 찾습니다. 키를 지정하지 않으면 $stdout 에 대한 기본 로거를 생성합니다. 키가 nil 또는 false 인 경우 NullLogger 개체를 대체하여 로깅을 비활성화합니다.

비워두면 잘 작동합니다 Hash .

require 'logger'

class NullLogger
  def method_missing(*); end
end

options = {}
logger = options[:logger] || Logger.new($stdout) 
unless logger
  logger = NullLogger.new
end
logger
# => #<Logger:0x000000030545a8
# @default_formatter=
# #<Logger::Formatter:0x00000003054580 @datetime_format=nil>,
# @formatter=nil,
# @level=0,
# @logdev=
# #<Logger::LogDevice:0x00000003054530
# @dev=#<IO:<STDOUT>>,
# @filename=nil,
# @mutex=
# #<Logger::LogDevice::LogDeviceMutex:0x00000003054508
# @mon_count=0,
# @mon_mutex=#<Mutex:0x000000030544b8>,
# @mon_owner=nil>,
# @shift_age=nil,
# @shift_size=nil>,
# @progname=nil>



그러나 false:logger 의 값으로 전달하면 놀라운 결과가 나타납니다.

options = {logger: false}
logger = options[:logger] || Logger.new($stdout) 
unless logger
  logger = NullLogger.new
end
logger
# => #<Logger:0x000000040bb608
# @default_formatter=
# #<Logger::Formatter:0x000000040bb5e0 @datetime_format=nil>,
# @formatter=nil,
# @level=0,
# @logdev=
# #<Logger::LogDevice:0x000000040bb590
# @dev=#<IO:<STDOUT>>,
# @filename=nil,
# @mutex=
# #<Logger::LogDevice::LogDeviceMutex:0x000000040bb568
# @mon_count=0,
# @mon_mutex=#<Mutex:0x000000040bb518>,
# @mon_owner=nil>,
# @shift_age=nil,
# @shift_size=nil>,
# @progname=nil>



그것은 기본 로거가 아니라 NullLogger 이어야 했습니다!

여기서 무슨 일이 일어났습니까? 기본값으로 ||와 함께 Hash를 사용할 때의 문제는 누락된 키와 값이 nil 또는 false인 키를 구별할 수 없다는 것입니다. 다음은 시연할 코드입니다.

{}[:foo] || :default # => :default
{foo: nil}[:foo] || :default # => :default
{foo: false}[:foo] || :default # => :default



대조적으로 #fetch는 지정된 키가 실제로 누락된 경우에만 기본값에 의지합니다.

{}.fetch(:foo){:default} # => :default
{foo: nil}.fetch(:foo){:default} # => nil
{foo: false}.fetch(:foo){:default} # => false



로거 기본 설정 코드에서 #fetch를 사용하도록 전환하면 의도한 대로 작동합니다.

options = {logger: false}
logger = options.fetch(:logger){Logger.new($stdout)}
unless logger
  logger = NullLogger.new
end
logger
# => #<NullLogger:0x00000003b73858>



누락된 해시 키에 대한 기본값을 제공하려는 경우 명시적으로 제공된 nil 또는 false를 누락된 키와 동일하게 처리할지 여부를 신중하게 고려하십시오. 그렇지 않은 경우 #fetch를 사용하여 기본값을 제공하십시오.

오늘은 여기까지입니다. Happy hacking!

좋은 웹페이지 즐겨찾기