如何用Ruby方式编写嵌套搜索?

时间:2014-02-22 19:00:49

标签: ruby coding-style

我正在尝试编写快速简洁的代码。我很感激您的想法,哪个是编写以下代码的最佳方式,以及原因:

选项#1

def get_title
  title = check_in_place_one
  if title.empty?
    title = check_in_place_two
    if title.empty?
      title = check_in_place_three
    end
  end
  return title
end

选项#2

def get_title
  title = check_in_place_one
  title = check_in_place_two unless !title.empty?
  title = check_in_place_three unless !title.empty?
  return title
end

我认为选项#1更好,因为如果title找到check_in_place_one,我们会测试title.empty?一次,然后跳过方法中的其余代码并返回。但是,它看起来太长了。选项#2看起来更好,但是处理title.empty?一个额外的时间,并且在返回之前不必要的时间。另外,我错过了第三种选择吗?

5 个答案:

答案 0 :(得分:2)

从性能上看,代码的两个版本之间没有区别(除了可能来自解析的非常小的差异,这应该是可忽略的)。控制结构是相同的。

从可读性来看,如果你可以摆脱嵌套,那么这样做会更好。你的第二个选择更好。

通常最好摆脱任何不需要进一步处理的情况。这是由return完成的。

def get_title
  title = check_in_place_one
  return title unless title.empty?
  title = check_in_place_two
  return title unless title.empty?
  title = check_in_place_three
  return title
end

上面代码中的最后一个title =return是多余的,但我将它们放在那里以保持一致性,这提高了可读性。

您可以使用tap进一步压缩代码,如下所示:

def get_title
  check_in_place_one.tap{|s| return s unless s.empty?}
  check_in_place_two.tap{|s| return s unless s.empty?}
  check_in_place_three
end

tap是一种非常快速的方法,与instance_eval不同,它的性能损失通常是可以忽略的。

答案 1 :(得分:2)

以下方法可用于任意数量的顺序测试。而且,它完全是一般的。可以更改返回条件,可以轻松地将参数分配给测试方法等。

tests = %w[check_in_place_one check_in_place_two check_in_place_three]

def do_tests(tests)
  title = nil # Define title outside block
  tests.each do |t|
    title = send(t)
    break unless title.empty?
  end
  title
end

我们试一试:

def check_in_place_one
  puts "check 1"
  []
end

def check_in_place_two
  puts "check 2"
  ''
end

def check_in_place_three
  puts "check 3"
  [3]
end

do_tests(tests) #=> [3]
check 1
check 2
check 3
  #=> [3]

现在更改其中一个测试:

def check_in_place_two
  puts "check 2"
  'cat'
end

do_tests(tests) #=> 'cat'
check 1
check 2
  #=> "cat"         

如果有更多的测试,将它们放入一个类include d的模块中可能会很方便。混合方法的行为与您为类定义的方法相同。例如,他们可以访问实例变量。我将用第一种测试方法的定义来证明。我们可能希望将测试方法设为私有。我们可以这样做:

module TestMethods
  private

  def check_in_place_one
    puts "@pet => #{@pet}"
    puts "check 1"
    []
  end

  def check_in_place_two
    puts "check 2"
    ''
  end

  def check_in_place_three
    puts "check 3"
    [3]
  end
end

class MyClass  
  @@tests = TestMethods.private_instance_methods(false)
  puts "@@tests = #{@@tests}"

  def initialize
    @pet = 'dog'
  end

  def do_tests
    title = nil # Define title outside block
    @@tests.each do |t|
      title = send(t)
      break unless title.empty?
    end
    title
  end   

  include TestMethods       
end

解析代码时会显示以下内容:

@@tests = [:check_in_place_one, :check_in_place_two, :check_in_place_three]

现在我们执行测试:

MyClass.new.do_tests #=> [3]
@pet => dog
check 1
check 2
check 3 

确认测试方法是私有的:

MyClass.new.check_in_place_one
  #=> private method 'check_in_place_one' called for...'

使用模块的优点是您可以添加,删除,重新排列和重命名测试方法,而无需对类进行任何更改。

答案 2 :(得分:1)

嗯,这里有一些其他选择。

选项1:首先返回非空检查。

def get_title
  return check_in_place_one   unless check_in_place_one.empty?
  return check_in_place_two   unless check_in_place_two.empty?
  return check_in_place_three
end

选项2:带有短路评估的辅助方法。

def get_title
  check_place("one") || check_place("two") || check_place("three")
end

private

def check_place(place)
  result = send("check_in_place_#{place}")
  result.empty? ? nil : result
end

选项3:检查所有地点,然后找到第一个非空的地方。

def get_title
  [
    check_in_place_one,
    check_in_place_two,
    check_in_place_three,
  ].find{|x| !x.empty? }
end

答案 3 :(得分:0)

虽然您使用unless !title.empty?进行了360度转弯,但选项2看起来不错。您可以将其缩短为if title.empty?,因为unless等同于if !,因此执行unless !会将您带回if

如果您只有3个地方可以查看,那么选项2是最好的。它简洁,简洁,易于阅读(一旦修复上述的麻烦,就会更容易)。如果您可以添加到您寻找标题的地方,您可以获得更多动态:

def get_title(num_places = 4)
  title, cur_place = nil, 0
  title = check_in_place(cur_place += 1) while title.nil? && cur_place < num_places
end

def check_in_place(place_num)
  # some code to check in the place # given by place_num
end

棘手的问题是其中有一个while。发生的事情是while会检查表达式title.nil? && cur_place < num_places并返回true,因为title仍为零且0小于4。

然后我们将调用check_in_place方法并传入值1,因为cur_place += 1表达式将其值增加到1然后返回它,将其提供给方法(假设我们想要当然,开始检查到位1)。

这将重复,直到check_in_place返回非零值,或者我们用完了要检查的地方。

现在,get_title方法更短,并且会自动支持在num_places位置进行检查,因为您的check_in_place方法也可以查看更多地方。

还有一件事,你可能想给https://codereview.stackexchange.com/看看,这个问题似乎很适合它。

答案 4 :(得分:0)

我认为没有任何理由变得过于聪明:

def get_title
  check_in_place_one || check_in_place_two || check_in_place_three
end

编辑:如果check_in_place_X方法确实在失败时返回空字符串,那么让它们返回nil会更好(并且更具惯用性)。它不仅允许像上面的代码那样进行真正的比较,return ""会导致构造一个新的和不必要的String对象。