-
- One of the most important lessons I have ever learned as a software
developer is to keep things simple. It’s also one of the hardest
lessons to remember, because as programmers, we revel in the complex.
Sometimes I am forced to reconsider designs where I let my love of
complexity override my good sense for simplicity.
Revisiting Builder
Here’s one small example where complexity got away from me. My last
blog entry mentioned BuilderObjects used to
build XML markup. It turns out that these builders are really useful and
I’ve been making good use of them in my latest web project.
However, as I was using, I discovered a rather annoying feature. If you
recall, builders depended on the method_missing feature of Ruby to
implement arbitrary XML tags on the fly. But, in order for code like this
…
builder.p { em("Hello World") }
to work, the em message must be sent to the builder object.
Normally unadorned messages are sent to self. However, the builder
object evaluated its block in a special way to hijack the value of self to
point to itself.
It is really cool that we just need to mention the tag name and it
automatically gets generated. But there is a downside. Suppose we replace
the literal "Hello World" with a method called greet
that dynamically determines the proper form of greeting. Now the code looks
like this…
builder.p { em(greet) } # greet is a method
Now, not only em, but also greet gets sent to the
builder. This is wrong, greet is a method defined on the current
object, not the builder. To get around this, we must save the value of self
outside the block and make it available under a different name. Perhaps
like this…
s = self
builder.p { em(s.greet) }
As I was using builder I notice I was doing the s = self step on
almost everytime I used a builder. And when I forget to use the s,
I would get weird bugs in my HTML output.
Clearly I was being too clever (in my defense, I just copied the Groovy
design for their builders). So I fixed the builder to use normal blocks
with normal evaluation and the number of accidental bugs went way down. The
greet example now reads like this under the new version of
builder:
builder.p { builder.em(greet) }
Now, since is it awkward to keep mentioning the builder name over and over,
it would be nice to have a shortcut. Hence builders pass themselves as a
parameter to the blocks. This gives us the opportunity to use a shorter
name, but only with in the block. Again, the greet example is
builder.p { |xml| xml.em(greet) }
(The shorter name isn’t a big advantage here, but is a big
convenience when many tags are created.)
So, it gets back to that old adage: KISS — Keep It Simple Stupid!
Postscript …
Builder is availble as a gem. Version 1.0.0 has the new, simpler interface.
Because Builder is a gem, you can install both versions and use either, as
long as you don’t mix their usage in a single application. To use the
new version (after installing), just say
require_gem 'builder', '~> 1.0'
This allows you to use any version greater than 1.0 but less that 2.0. To
use the old interface, say …
require_gem 'builder', '~> 0.1'
This allows any version 0.1 or later, but rejects version 1.0 and up. The
~> operator is called the Pessimistic version constraint is is helpful
when versions of software have incompatible interfaces. The RubyGems wiki
has more information about pessimistic
version constraint and about versioning
policies.
comments
|