Code Smell: this.object.knows.too.much

One day in your coding journey you might run into a linting error like this:

RSpec/MessageChain: Avoid stubbing using receive_message_chain

Uh, oh. What happened here?

This is the Ruby community's best effort at enforcing The Law of Demeter. While this linting rule doesn't protect us from The Law of Demeter, it does let us know something fishy is happening.

Like everything in software The Law of Demeter has very little to do with the Greek goddess, Demeter.

The Law of Demeter "requires that a method m of an object a may only invoke the methods of the following kinds of objects:

  • a itself;
  • m's parameters;
  • any objects instantiated within m;
  • a's attributes;
  • global variables accessible by a in the scope of m."

Thank you, wikipedia.

Another way of thinking about this is, "how much does an object need to know about unrelated objects to get its work done?" If the answer is, "a lot", that's bad.

The Law of Demeter, or message chaining, is an example of coupling in your code that makes dependencies hard to reason about.

Example Time

Here's what this might look like in a Rails project:

class User
  def comment(text:)
    Comment.create(text: text, user_id: id)

This might look a bit contrived, but I have seen things like this in the wild. In this example User knows how to create a Comment, if new attributes are added to Comment, User needs to know these too.

Another funky looking example is message chaining, especially message chaining that creates new objects. I've seen code like this in rails projects before too:

class User
  def comment_on_post(post_id:)
    posts.find_by(id: post_id).comments.create(text: text, user: self)

Here we have a long message chain that allows us to call create on a comment through a user. While this might look incorrect, it is technically possible, but not advised, in a Rails project.

What to do instead?

Make sure objects only know about their own functionality.

For both of the examples above, let the Comment object handle creating its records. If you find yourself needing logic before and after creating a Comment, you can use validations, or service objects that handle the business logic you need.

Why isn't there a linter rule for this?

The best we have today is the RSpec linter rule. Unfortunately, it is really hard to detect Law of Demeter violations in Ruby. For example some_string.downcase.chomp.gsub('-', '_') might look like a violation but it's a perfectly reasonable chain of calls since each method returns an instance of String.

The Law of Demeter isn't so much a style preference like double quotes vs single quotes, as it is a code smell, or an indication that there might be something wrong with the code.

Code smells are a lot harder to lint for, as developers we need to learn to spot them so that we can decouple our code to keep things clean and readable.

Further Reading

Law of Demeter - Ruby Science by thoughtbot
RSpec :: RuboCop Docs
Misunderstanding the Law of Demeter - Dan Manges’s Blog
[new cop] law of demeter · Issue #720 · rubocop/rubocop read more to know why it is invalid code