I gave this as a lightning talk at Boston.rb on 7/14//2015. It's a discussion of the challenges of applying the Law of Demeter to Rails programming.
I gave an older, simpler version of this talk at Philly.rb on 1/15/2013. I've replaced those older slides.
Advancing Engineering with AI through the Next Generation of Strategic Projec...
Talking to strangers causes train wrecks unpacked
1. July 14, 2015
Talking to strangers causes train wrecks
Mike Toppa
CTO, Poka Yoke Design
@mtoppa
http://pokayoke.design
2. My house, yesterday
I just moved to Boston from Philly. I took this picture yesterday. It looks pretty much the same today.
3. Goal: unpack this quote
“Mockist testers do talk more about avoiding 'train wrecks' - method
chains of style getThis().getThat().getTheOther(). Avoiding method
chains is also known as following the Law of Demeter. While method
chains are a smell, the opposite problem of middle men objects bloated
with forwarding methods is also a smell. (I've always felt I'd be more
comfortable with the Law of Demeter if it were called the Suggestion of
Demeter.)”
Martin Fowler, 2004
Mocks aren’t Stubs
4. The Law of Demeter, I
✤ Your method can call other methods in its class directly
def full_name
"#{self.first_name} #{self.last_name}"
end
✤ You can play with yourself
From c2.com "Law of Demeter"
Back in the 1980s, a group of programmers working on a project called the Demeter system realized that certain qualities in their object-oriented code led
to the code being easier to maintain and change. [from Avdi Grimm]
Analogy by Peter VanRooijen
5. The Law of Demeter, II
✤ Your method can call methods on its own fields directly (but not on
the fields' fields)
user.department.name
✤ You can play with your own toys (but you can’t take them apart)
From c2.com "Law of Demeter"
6. The Law of Demeter, III
✤ When your method takes parameters, your method can call methods
on those parameters directly
def user_info(user)
"Name: #{user.full_name}"
end
✤ You can play with toys that were given to you
From c2.com "Law of Demeter"
7. The Law of Demeter, IV
✤ When your method creates local objects, that method can call
methods on the local objects
# Sorry, I can’t give an example without
# going on a tangent about dependency injection
✤ And you can play with toys you’ve made yourself
From c2.com "Law of Demeter"
8. Violation example in a Rails project
In Ruby, and Rails, this kind of chaining is so easy to do, you might not realize the implications of what you’re doing. There are 3 concerns to emphasize:
1. This code is brittle. If any component of the method chain changes, this line of code, and any others like it, will break. Coupling is high, and information
is not localized.
2. In the context of ActiveRecord, this approach has the potential to yield horrific, poorly performing underlying SQL queries.
3. If the code was in a method, it would be challenging to write a unit test for it
9. Testing with train wrecks
def user_info(user)
"Name: #{user.name}. "
"Boss: #{user.department.try(:head).try(:name)}"
end
From Demeter: It’s not just a good idea. It’s the law.
This example is from Avdi Grimm
10. describe '#user_info' do
subject { user_info(user) }
let(:user) {
stub(
'user',
name: "Bob",
department:
stub(
name: "Accounting",
head: stub(
name: "Jack",
position: stub(title: "Vice President")
)
),
division: stub(
name: "Microwave Oven Programming"
)
),
position: stub(title: "Senior Bean Counter")
)
}
# examples...
end
From Demeter: It’s not just a good idea. It’s the law.
Alternately, you could try to do this without mocks, and use factories or fixtures instead. But that just moves the brittleness into your test data setup.
11. Reducing Rails train wrecks:
Delegation
From Demeter: It’s not just a good idea. It’s the law.
class User
delegate :name, to: :department, prefix: true, allow_nil: true
end
…
def user_info(user)
"Name: #{user.name}. Dept: #{user.department_name}"
end
While Demeter allows us to play with our friends, delegation can still help reduce brittleness. If there is a change in the relationship between users and
department, we can create a department_name method that expresses the new logic, without having to change calling code. For example, if users can now
be part of more than one department.
12. Reducing Rails train wrecks:
has many :through
class User < ActiveRecord::Base
has_many :departments
has_many :divisions, through: :departments
end
Now we can treat divisions as an immediate collaborator. You can say this is cheating, but the point is, information about divisions is now localized. If
something changes in the relationship to divisions, we can replace the “has many through” relationship with a “divisions” method.
13. Reducing Rails train wrecks:
nested has many :through
class Candidate < ActiveRecord::Base
has_many :campaigns
has_many :races, through: :campaigns
has_many :sought_offices, through: :races
end
14. Fowler’s conundrum:
Demeter as suggestion, or law?
✤ With nested has many :through, now we can say:
✤ candidate.sought_offices
✤ With Rails, we don’t need “middle men objects bloated with
forwarding methods” to access distant collaborators
✤ The question I’ll leave you to ponder is:
✤ Is this a good solution to Fowler’s conundrum?
15. ポカヨケ
http://pokayoke.design
@mtoppa
My wife has a new job in Boston, so we’re moving, and I’ll be starting a new job too. I’ll be co-founder and CTO of Poka Yoke Design, and I’m looking forward to working
more with WordPress again. My partner is based in Memphis, so I’m very pleased to be strengthening my ties to the WordPress community here in Tennessee.