Least Significant Bit

Ask Hack Learn Share!

Antipatterns: Using modules as classes

Imagine a module Calculator defining some methods which then you call like Calculator.sum(1, 2). While this usage of a module is valid in some cases, this time I think we are seeing an antipattern in place. Let's see in my opinion when using a module directly to call methods on it makes sense and when it is an antipattern.

A simplified version of the code is this

module Calculator
  def sum(a, b)
    a + b
  end

  def mean(a, b, counter)
    divide sum(a, b), counter
  end

private

  def divide(a, b)
    a / b
  end
end

before going on the problems I see in this code, let’s see the good uses of modules in Ruby and see why I think this code is not eloquent enough.

Good uses of Ruby modules

Ruby modules are good to:

  • Mixins: group methods related to a common functionality, to then include the behavior in classes. The Enumerable mixin is a good example of this, which then Array and others include.

  • for namespacing purposes, e.g.

module MySQL
  class Query
    def self.select
    end
  end
end

and then using it makes perfectly clear that the Query is a MySQL one MySQL::Query.select ... plus other benefits of separation of code.

  • use them as a place to put methods at the namespace level. A very widely spread use of this is to have methods directly in a gem name, e.g. to configure a gem in a Rails initializer.
ComfortableMexicanSofa.configure do |config|
  config.cms_title = "It is handy to configure at the level of the gem name"
end
  • As a junk room: an there’s a last one usecase to which Russ Olsen in his book Eloquent Ruby refers as “Modules make great homes for those pesky methods that just don’t seem to fit anywhere else”.

And it is in this last category where the code at hand seems to fit into.

Use Class instead of Module

Let’s see the symptoms that point to the fact that using a class would mean here clearer code. As Olsen’s Eloquent Ruby says: the code has to tell a story, and has to tell it well, and I’ll add to that that it should tell it also by minimizing the surprise points for developers.

The code when used looks like Calculator.sum(1,2) when I read that I normally expect that Calculator to be a class not a module. So here first surprise appears.

Moreover the mean method calls both sum and divide, passing the same parameters repeated times. The symptoms here are, a method calling another when they are supossedly not much related (since you put them in the junk drawer), and at the same time many parameters are floating around in method calls.

To add more confusion to the latter, notice also that divide is a private method in a module.

Let’s use the code to see another problem:

sum = Calculator.sum(1, 2)
sum = Calculator.sum(sum, 2)
mean = Calculator.mean(sum, 3, 4)

As you see you need to keep the sum yourself, and also know the amount of sums you have made in order for your method mean to work. This implies that encapsulating state inside the Calculator would be a good idea.

And last but no least, the 3 methods are related to calculations, so this points to the fact that an object Calculator is needed. But instead a junk room for methods was used, somehow implying that the object modeling was not clearly achieved, so the code fell short in being eloquent again.

An object version

A version using a class could be:

class MeanCalculator
  attr_accessor :total, :counter

  def initialize
    @total = 0
    @counter = 0
  end

  def incr(amount)
    total += amount
    counter += 1
  end

  def mean
    if counter > 0
      divide
    else
      total
    end
  end

  private

  def divide
    total / counter
  end

end

And the use of the code would be:

calc = MeanCalculator.new
calc.incr 1
calc.incr 2
calc.incr 2
calc.incr 3
mean = calc.mean

So, would you write the above as an class/object or as a module? My advice is use an object whenever you can.

blog comments powered by Disqus