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
