never be satisified

Jared Carroll

Everyone loves metrics. I think they’re great guidelines and can help you push yourself into writing a lot better code. The first step is knowing the guidelines, then you have to know when to apply them. For example, “classes should have roughly 12-15 methods”; now don’t always follow that for the sake of meeting the metric. In some cases it applies, in others it doesn’t, it’s a judgement call. So say you’ve written the following code and you now take some time to reflect on it by comparing it to your list of metrics, specifically the above metric of “classes should have roughly 12-15 methods”.

class User < ActiveRecord::Base

  has_many :subscriptions

  def subscribe_to(magazine)
    self.subscriptions << Subscription.new(:user => self,
                                            :magazine => magazine)
  end

  # 13 other User related methods

  def full_name
    "#{first_name} #{last_name}"
  end

  def short_name
    "#{first_name.first}. #{last_name}"
  end

  def formal_name
    "#{last_name}, #{first_name}"
  end

end

Now looking at the above code there’s 17 (ignoring Rails generated methods) methods in the User class. Now those last three methods deal with formatting the user’s name in different ways. More importantly, they put me over the metric; I’m now at 17 methods. So I ask myself, those 3 methods seem all related to a user’s name. Maybe I’m missing an object in there somewhere? Yes of course, a Name object. Let’s move that name related behavior there. So we now have:

class User < ActiveRecord::Base

  has_many :subscriptions

  composed_of :name,
    :mapping => [%w(first_name first_name),
                  %w(last_name last_name)]

  def subscribe_to(magazine)
    self.subscriptions << Subscription.new(:user => self,
                                            :magazine => magazine)
  end

  # 13 other user related methods
  # name related behavior has moved to the Name class

end

class Name

  attr_reader :first_name, :last_name

  def initialize(first_name, last_name)
    @first_name, @last_name = first_name, last_name
  end

  def full_name
    "#{first_name} #{last_name}"
  end

  def short_name
    "#{first_name.first}. #{last_name}"
  end

  def formal_name
    "#{last_name}, #{first_name}"
  end

end

(I’d probably refactor those method names in the Name class to just #full, #short and #formal; having the word “name” on the end of them is redundant)

We now meet our metric of “12-15 methods per class”. In the process we’ve modularized (put in one place) all the name related behavior and simplified the User model by delegating its name related behavior to another object. Metrics pushed us into thinking harder about our code and resulted in simpler, more expressive code.