Ruby:按引用传值
Source: Dev.to
大多数 Ruby 开发者都熟悉一种常被称为“按引用值传递”(pass‑by‑reference‑value)的概念。
当一个可变对象(例如 String)被赋值给另一个变量时,两个变量引用的是同一个对象。通过其中一个引用对对象进行修改,会在另一个引用中体现出来。
简单赋值示例
var1 = "Hello, World"
var2 = var1
var2.gsub!("World", "Ruby")
puts var1 # => Hello, Ruby
服务类示例
class DummyClass
attr_accessor :value
def initialize(value)
@value = value
end
end
dummy = DummyClass.new("Hello, World!")
puts "Before service action:"
puts dummy.value # => Hello, World!
class ServiceClass
def initialize(value)
@value = value
end
def perform_action
local_value = @value
local_value.gsub!("World", "Ruby")
end
end
ServiceClass.new(dummy.value).perform_action
puts "After service action:"
puts dummy.value # => Hello, Ruby!
实际场景中的陷阱
在生产代码库中,同样的行为可能会成为潜在的 bug。设想有一个 ActiveRecord 模型,其字符串属性被传递给一个负责在模板字符串中插入占位符(例如 %%BRAND%%)的服务:
# Hypothetical usage
Interpolator.custom_interpolate(source, brand_name)
方法名 custom_interpolate 暗示它会返回一个新字符串,但如果实现中在 source 对象上使用了会修改原对象的 gsub!,则模型的属性会在记录保存之前 在内存中 被修改。此副作用可能导致整个应用出现意料之外的数据变化。
最佳实践
-
优先使用非变异方法(
gsub而不是gsub!),当你只需要得到一个转换后的值时。 -
在需要保护原对象时进行复制:
def safe_interpolate(str, replacements) str = str.dup # perform non‑mutating transformations str.gsub!("%BRAND%", replacements[:brand]) str end -
明确记录副作用,如果方法有意修改其参数,需要在文档中说明。
-
编写测试,验证在调用服务后对象没有被意外修改。
通过对可变性保持明确、使用防御性复制,你可以避免因 Ruby 的按引用值传递语义而产生的细微 bug。