Blocks and Glue

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:

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.

Copyright © 2003 Nat Pryce. Posted 2003-12-31. Share it.