传递哈希而不是方法参数

82

我看到在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})

我想知道你对此的看法。这是一个好习惯还是坏习惯,我们应该这样做还是不应该?在何种情况下使用这个做法是有效的,在何种情况下会有危险?


12
一个小提示:{width => 400, height => 50, show_border => false} 不是有效的 Ruby 语法。我认为你想表达的是 {:width => 400, :height => 50, :show_border => false} 或者 {width: 400, height: 50, show_border: false}(后者只在 Ruby 1.9.1 及以上版本中有效)。 - sarahhodne
11个回答

46

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个参数时,不用数各自的位置就更容易看出每个参数都代表什么。


1
从Ruby 2.7开始,隐式哈希参数已被弃用,并将在Ruby 3.0中删除 - 这意味着我们需要显式地使用{...}来传递哈希(参见)。这里显示的Ruby 1.9语法仍然可以用于调用具有关键字参数的方法。 - tanius

30

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

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


1
现在,如果我有一个选项哈希表,我可以直接把整个哈希表放进去,还是必须逐个传递键=>值? - FilBot3
1
虽然两者都有优点和缺点,但我不同意一般规则。我认为选项通常是不好的做法,只有在解决问题的唯一方法时才应该使用。 - Dragan Nikolic

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

4
在Ruby中,使用散列而不是正式参数并不是常见的做法。我认为这与将散列作为参数传递的常见模式混淆了,例如在GUI工具包中设置窗口的属性时可以采用该方法。如果您的方法或函数有多个参数,请明确声明并传递它们。您将获得一个好处,即解释器将检查您是否已经传递了所有参数。不要滥用语言功能,知道何时使用它以及何时不使用它。

1
如果您需要可变数量的参数,只需使用 def method(*args),哈希表无法解决这个特定的问题。 - MBO
1
感谢您指出可能存在的混淆。我真正考虑的是原帖提出的参数顺序和数量可变的情况。 - Chris McCauley
1
此外,该反模式也被称为魔法容器 - jtzero

3

我认为在参数数量较多或存在许多可选参数时,此种参数传递方式更为清晰明了。它使得方法调用在本质上变得自我记录。


3
使用哈希表作为参数的好处是,您可以消除对参数数量和顺序的依赖。
实际上,这意味着您稍后可以灵活地重构/更改方法而不会破坏与客户端代码的兼容性(在构建库时非常有用,因为您实际上无法更改客户端代码)。
(Sandy Metz的"Ruby实践中的面向对象设计"是一本很棒的书,如果您对Ruby软件设计感兴趣)

虽然你的说法是正确的,但我认为这不是保持库兼容性的最佳方式。你可以通过将新参数作为可选项简单地添加到代码中来实现相同的效果。这样不会破坏兼容性,同时还具有清晰和自我说明的好处。 - Dragan Nikolic
@DraganNikolic 我也支持sany metz的东西 - 不需要安排一长串手动参数列表的好处是巨大的,书中还介绍了其他自我记录代码的方法。 - Mirv - Matt
许多人(包括我自己!)经常忘记的另一件事是参数顺序也是一个依赖关系!这意味着仅使用可选参数意味着您无法出于任何原因更改参数顺序(但通常您也不需要太多参数!) - Aldo 'xoen' Giambelluca

2

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


1
哈希表在传递多个可选参数时特别有用。例如,我使用哈希表来初始化一个类,其参数是可选的。

示例

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

1
一般情况下,我们应该始终使用标准参数,除非不可能。当你不必使用选项时,使用选项是不好的实践。标准参数清晰易懂,并且自我记录(如果命名得当)。
使用选项的一个(也许是唯一的)原因是,函数接收到的参数不会被处理,而只是传递给另一个函数。
以下是一个说明这一点的示例:
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',而由它们负责检查和处理这些参数。

1
这是一个权衡。你会失去一些清晰度(我怎么知道要传递什么参数)和检查(我是否传递了正确数量的参数?),但会获得灵活性(该方法可以默认不接收的参数,我们可以部署一个新版本,接受更多参数而不破坏任何现有代码)。
你可以将这个问题视为更大的强/弱类型讨论的一部分。在Steve yegge's的博客中可以看到这种风格。我在C和C++中使用这种风格,以支持相当灵活的参数传递。可以说,标准的HTTP GET请求,带有一些查询参数,正是这种风格。
如果你选择哈希表方法,我建议你确保你的测试非常好,因为拼写错误的参数名称问题只会在运行时显示出来。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接