Эта статья была первоначально опубликована в моем блоге: Как проверить, определена ли переменная в Ruby
Ruby предоставляет удобное ключевое слово defined?(expression)
, которое проверяет, относится ли выражение
к чему-либо распознаваемому. Выражение может быть объектом, инициализированной переменной, именем метода и т.д. Если Ruby не может разрешить выражение, он возвращает
nil
. В противном случае возвращается строка, описывающая выражение.
Вот несколько примеров использования defined?
с различными типами выражений. Обратите внимание, что переменная, установленная в nil
, все равно инициализируется и распознается ruby.
RSpec.describe 'Defined' do
it 'tests if the local variable is defined' do
name = 'Akshay'
expect(defined? name).to eq('local-variable')
expect(defined? b).to eq(nil)
expect(defined? nil).to eq('nil')
expect(defined? String).to eq('constant')
expect(defined? 1).to eq('expression')
end
it 'ensures that a variable set to nil is still recognized' do
name = nil
expect(defined? name).to eq('local-variable')
end
end
Использование с условным присваиванием
Иногда вы хотите лениво оценить некоторый код только один раз. То есть, ничего не делать, если переменная существует, но инициализировать ее, если нет. Идиоматический подход ruby заключается в использовании оператора ||=
.
def result
@result ||= calculate_result
end
def calculate_result
puts '>>> heavy calculation here.. should happen only once'
100
end
it 'lazy-evaluates the calculate_result operation once' do
expect(result).to eq(100)
expect(result).to eq(100)
end
# Output
>>> heavy calculation here.. should happen only once
Только убедитесь, что вы не используете его с операцией, которая может вернуть nil
или булево значение false
в качестве результата. В противном случае он будет вызывать calculate_result
каждый раз, устраняя преимущество оператора ||=
.
Например, если вы измените метод calculate_result
выше, чтобы он возвращал false
(или nil
) вместо 100, ruby будет вызывать calculate_result
каждый раз, когда вызывается result
.
def calculate_result
puts '>>> heavy calculation here.. should happen only once'
false # or nil
end
# Output
>>> heavy calculation here.. should happen only once
>>> heavy calculation here.. should happen only once
В таких случаях пригодится метод defined?
. Измените метод result
так, чтобы он сначала проверял, определена ли переменная @result
.
def result
return @result if defined? @result
@result = calculate_result
end
Не используйте defined?
для проверки хэш-ключей
Распространенной ошибкой является использование defined?
для проверки того, определен ли хэш-ключ. Например,
def check_hash_key
hash = {}
defined?(hash['key']) ? 'unexpected!' : 'not defined'
end
it 'does not return false for non-existing hash key' do
expect(check_hash_key).to eq('unexpected!')
end
Это потому, что он возвращает строку method
, которую ruby оценивает как true
в булевом выражении.
it 'returns method for non-existing hash key' do
data = {}
expect(defined? data['key']).to eq('method')
end
Идиоматическим решением ruby для проверки существования ключа в хэше является использование любого из следующих методов: has_key?
, key?
, include?
, или member?
.
data = {}
expect(data.key?('key')).to eq(false)
Используйте круглые скобки при использовании defined?
Вы не всегда должны их использовать, но это настоятельно рекомендуется из-за низкого приоритета ключевого слова defined?
. Например, если вы хотите проверить, существует ли переменная и больше ли она нуля, вы можете написать что-то вроде этого.
it 'has low precedence' do
result = 10
expect(defined? result && result > 0).to eq(true)
end
# Fails!
# expected: true
# got: "expression"
Добавление круглых скобок приводит к лучшей проверке, к тому же она очень наглядна. Следующий тест проходит на ура.
it 'has low precedence' do
result = 10
expect(defined? result && result > 0).to eq('expression')
expect(defined?(result) && result > 0).to eq(true)
end