Passing hashes instead of method parameters

Viewed 50812

I see that in Ruby (and dynamically typed languages, in general) a very common practice is to pass a hash, instead of declaring concrete method parameters. For example, instead of declaring a method with parameters and calling it like this:

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

you can do it this way:

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

I'd like to know your opinion about it. Is it a good or a bad practice, should we do it or not? In what situation using this practice is valid, and it what situation can it be dangerous?

11 Answers

Ruby has implicit hash parameters, so you could also write

def my_method(options = {}) 

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

and with Ruby 1.9 and new hash syntax it can be

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

When a function takes more than 3-4 parameters, it's much easier to see which is what, without counting the respective positions.

Both approaches have their own advantages and disadvantages, when you use an options hash replacing standard arguments you lose clarity in the code defining the method but gain clarity whenever you use the method because of the pseudo-named paramaters created by using an options hash.

My general rule is if you either have a lot of arguments for a method (more than 3 or 4) or lots of optional arguments then use an options hash otherwise use standard arguments. However when using an options hash it is important to always include a comment with the method definition describing the possible arguments.

I'd say that if you are either:

  1. Having more than 6 method parameters
  2. Passing options that have some required, some optional and some with default values

You would most probably want to use a hash. It's much easier to see what the arguments mean without looking up in the documentation.

To those of you saying it's hard to figure what options a method takes, that just means that the code is poorly documented. With YARD, you can use the @option tag to specify options:

##
# 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

But in that specific example there's such few and simple parameters, so I think I'd go with this:

##
# 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

It is not common practice in Ruby to use a hash rather than formal parameters.

I think this is being confused with the common pattern of passing a hash as a parameter when the parameter can take a number of values e.g. setting attributes of a Window in a GUI toolkit.

If you have a number of arguments to your method or function then explicitly declare them and pass them. You get the benefit that the interpreter will check that you have passed all the arguments.

Don't abuse the language feature, know when to use it and when not to use it.

I think this method of parameter passing is much clearer when there are more than a couple of parameters or when there are a number of optional parameters. It essentially makes method calls manifestly self-documenting.

It is a good practice. You don't need to think about method signature and the order of the arguments. Another advantage is that you can easily omit the arguments you do not want to enter. You can take a look at the ExtJS framework as it is using this type of argument passing extensively.

It's a trade-off. You lose some clarity (how do I know what params to pass) and checking (did I pass the right number of arguments?) and gain flexibility (the method can default params it doesn't receive, we can deploy a new version that takes more params and break no existing code)

You can see this question as part of the larger Strong/Weak type discussion. See Steve yegge's blog here. I've used this style in C and C++ in cases where I wanted to support quite flexible argument passing. Arguably a standard HTTP GET, with some query parameters is exactly this style.

If you go for the hash appraoch I'd say that you need to make sure your testing is really good, problems with incorrectly spelled parameter names will only show up at run time.

I'm sure no one using dynamic languages cares, but think about the performance penalty your program is going to be hit with when you start passing hashes to functions.

The interpreter may possibly be smart enough to create a static const hash object and only reference it by pointer, if the code is using a hash with all members that are source code literals.

But if any of those members are variables then the hash must be reconstructed each time it is called.

I've done some Perl optimizations and this kind of thing can become noticeable in inner code loops.

Function parameters perform much better.

Related