Ruby默认赋值(|| =)vs Rescuing error

时间:2017-06-12 05:14:43

标签: ruby if-statement

由于a ||= 1相当于a || a = 1,因此可以说这是同义词糖:

if a.nil?
  a = 1
end

同样,假设session是类似哈希的对象,则包含以下内容:

def increment_session_counter
  session[:counter] ||= 0
  session[:counter] += 1
end

相当于:

def increment_session_counter
  if session[:counter].nil?
    session[:counter] = 0
  end
  session[:counter] += 1
end

这是否意味着每次在if的原始定义中都会执行隐式increment_session_counter语句?由于session[:counter]很可能只是第一次nil(即&lt; <1%的时间),我觉得以下代码更好,因为隐式{{1} }每次都不会被解雇:

if

这种代码在这种意义上是否更好?

话虽如此,我不知道如何在ruby中实现def increment_session_counter session[:counter] += 1 rescue NoMethodError session[:counter] = 1 end ,以及它是否与可能带来的微小优化有关。

3 个答案:

答案 0 :(得分:3)

捕获错误是一个非常聪明的主意,但它比使用||=更难阅读。但更容易是在第一次创建哈希时设置初始值:

@session = {:counter => 0}
def increment_session_counter
  @session[:counter] += 1
end

当事先不知道密钥时,这不起作用:

def increment_user_counter(username)
  @session[username] ||= 0
  @session[username]  += 1
end

但是,在这种情况下,你假设计数器的价值仅为零一次就会陷入危险之中。事实上,自many distributions follow the power law起,它可能是最常见的1。

因此,如果您事先知道可能的值,则最好在程序或类初始化时将它们设置为零,并且不需要进行默认值检查。如果您事先不知道所有可能的值,您可能会经常发现需要||=语句。在假设checking for nil is substantially cheaper的情况下,使用异常的情况可能很少。

答案 1 :(得分:3)

session[:counter] += 1做了三件事:

  1. 获取值(Hash#[]
  2. 增加值(Integer#+
  3. 存储递增的值(Hash#[]=
  4. 这很方便,但它的简洁也使它变得不灵活。

    如果您将步骤分开,则提供默认设置会更容易:

    def increment_session_counter
      session[:counter] = session.fetch(:counter, 0) + 1
    end
    

答案 2 :(得分:1)

我使用以下代码尝试了一些基准测试。 这是一个rails应用程序。 Rails v.5.1.1 / Ruby v2.4.1

最初的目的只是计算某些控制器的任何show操作中的访问次数,如下所示:

include SessionCount
...
def show
  ...
  @counter = increment_session_counter
  ...

然后我可以在相关视图中显示计数器。

所以我使用我希望测试的默认作业版本的相应代码表示关注:

module SessionCount
  private

  #Counter version A
  def increment_session_counter_A
    session[:counter] ||= 0
    session[:counter] += 1
  end

  #Counter version B
  def increment_session_counter_B
    session[:counter] += 1
  rescue
    session[:counter] = 1
  end
end

为了测试默认分配的两个版本,我更改了控制器代码,如下所示:

include SessionCount
...
def show
  ...
  t0 = Time.now
  1000000.times do
    session[:counter] = 0; #initialization for normalization purpose
    increment_session_counter_A
  end
  t1 = Time.now
  puts "Elapsed time: #{((end_time - beginning_time)*1000).round} ms"
  @counter = increment_session_counter_A
  ...

备注:在该代码中,初始化在这里是为了强制执行“快乐路径”(其中值不是nil)。在实际情况中,这仅在给定用户的第一次发生。

结果如下:
版本A(||=运算符)的平均值为3100毫秒 版本B(rescue)平均得到2000毫秒。

但有趣的部分现在开始......

在前面的代码中,代码是在“happy path”之后执行的,其中没有异常发生..
所以为了强制执行“异常路径”,我更改了为规范化目的而进行的初始化::

1000000.times do
  session[:counter] = nil; #initialization for normalization purpose
  increment_session_counter_A
end

以下是结果:
版本A(||=运算符)的平均值为3500毫秒 我用版本B(rescue)得到平均值大约60 000 ms。 是的我只尝试了几次..

所以在这里我可以得出结论, spickermann 表示异常处理的确非常昂贵。

但我认为很多情况下首次初始化很少发生(就像一开始就没有帖子的博客) .. <登记/> 在这种情况下,没有理由每次都测试nil,并且使用异常处理可能会很有趣。

我错了吗?

我不想挑剔一些ms ..在这里我只是想知道这个成语是否有意义作为设计模式。我看到这两个版本的区别,如while... enddo ... while之间的差异,因为意图不一样。