Sonntag, 28. Dezember 2014

Reduce Ruby method parameter complexity!

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