传递哈希而不是方法参数


78

我看到在Ruby(通常是动态类型的语言)中,一种非常常见的做法是传递哈希,而不是声明具体的方法参数。例如,与其声明一个带有参数的方法并像这样调用它:

def my_method(width, height, show_border)
my_method(400, 50, false)

您可以这样操作:

def my_method(options)
my_method({"width" => 400, "height" => 50, "show_border" => false})

我想知道您对此的看法。这是好事还是坏事,我们应该做还是不做?在哪种情况下使用此做法是有效的,并且在什么情况下可能会有危险?


11
一点注意:{width => 400, height => 50, show_border => false}无效的ruby语法。我认为您的意思是{:width => 400, :height => 50, :show_border => false}{width: 400, height: 50, show_border: false}(后者仅在红宝石1.9.1中有效)
萨拉赫德涅

Answers:


28

两种方法都有其优点和缺点,当您使用选项哈希替换标准参数时,由于使用选项哈希创建的伪命名参数,您在定义方法的代码中会失去清晰度,但是在每次使用该方法时都会获得清晰度。

我的一般规则是,如果一个方法有很多参数(超过3个或4个)或很多可选参数,则使用选项哈希,否则请使用标准参数。但是,在使用选项哈希时,务必始终在方法定义中包括描述可能的参数的注释,这一点很重要。


1
现在,如果要有一个选项哈希,是否可以将整个哈希值放入其中,还是必须分别传递键=>值?
FilBot3

1
确实两者都有优点和缺点,但是我不同意一般规则。我认为选项通常是不好的做法,并且只有在那是解决问题的唯一方法时才应使用。
Dragan Nikolic

42

Ruby具有隐式哈希参数,因此您还可以编写

def my_method(options = {}) 

my_method(:width => 400, :height => 50, :show_border => false)

并使用Ruby 1.9和新的哈希语法,可以

my_method( width: 400, height: 50, show_border: false )

当一个函数使用的参数超过3-4个时,不计各个位置就更容易看到哪个是什么。


从Ruby 2.7到现在,不推荐使用隐式哈希参数,并将在Ruby 3.0中将其删除-意味着,我们再次需要{…} 显式传递哈希(请参阅参考资料)。此处显示的Ruby 1.9语法仍然可以用于调用带有关键字参数的方法。
tanius

8

我想说的是,如果你是:

  1. 具有6个以上的方法参数
  2. 通过选择具有某种需要,一些可选的,有些用默认值

您很可能想使用哈希。无需查阅文档即可更轻松地了解参数的含义。

对于您中的那些人来说,很难弄清楚方法采用什么选项,这仅意味着代码记录不充分。使用YARD,您可以使用@option标签指定选项:

##
# Create a box.
#
# @param [Hash] options The options hash.
# @option options [Numeric] :width The width of the box.
# @option options [Numeric] :height The height of the box.
# @option options [Boolean] :show_border (false) Whether to show a
#   border or not.
def create_box(options={})
  options[:show_border] ||= false
end

但是在那个特定的例子中,参数太少了,很简单,所以我认为我会这样做:

##
# Create a box.
#
# @param [Numeric] width The width of the box.
# @param [Numeric] height The height of the box.
# @param [Boolean] show_border Whether to show a border or not.
def create_box(width, height, show_border=false)
end

3

我认为,当有多个参数或多个可选参数时,这种参数传递方法会更加清晰。从本质上讲,方法调用显然是自记录的。


3

在Ruby中,通常不使用哈希而不是形式参数。

我认为与将哈希作为参数传递的常见模式混淆了,因为该参数可以采用多个值,例如在GUI工具箱中设置Window的属性。

如果您的方法或函数有许多参数,则显式声明它们并传递它们。这样做的好处是,解释器将检查您是否已通过所有参数。

不要滥用语言功能,知道何时使用以及何时不使用它。


1
如果仅需要使用可变数量的参数def method(*args),则哈希不能解决该特定问题
MBO,2009年

