我知道避免在Ruby中更改方法参数是一种很好的做法(除非专门用于此目的)。
关于如何管理此代码的简单的经验法则是什么(在样式,命名约定等方面)?
例如,这是一种好习惯吗?
def array_of_three(a, b, c)
d = a.dup
e = b.dup
f = c.dup
# Do work on d, e and f
return [d, e, f]
end
我该如何命名方法参数与函数中可以更改的重复项? (假设类似上述方法是“正确的”!)
谢谢。
答案 0 :(得分:3)
Ruby的“良好实践”通常可以归结为不变异您不拥有的数据。问题在于所有权可能有些模糊,因此如果有疑问,您将假定您不拥有数据并制作副本如果,则需要进行突变它。
不良行为的表现方式是这样的一个例子:
a = [ 1, 2, 3 ]
do_stuff(a) # Works
do_stuff(a) # Doesn't do anything for some reason
a
# => [ ] # Hey! Who did that?
假设do_stuff
是一个混蛋,是这样做的:
def do_stuff(a)
while (v = a.pop)
puts a
end
end
那破坏了给您的参数,这很糟糕。相反,您应该采用非破坏性方法:
def do_stuff(a)
a.each do |v|
puts v
end
end
或者如果有必要操纵该结构,则制作一个副本,例如:
def do_stuff(a)
a = a.uniq.sort
while (v = a.pop)
puts v
end
end
测试方法是否正常的一种好方法是在测试中提供冻结数据:
a = [ 1, 2, 3 ].freeze
do_stuff(a) # Fails trying to manipulate frozen object
我倾向于使用激进的冻结数据(例如deep_freeze
)编写单元测试,以确保参数不能被修改。这真的很快消除了所有权问题。
如果您的函数具有这样的方法签名:
def do_stuff(*a, **b)
# ...
end
然后,您将同时拥有a
(变量)和b
(变量),因为它们是专门针对您的方法而创建的。在所有其他情况下,您需要小心。在这里,按合同编程很重要,因为您的方法的调用者必须知道要移交哪些参数,哪些不移交。文档必须明确这一点。
答案 1 :(得分:2)
这更多是对其他答案的扩展注释,而不是其本身的答案,但是,如果您要对自己不拥有的数据进行变异,则方法名称应以{{ 1}}。对此没有花哨的语法。 !
只是终止方法的有效字符。例如,看下面的代码:
!
这将输出a = [1, 2, 2, 3, 3, 3]
puts a.uniq
puts a
,然后输出[1, 2, 3]
,如您所愿。另一方面,此代码:
[1, 2, 2, 3, 3, 3]
将输出两次a = [1, 2, 2, 3, 3, 3]
puts a.uniq!
puts a
,因为它就地修改了[1, 2, 3]
。如果有两件事是对的,那就特别好:
a
不太适合这种模式,因为您如何在不变异的情况下弹出?这不是一成不变的规则,但是它是采用的一种不错的样式,因为它使人们一眼就可以看出事物已被突变。
请注意,如果pop
已经是所有唯一元素(例如a
),则a = [4, 5, 6]
将返回uniq!
,这是标准的用于最终不会执行修改的就地修改功能。我建议,如果您采用nil
样式,那么也应采用这种样式,以避免与人们对!
功能如何工作的心理模型进行比较时产生混淆。
答案 2 :(得分:1)
我知道这是基于我的观点的答案,但这是我在ruby中“套用”参数的首选方式(不求助于.dup
之类)是将其传递给函数,该函数通过一个lambda的参数,该参数在适当位置求值:
def argument_trap(*args)
lambda { args }.call
end
def array_of_three(a, b, c)
a, b, c = argument_trap(a, b, c)
a += 1
b -= 1
c *= 2
[a, b, c]
end
def array_of_n(*args)
args = argument_trap(*args)
args.map { |arg| arg += arg }
end
a, b, c, d, e, f = 1, 2, 3, 4, 5, 6
three = array_of_three(a, b, c)
n = array_of_n(a, b, c, d, e, f)
p [a, b, c]
p three
puts
p [a, b, c, d, e, f]
p n
给予
[1, 2, 3]
[2, 1, 6]
[1, 2, 3, 4, 5, 6]
[2, 4, 6, 8, 10, 12]
使用这种模式,在第一条函数主体行上很明显,参数名称现在指向克隆。但这又是一个优先事项-我喜欢保留原始参数名称,而不是命名它们为dupA
或类似名称,因为对我来说,很明显,我现在在这些函数中正在处理重复项。 / p>
这也阻止了同时执行以下功能:使用对原始值的引用和重复项,这在我眼中是一个优势,因为同时执行会导致很多代码混乱。另一方面,一个人可能会忘记一两个参数,并且再次陷入地狱。.