拼音默认赋值 ( ||= ) 与救援错误



由于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]很可能只是第一次(即<<1% 的时间)才nil,我觉得以下代码更好,因为隐式if不会每次都触发:

def increment_session_counter
session[:counter] += 1
rescue NoMethodError
session[:counter] = 1
end

从这个意义上说,这段代码更好吗?

话虽如此,我不知道Rescue是如何在 ruby 中实现的,以及如果是这样,它是否真的与可以带来的微小优化相关。

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

  1. 获取值 (Hash#[])
  2. 递增值 (Integer#+)
  3. 存储递增的值 (Hash#[]=)

这非常方便,但它的简洁性也使其不灵活。

如果将步骤分开,则提供默认值要容易得多:

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

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

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

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

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

但是,在这种情况下,您假设计数器的值只有零一次就会陷入危险。事实上,由于许多分布都遵循幂律,因此 1 可能是最常见的计数。

因此,如果您事先知道可能的值,最好在初始化程序或类时将它们设置为零,并跳过默认值检查的需要。如果您事先不知道所有可能的值,您可能会发现经常需要||=语句。在很少的情况下,假设检查nil便宜得多,使用异常是最佳解决方案。

我使用以下代码尝试了一些基准测试。这是一个 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 毫秒。

但有趣的部分现在开始了。

在前面的代码中,代码按照"快乐路径">执行,其中没有发生异常。
因此,我将为规范化目的所做的初始化更改如下,以强制执行"异常路径">

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

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

所以在这里我可以得出结论,正如斯皮克曼所说,异常处理确实非常昂贵。

但我认为在很多情况下,第一次初始化很少发生(比如一开始没有帖子的博客)。
在这种情况下,没有理由每次都测试nil,使用异常处理可能会很有趣。

我错了吗?

我不想挑剔一些女士。在这里,我只想知道这个成语作为一种设计模式是否有意义。我看到了这两个版本的差异,例如while... enddo ... while之间的差异,因为意图并不相同。

相关内容

最新更新