To be an object, or not to be, that is the question
August 29th, 2009
Be is the most boring word in the English language. It so weakens communication that there’s even a funky sublanguage called E’ (“e-prime”) that removes all or almost all of its usage. Supposedly it makes for more efficient communication, but also supposedly sometimes it’s awkward, though I have heard of people fudging when it sounded better to do so.
Anyway we’re not here to talk about that. We’re here to talk about Be as applied to object-oriented programming. Specifically, that the notion of objects being a combination of state and operation results in object designs that are brittle, overcomplicated, and altogether uninteresting.
Let me tell you about a little clinic I worked on at Agile 2009 a few weeks back. We were given a set of use cases for a veterinarian’s accounts receivable. The first use case was really simple, it looked something like:
David brings Fluffy to the vet’s office for a routine visit. While there, Fluffy receives a rabies vaccination. David pays in cash, and receives a receipt listing the cost of the office visit, the rabies vaccination, and total amount.
By the way, this is the perfect kind of use case from which you can begin to write BDD examples. I had a fascinating conversation with Liz Keogh and JB Rainsberger at our BDD clinic at Agile 2009. I’d read about feature injection from Liz before, but here she pointed me to Chris Matts who has written a killer comic book to get his ideas out there.
So back to the point. I watched someone approach this with a typical OO style, writing classes for each of the domain objects that appeared in the use case. In Ruby, it looked roughly like this:
class Person
attr_reader :name
def initialize(name)
@name = name
end
end
class Pet
attr_reader :name, :owner
def initialize(name, owner)
@name = name
@owner = owner
end
end
class OfficeVisit
attr_reader :pet, :procedures
def initialize(pet)
@pet = pet
@procedures = []
end
def add_procedure(procedure)
@procedures << procedure
end
end
class Procedure
attr_reader :name, :cost
def initialize(name, cost)
@name = name
@cost = cost
end
end
There was also something else which actually printed the receipt, but it was using a templating/parsing engine that I was not familiar with. It was in Java and to tell you the truth I had no idea wtf was going on.
As best I can recall, the tests looked like
it "should show a receipt with all line items" do
@owner = Person.new 'David'
@pet = Pet.new 'Fluffy', @owner
@office_visit = OfficeVisit.new @pet
@office_visit.add_procedure Procedure.new('Routine checkup', 10)
@office_visit.add_procedure Procedure.new('Rabies vaccine', 5)
template_result = @crazy_templating_engine.sub @office_visit
template_result.should include('Routine checkup: $10')
template_result.should include('Rabies vaccine: $5')
template_result.should include('Total: $15')
template_result.should include('Paid in cash $15')
end
Entity-based domain objects
So what’s wrong with this? The domain language is reflected in code. Each class is doing one thing only. There’s no duplication. We’re good, right?
Wrong. It’s true that you can read the code and build up a mental image of the domain, but can you tell me what the domain does? Can you tell me what the app does?
I want to reexamine the justification that this is a good design.
Domain language is reflected in the code
Yes…sort of. At best it’s a naive encoding and at worst it’s lazy bordering on negligent. Without getting too much into feature injection (subject for another post), the domain language we’ve used here doesn’t express the value we get from the use case or how to achieve that value. At the very least you would expect to see a Receipt class, right? It may have been a poor choice to go with a templating engine, or it may have been a technical constraint with working in Java. Either way, the words are right for the domain, but we need to ask ourselves if whether they’re the right words for the system that we’re trying to build.
Each class is doing one thing only
Here’s where things get a little bit murky. Each class represents a separate domain concept, with no overlap between them. My question is, what are these classes doing? Doesn’t look like much to me. OfficeVisit probably calculates the total amount of the visit, or maybe that responsibility is misplaced in the receipt printer. At any rate, a class’s name should give you a good idea of what the class does, and “be a procedure” does not yield much info. I submit that in developing software, “Be” is interesting only when followed by “havior”. In part 2 we’ll look at alternatives to this entity-based naming (hint: look for Liz’s writing on anthropomorphic role-based interfaces. Corey Haines pointed out to me that it’s hard to violate the SRP when your class is named PrintsReceipt.
There’s no duplication (the code is DRY)
On the surface this appears to be true, because again there’s no overlap between what the different classes do. As it turns out, there’s duplication all over the stinkin place. This duplication occurs in a form called connascence. Jim Weirich has discussed this as part of his Grand Unified Theory of Software Design, which you should watch immediately, or at least read the slides. The particular connascence that occurs in this design is connascence of name, which is a weak form of connascence. Objects have to interact with each other over an agreed upon protocol, but that is a protocol whose chattiness and interested parties you would like to limit. Demeter “violations” are the product of unchecked connascence of name. One object knows the name of another object’s method, and a method on that object, and so on. When one object in the chain changes, it affects nearly every place in the system that uses that object. Baaaad object modeler. Wrapping these chains in delegator methods just pushes the connascence down, but admittedly encapsulates it just enough that a change in one object usually only results in a change in its immediate neighbors, who keep that change encapsulated from other potential collaborators.
So what do we do instead?
Well I’m not going to tell you just yet. In my follow-up post I will share another possible approach to tackling this problem, an approach that I think is simpler and more flexible and manages to express the domain and requirements more vividly.