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.