Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
开发者_C百科Closed 4 years ago.
Improve this questionI 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?
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:
- Having more than 6 method parameters
- 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.
The benefit of using an Hash
as parameter is that you remove the dependency on the number and order of arguments.
In practice this means that you'll later have the flexibility to refactor/change your method without breaking the compatibility with the client code (and this is very good when building libraries because you can't actually change the client code).
(Sandy Metz's "Practical Object-Oriented Design in Ruby" is a great book if you're interested in software design in Ruby)
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.
In general we should always use standard arguments, unless it's not possible. Using options when you do not have to use them is bad practice. Standard arguments are clear and self-documented (if properly named).
One (and maybe the only) reason to use options is if function receives arguments which does not process but just pass to another function.
Here is an example, that illustrates that:
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
In this case 'myfactory' is not even aware of the arguments required by the 'myobj1' or 'myobj2'. 'myfactory' just passes the arguments to the 'myobj1' and 'myobj2' and it's their responsibility to check and process them.
Hashes are particularly useful to pass multiple optional arguments. I use hash, for example to initialize a class whose arguments are optional.
EXAMPLE
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
精彩评论