In Progress
Unit 1, Lesson 1
In Progress

Minimum Viable Method

Is there such a thing as a method that is too small? Is it worth it to extract a method just to satisfy a guideline like the Law of Demeter? These are the questions we’ll explore in this episode.

Video transcript & code

My friend Sam Livingston-Gray was kind enough to bring this snippet of code to my attention the other day:

def crm_owner_id_present?
  crm_owner_id.present?
end

The name of this predicate method is crm_owner_id_present?. Its implementation consists of taking the crm_owner_id attribute, and asking it if it is present?. Which, in Ruby on Rails, is a way of checking if an attribute is both non-nil and non-blank.

The question that arises when looking at this code is this: does this method have any reason for existing?

Let's imagine some code where this method might be called. It might look something like this:

if crm_owner_id_present?
  # do something that requires a CRM owner
end

We ask whether the current object has a CRM owner ID attribute set, and if so we perform some action which requires this attribute.

What would this code look like in the absence of the method in question? Not very different:

if crm_owner_id.present?
  # do something that requires a CRM owner
end

So what might have been the impetus for creating this method in the first place?

def crm_owner_id_present?
  crm_owner_id.present?
end

Perhaps this method comes from a codebase in which the developers have tried to strictly observe the so-called "Law of Demeter". The Law of Demeter has been stated a few different ways, but in essence what it says is that a single object method shouldn't know about more than a single layer of another object type's internal structure.

To get a concrete idea of what this means, let's imagine some code that clearly violates the Law of Demeter.

if crm_owner.account.user_name == "bob"
  # do something bob-specific
end

Here, in a single if statement, we navigate from the current object's crm_owner to an associated account object and then access the user_name field inside the account object.

This is a two-layer deep navigation of an external type. The method in which this code is found is now implicitly tied to the structure of a crm_owner object and its associations. If that structure chantges, this code will break.

In this case, we might decide to satisfy the Law of Demeter, and isolate potential breakage, by defining new methods to encapsulate this structural navigation.

def crm_owner_account
  crm_owner.account
end

def crm_owner_name
  crm_owner_account.user_name
end

Each of these methods deals with a single level of structural navigation. So long as we are careful to use these methods everywhere, in place of spelling out the whole path to the information we need, then we'll be able to fix any future breakage caused by a structure change by updating just one method.

Now let's go back to our original imaginary if-statement.

if crm_owner_id.present?
  # do something that requires a CRM owner
end

This code represents only a single layer of navigation: taking the crm_owner_id attribute, and asking if it is present. But beyond that, consider the type of object we are dealing with here.

The Law of Demeter is all about limiting one object's coupling to external types which might change structure out from under it. So what type of object are we expecting in this case?

This appears to be an ActiveRecord ID field, which means that it's probably going to contain either an integer or nil. But more importantly, what message are we sending to the attribute? We're sending the present? message.

What object responds to this message? As you may know if you're used to working with Rails, the ActiveSupport library adds this message to the base Object type. Which means it's available on every object in the system.

require "active_support"
require "active_support/core_ext"
nil.present?                     # => false
"".present?                      # => false
"banana".present?                # => true
123.present?                     # => true

In effect, the only "external type" we are depending on here is Object. And Object is implicitly already a dependency of any code we write. Depending on methods available to any Object doesn't violate the Law of Demeter no matter how deeply we chain the calls.

So is there any justification for this method?

def crm_owner_id_present?
  crm_owner_id.present?
end

In its present form, I'd be hard-pressed to find one. However, that doesn't necessarily mean that this little method shouldn't exist. As we saw in Episode 101, it can be quite useful to clarify code by defining intention-revealing messages. With a change in name, this method goes from redundant, to explanatory:

def has_crm_owner?
  crm_owner_id.present?
end

When we use the method in context, it now makes the calling code read more clearly:

if has_crm_owner?
  # do something that requires a CRM owner
end

The moral of this story is twofold: first, when applying guidelines such as the Law of Demeter, it's important to understand the intent of the rule. Rather than just robotically applying heuristics to identify and fix violations.

And second: there is no such thing as a method that's "too small"… so long as it contributes to the clarity of the code context in which it is used.

Happy hacking!