Blocks and Glue
When writing Ruby libraries it is tempting to use code blocks as a way for the user to hook into the library or specify custom behaviour. This leads to a lot of code duplication, since blocks cannot easily be factored into a class hierarchy or mixins. Code blocks are no substitute for a good object model. Blocks are good for creating control structures and "glue" between objects, but should be refactored into methods or classes when they become longer than one or two lines.
When writing and using the Ruby dynamic mock library I used code blocks as the way that the programmer defined expected method signatures and checked expectations. For example:
mock.expect :set_property do |name,value| assert_equals( "Content-Type", name ) assert_equals( "text/html", value ) return nil end
However, using blocks in this way has a number of disadvantages:
- They cannot be turned into a useful representation in error messages
- There is a lot of duplicated assert statements among expectations
- Expectations cannot easily be reused
- Expectations aren't represented as named concept, they just exist as undifferentiated lines of code
Constraints
solved all these problems: they name what they are used for (e.g.
IsEqual vs. IsSame), can easily be reused and combined (and, or,
not), can describe themselves (toString
). With Java
dynamic mocks, the expectation above would be written as follows,
where eq
is a factory method that creates an
IsEqual
constraint.
mock.method("setProperty") .with(eq("Content-Type"),eq("text/html")).willReturn(null) .expectOnce();
Factoring out the concept of a constraint into the Constraint interface and various implementations gave us additional benefits for free: we could use Constraints to set up different expectations based on argument values, for example.
I've come to the conclusion that blocks are good for creating control structures and "glue" between objects, but should be refactored into methods or classes when they become longer than one or two lines.