I've been working on a DSL for mock-based testing C recently. I'm writing it in Ruby and it's been coming along nicely.
I started with a Translator class that had an accept_line method. I used strings as lines for a while and then I got to the point where I had to report errors so it was time to add a Line class:
class Line
attr_reader :text, :line_number, :file_name
def initialize(text, line_number, file_name)
@text = text
@line_number = line_number
@file_name = file_name
end
end
After I started passing Lines rather than strings, I ran my tests and modified my code so that it worked correctly with them. Then, I started to report errors to an Errorhandler class.
Some people would pass the line to the Errorhandler like this:
handler.report(current_line, "Expected call or returns clause")
but then the Errorhandler would have to read all of the information from the Line and form the actual message.
Why not let Line do it. If it did, we wouldn't need readers for the attributes:
class Line
def initialize(text, line_number, file_name)
@text = text
@line_number = line_number
@file_name = file_name
end
def form_message(message_text)
"#{@file_name}(#{@line_number}): #{message_text}"
end
end
A bit better. The only problem is that some client of Errorhandler will have to go to a Line object, ask it to form a message and then take that message and pass it to the Errorhandler. It might be cleaner to just have the Line report to an Errorhandler:
class Line
def initialize(text, line_number, file_name)
@text = text
@line_number = line_number
@file_name = file_name
end
def report_error(message_text, handler)
handler.report("#{@file_name}(#{@line_number}): #{message_text}")
end
end
This sort of thing is design at a lower level than many people are accustomed to. Typically, people think about concepts like Line and Errorhandler and they imagine that the first is data and the second is behavioral. But the truth is, concepts are whatever we make them. We can put behavior where it makes sense in service of a sounder protocol.
I can always tell when people have been working primarily at the conceptual level. As you look downward, there's a point at which design just stops. When I'm curious about people's design habits, I look for micro-design, the small decisions. They seem to be good indicators of attention.
Note to Ruby cognoscenti: I know the code would be shorter with a Struct, but I decided to keep the example less idiomatic for a wider audience. - mf

Comments (8)
The last version is the worst IMHO. Line has no business knowing how the error handler should be invoked. This is how "library" code becomes "framework" code.
Much cleaner to return a string and have the calling code decide what to do.
Posted by Stephen | December 18, 2007 6:30 AM
Stephen, why? What's the rationale?
BTW, this is neither library code or framework code. It's application code.
Posted by Michael Feathers | December 18, 2007 7:15 AM
Hi Michael,
It's worse because it has been demodularized. You've created a depencency between Line and ErrorHandler when there was none before. The second version keeps them separate. http://en.wikipedia.org/wiki/Separation_of_concerns
Having said that, it's perfectly ok for implementation detail "private" code to use this style. It just makes it more difficult to use the Line class in another context.
Stephen.
Posted by Stephen | December 19, 2007 6:32 AM
Stephan, yes, there is that, and I agree that it is the traditional rule of thumb, but I do think that decisions like this can be contextual.
If there's no plan to ever reuse Line independently in another context there isn't much to worry about. If we do end up using Line someplace else, it should be easy enough to modify. FWIW, I don't think it would would ever be used reused independently.. it's a player in a little parsing ecosystem.
The fact that Ruby is dynamically-typed works in our favor also. Technically, there is no dependency from Line to Errorhandler, there's just a dependency from Line to anything that has a single argument method named report. In a way, this is something that Kevlin Henney calls a "design affordance." The class is giving us a hint about how it is supposed to be used (actually, IIRC, the term comes from Donald Norman).
I wouldn't fault anyone for going the other way, but more and more I like to move my code toward "tell, don't ask" style by default.
Posted by Michael Feathers | December 19, 2007 7:49 AM
If you don't plan to reuse the Line independently, why don't you just put it in the ErrorHandler ? This way the context dependency would be more explicit.
Posted by Alexandre | December 22, 2007 5:45 PM
Alexandre, there are different cardinalities. Every line in the source text gets a different Line object, and they all share an Errorhandler.
Posted by Michael Feathers | December 22, 2007 8:40 PM
Right, but I was not clear enough. I think that Ruby like a lot of oriented-object languages permit class encapsulation. Therefore, we could demodularize the Line class a bit more (use the ErrorHandler namespace) and avoid generic/specific class name collision.
We could have one Line class like the first version or i don't know, and one ErrorHandler.Line class like the last version. ErrorHandler.Line could also inherits from Line.
class ErrorHandler class Line def initialize(text, line_number, file_name) @text = text @line_number = line_number @file_name = file_name end def report_error(message_text, handler) handler.report("#{@file_name}(#{@line_number}): #{message_text}") end... handler stuff ...
end
end
Posted by Alexandre | December 22, 2007 9:25 PM
Alexandre, yes I was wondering if you were talking about an inner class.
As a matter of personal preference, I really don't like putting classes inside other classes unless there is a very compelling reason. I'm not quite sure what is common in Ruby, but I stick to that rule in Java, C++, and C# as much as I can. I also try to stick to one class per file in every language I program in also.
Admittedly, that makes my Ruby and Python a bit weird, but overall, I just find that it makes things easier to find and there's less mental parsing when trying to understand relationships. It's a simplification move and, I guess, a value judgment. Some of my things end up a little less encapsulated, but for me, the tradeoff in understanding is a win.
Posted by Michael Feathers | December 23, 2007 6:09 AM