避免变异参数的最佳做法?

时间:2015-12-10 11:58:23

标签: ruby

有人可以建议一种好的,红宝石惯用的方法来避免这种情况吗?

class Foo
  attr_accessor :bar
end

a = {one: 1}
x = Foo.new; x.bar = a

x.bar[:two] = 2
p a  #=> {one: 1, two: 2}
  • 我可能根本不允许类的用户访问其属性,这解决了问题......在这种情况下。 (将参数传递给方法怎么样?)无论如何,避免除了attr_reader以外的所有事情,并且只在非可变属性上使用它,并不是Ruby-ish。

  • 或者,我可以不编写任何改变值的代码,这些代码很有吸引力,但在Ruby中并不容易。

  • 我可以系统地dupclone我的课程给出的每个参数 - 除了这些方法不适用于Nilclass,Fixnums,Symbols等 - 更糟糕的是,responds_to?(:dup) == true表示这些类型。 (另外,dupclone都不做深层复制。)

在上面的示例中,我修改了调用者中的bar属性,但如果代码在类中,或者我在类上使用方法而不是attr_accessor,则问题仍然相同:如果我想要一个可以接受价值并用它做某事的课程,如果由于某种原因我必须通过在某个地方改变这个价值来做到这一点 - 是否有一种惯用的方式在红宝石中确保我不会用该突变值感染调用者?

在Ruby中,我们不应该非常关心传入数据的类型,但看起来好像我必须非常关心它,以便告诉如何使这个值我想要变异安全。如果它是NullObject或Fixnum或符号它没问题,否则我可以dup它......除非我需要深度复制它。

那可能是对的,可以吗?

编辑:经过多思考后

塞尔吉奥当然是对的 - 有时你想要这种行为。不是因为在代码中使用副作用是一个好主意,但是因为有时候传递消息的类需要对可能随后发生变化的对象进行实时引用。

这种行为唯一有问题的是你传递一个Enumerable。如果我传递数组或哈希,我真的不希望接收者修改它。所以我的意思是:

  • 每当我把东西传递给接收者时,做塞尔吉奥所说的并且防御性地编码,以防万一编码它的人没有小心。
  • 在我自己的课程中实施一揽子规则:复制所有传入的Enumerables。

2 个答案:

答案 0 :(得分:4)

调用者有责任保护自己免受被调用的代码的影响。让我们说,你有一些解析代码的命令行选项。你有这个参数哈希,你想做一些验证(或其他东西)。现在,验证代码是由其他一些喜欢就地效应的人来编写的。效率"。因此,您的哈希可能会发生变异,以后您无法使用它。

解决方案?传递副本

validate_params! Marshal.load(Marshal.dump(params)) # deep copy

请注意,在某些情况下,突变是理想的。所以必须是调用者控制效果(允许或阻止它)。

答案 1 :(得分:1)

我会考虑使用freeze

class Foo
  attr_reader :bar

  def bar=(value)
    @bar = value.freeze # You may need to freeze nested values too
  end
end

a = { one: 1 }
x = Foo.new
x.bar = a

x.bar[:two] = 2
# raises: can't modify frozen Hash

或者如果您不想更改Foo,请在分配时冻结值:

class Foo
  attr_accessor :bar
end

a = {one: 1}
x = Foo.new
x.bar = a.freeze

x.bar[:two] = 2
# raises: can't modify frozen Hash