Помогите реорганизовать этот отвратительный оператор Ruby if/else
Итак, у меня есть это большое, волосатое утверждение if/else. Я передаю ему номер отслеживания, а затем он определяет, какой тип номера отслеживания он имеет.
Как я могу упростить эту вещь? В частности, требуется уменьшить количество строк кодов.
if num_length < 8
tracking_service = false
else
if number[1, 1] == 'Z'
tracking_service = 'ups'
elsif number[0, 1] == 'Q'
tracking_service = 'dhl'
elsif number[0, 2] == '96' && num_length == 22
tracking_service = 'fedex'
elsif number[0, 1] == 'H' && num_length == 11
tracking_service = 'ups'
elsif number[0, 1] == 'K' && num_length == 11
tracking_service = 'ups'
elsif num_length == 18 || num_length == 20
check_response(number)
else
case num_length
when 17
tracking_service = 'dhlgm'
when 13,20,22,30
tracking_service = 'usps'
when 12,15,19
tracking_service = 'fedex'
when 10,11
tracking_service = 'dhl'
else
tracking_service = false
end
end
end
Да, я знаю. Это неприятно.
Ответы
Ответ 1
Попробуйте это. Я переписал его с помощью case
и регулярных выражений. Я также использовал :symbols
вместо "strings"
для возвращаемых значений, но вы можете изменить это.
tracking_service = case number
when /^.Z/ then :ups
when /^Q/ then :dhl
when /^96.{20}$/ then :fedex
when /^[HK].{10}$/ then :ups
else
check_response(number) if num_length == 18 || num_length == 20
case num_length
when 17 then :dhlgm
when 13, 20, 22, 30 then :usps
when 12, 15, 19 then :fedex
when 10, 11 then :dhl
else false
end
end
Ответ 2
В зависимости от того, является ли код отслеживания рубиновым объектом, вы также можете указать в нем определение класса:
class TrackingCode < String
# not sure if this makes sense for your use case
def ups?
self[1,1] == 'Z'
end
def dhl?
self[0,1] == 'Q'
end
def fedex?
self.length == 22 && self[0, 2] == '96'
end
# etc...
end
Тогда ваше условие будет выглядеть следующим образом:
if number.ups?
# ...
elsif number.dhl?
# ...
elseif number.fedex?
end
Одно упрощенное условие, в котором вы работаете с подразумеваемой функцией кода отслеживания. Точно так же, если бы вы взяли цикл, ваш цикл также был бы чище:
%w(ups? dhl? fedex?).each do |is_code|
return if number.send(is_code)
end
или даже:
%w(ups? dhl? fedex?).each do |is_code|
yield if number.send(is_code)
end
Ответ 3
Этот метод выглядит так, как будто он написан для скорости. Вы можете использовать minhash как замену, но я думаю, что код довольно чистый и не требует рефакторинга. Рубисты, как правило, испытывают отвращение от ненужной структуры, но часто им нужно моделировать ситуации в реальном мире и/или обеспечивать повышение производительности. Ключевое слово должно быть ненужным.
Ответ 4
Я считаю, что это достаточно сложно, чтобы заслужить свой собственный метод.
BTW, если длина равна 20, тогда исходная функция возвращает все, что возвращается check_response(n)
, но затем пытается (и всегда будет терпеть неудачу) возвращать 'usps'
.
@lenMap = Hash.new false
@lenMap[17] = 'dhlgm'
@lenMap[13] = @lenMap[20] = @lenMap[22] = @lenMap[30] = 'usps'
@lenMap[12] = @lenMap[15] = @lenMap[19] = 'fedex'
@lenMap[10] = @lenMap[11] = 'dhl'
def ts n
len = n.length
return false if len < 8
case n
when /^.Z/
return 'ups'
when /^Q/
return 'dhl'
when /^96....................$/
return 'fedex'
when /^[HK]..........$/
return 'ups'
end
return check_response n if len == 18 or len == 20
return @lenMap[len]
end
# test code...
def check_response n
return 'check 18/20 '
end
%w{ 1Zwhatever Qetcetcetc 9634567890123456789012 H2345678901
K2345678901 hownowhownowhownow hownowhownowhownow90
12345678901234567
1234567890123
12345678901234567890
1234567890123456789012
123456789012345678901234567890
123456789012
123456789012345
1234567890123456789
1234567890
12345678901 }.each do |s|
puts "%32s %s" % [s, (ts s).to_s]
end
Ответ 5
В то время как дольше, чем решение jtbandes, вам может понравиться это, поскольку оно немного более декларативно:
class Condition
attr_reader :service_name, :predicate
def initialize(service_name, &block)
@service_name = service_name
@predicate = block
end
end
CONDITIONS = [
Condition.new('ups') { |n| n[1] == 'Z' },
Condition.new('dhl') { |n| n[0] == 'Q' },
Condition.new('fedex') { |n| n[0..1] == '96' && n.size == 22 },
Condition.new('ups') { |n| n[0] == 'H' && n.size == 11 },
Condition.new('ups') { |n| n[0] == 'K' && n.size == 11 },
Condition.new('dhlgm') { |n| n.size == 17 },
Condition.new('usps') { |n| [13, 20, 22, 30].include?(n.size) },
Condition.new('fedex') { |n| [12, 15, 19].include?(n.size) },
Condition.new('dhl') { |n| [10, 11].include?(n.size) },
]
def tracking_service(tracking_number)
result = CONDITIONS.find do |condition|
condition.predicate.call(tracking_number)
end
result.service_name if result
end
Я не рассматривал вызов метода check_response здесь, так как я чувствую, что вы, вероятно, должны справиться с этим в другом месте (при условии, что он делает что-то другое, кроме как вернуть имя службы отслеживания).