In Ruby land it is approved that more than 3 method parameters are far too much. Such bloated parameter lists are a code smell. And they smell like a logic overload, which by the way is hard to test. So looking for breaking them apart into smaller pieces is definitely the first step. But even if there is only a small thing going on, there are chances to decrease parameter complexity. A small parameter list is fundamental for a maintainable method. The smaller the parameter list, the less it is likely to change and the greater the chances the method keeps on being stable. Aside from that, a long API parameter list is harder to satisfy and harder to read:
def foo name, value, state, start_at, end_at, is_new
# something weird is going on
end
Never waste time (money) and cheerfulness by trying to deal with code like the above. Refactor it!
There are several options, depending on the use case.
1. Deal with parameter objects!
If the method signature consists of parameters, which are likely to be properties of the same object, it is healthy to assign the entire object itself.
class Food
attr_accessor :name, :kcal, :freshness_date
end
class Consumer < Person
def eats food_name, kcal, freshness_date
return if self.allergies.includes?(food_name) or
freshness_date.eql?(Date.today)
self.kcal += kcal
end
end
can be refactored to:
class Consumer < Person
def eats food
return if self.allergies.includes?(food.name) or
food.freshness_date < Date.today
self.kcal += food.kcal
end
end
With parameter objects, the method signature is way more resistant against changes and therefore more stable in the long run. Furthermore if the logic needs to be reused, the method can be extracted into a module easily.
2. Introduce named parameters!
The more generalized the methods logic or the lower the abstraction level, the more the methods parameter list is likely to consist of optional parameters. Sometimes such methods look like:
class Food
def initialize name, kcal=0, freshness_date=nil, preservative=nil
@name = name
@kcal = kcal
@freshness_date = freshness_date
@preservative = preservative
end
end
Food.new 'Apple', nil, nil, false
which is awkward (since nil is always a bad choice). It should be refactored to:
class Food
def initialize name, options={}
@name = name
@kcal = options[:kcal]
@freshness_date = options[:freshness_date]
@preservative = options[:preservative]
end
end
Food.new 'Apple', preservative: false
Please note the way more expressive object instantiation. Besides adding new optional parameters to the options
Hash is easy and does not affect existing code.
3. Replace Parameter with Explicit Methods!
Some use cases do not require a parameter list at all and those parameters should be set explicitly. The object instantiation example again:
class Food
def initialize name, kcal=0, freshness_date=nil, preservative=nil
@name = name
@kcal = kcal
@freshness_date = freshness_date
@preservative = preservative
end
end
Food.new 'Apple', nil, nil, false
but his time defining values with explicit messages:
class Food
attr_accessor :kcal, :freshness_date, :preservative
def initialize name
@name = name
end
end
apple = Food.new 'Apple'
apple.preservative = false
Further articles of interest:
Supported by Ruby 2.1.1