1
感谢您指出潜在的困惑。我真的在想原始海报出现的情况,其中参数的顺序和数量是可变的。
克里斯·麦考利

1
此外,该反模式也称为魔术容器
jtzero 2015年

3

使用Hashas参数的好处是您消除了对参数数量和顺序的依赖。

实际上,这意味着您以后可以灵活地重构/更改方法,而不会破坏与客户端代码的兼容性(这在构建库时非常有用,因为您实际上无法更改客户端代码)。

(如果您对Ruby中的软件设计感兴趣,Sandy Metz的“ Ruby中的实用面向对象设计”是一本好书)


尽管您的陈述是正确的,但我认为这不是保持库兼容的最佳方法。您可以通过简单地将新参数添加为可选参数来实现相同目的。它们不会破坏兼容性,并且仍然具有清晰性和自说明性。
Dragan Nikolic

@DraganNikolic我也参与其中,不必安排手动参数列表,这是巨大的好处,而且书中还列出了其他自记录代码的方式
Mirv-Matt

许多(包括我在内!)经常忘记的另一件事是,参数的顺序也是依赖项!这意味着仅使用可选的参数意味着无论出于何种原因您都无法更改参数的顺序(但通常无论如何您都不希望有许多args参数!)
Aldo'xoen'Giambelluca

2

这是一个好习惯。您无需考虑方法签名和参数的顺序。另一个优点是,您可以轻松省略不想输入的参数。您可以看看ExtJS框架,因为它正在广泛使用这种类型的参数。


1

这是一个权衡。您会失去一些清晰度(我如何知道要传递的参数)并进行检查(是否传递了正确数量的参数?)并获得了灵活性(该方法可以默认它不会收到的参数,我们可以部署一个新版本,更多参数,不破坏现有代码)

您可以在较大的“强/弱”类型讨论中看到此问题。在此处查看Steve yegge的博客。在我想支持非常灵活的参数传递的情况下,我在C和C ++中使用了这种样式。可以说,带有一些查询参数的标准HTTP GET就是这种样式。

如果您使用哈希方法,我会说您需要确保您的测试真的很好,参数名称拼写错误的问题只会在运行时出现。


1

我敢肯定没有人会使用动态语言,但是请考虑一下,当您开始将散列传递给函数时,将对程序的性能造成不利影响。

解释器可以可能是足够聪明来创建静态常量哈希对象和仅由指针引用它,如果代码使用散列与是源代码的文字的所有成员。

但是,如果这些成员中的任何一个是变量,则每次调用哈希都必须对其进行重构。

我已经做了一些Perl优化,这种事情在内部代码循环中会变得很明显。

函数参数的性能要好得多。


0

通常,除非不可能,否则应始终使用标准参数。不必使用选项时,请不要使用它们。标准参数清晰明了且自成文件(如果正确命名)。

使用选项的一个(也是唯一的)理由是,如果函数接收的参数不进行处理而是仅传递给另一个函数。

这是一个示例,说明了这一点:

def myfactory(otype, *args)
  if otype == "obj1"
    myobj1(*args)
  elsif otype == "obj2"
    myobj2(*args)
  else
    puts("unknown object")
  end
end

def myobj1(arg11)
  puts("this is myobj1 #{arg11}")
end

def myobj2(arg21, arg22)
  puts("this is myobj2 #{arg21} #{arg22}")
end

在这种情况下,“ myfactory”甚至不知道“ myobj1”或“ myobj2”所需的参数。“ myfactory”只是将参数传递给“ myobj1”和“ myobj2”,这是检查和处理它们的责任。


0

哈希对于传递多个可选参数特别有用。例如,我使用哈希来初始化其参数为可选的类。

class Example

def initialize(args = {})

  @code

  code = args[:code] # No error but you have no control of the variable initialization. the default value is supplied by Hash

  @code = args.fetch(:code) # returns IndexError exception if the argument wasn't passed. And the program stops

  # Handling the execption

  begin

     @code = args.fetch(:code)

  rescue 

 @code = 0

  end

end
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.