由于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
,以及它是否与可能带来的微小优化有关。
答案 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
做了三件事:
Hash#[]
)Integer#+
)Hash#[]=
)这很方便,但它的简洁也使它变得不灵活。
如果您将步骤分开,则提供默认设置会更容易:
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... end
和do ... while
之间的差异,因为意图不一样。