Spike Solutions

One of the things I like about Extreme Programming is the spike solution. A spike solution is a small, focused effort to explore a possibility, or solve a problem, by writing code. I worked a bit on Declan today. Declan is the declarative XML transformation system I wrote about in a previous post.

I have a very simple Declan prototype up and running, but it has one little snag: declarative Declan statements must be part of the body of a class declaration, or they won't work. I want to be able to write a file with rules, and then load those rules with a load or require statement. This would be easy to do, but for one thing: as the rules file is loaded, Ruby will execute the statements. This is not a problem per se, but the Declan statements will change the state of the application. I need to store that state, without having an object handy to store it in.

Being something of a Ruby tenderfoot, I'd like to explore the solution I have in mind before committing serious effort to refactoring the Declan prototype. A spike solution seems to be in order, but first well have a look at how the prototype works. You'll soon see why I am not happy with it.

In the prototype the Declan statements are declared in a superclass. The subclass, where the transformations are declared, creates a singleton instance of itself, and then call declan statements as methods on the singleton instance. This not only sounds complex, it looks horrible. The following is an excerpt from a test case for the prototype:

class TestTransformer < Test::Unit::TestCase
XPATH_ROOT = "root"
XPATH_CH_MATCHED = '//ch[@class="matched"]'

class Template < Declan::Transformer
attr_reader :rules, :node_hash

# Make a singleton
private_class_method :new
@@template = nil

def Template.create
@@template = new unless @@template
@@template
end

# Instantiate the singleton
Template.create

# These method calls are executed when the class is read in.

@@template.match(XPATH_ROOT) {|root|
<<-"ENDTEMPLATE"
<book>
#{@@template.apply_templates}
</book>
ENDTEMPLATE
}

@@template.match(XPATH_CH_MATCHED) {|ch|
<<-"ENDTEMPLATE"
<s status="#{ch.attributes['class']}">
#{@@template.apply_templates}
</s>
ENDTEMPLATE
}
end
#Setup and tests follow here
end

The first thing to do is to make a simplified model of the system I have now. This is the simplest thing I could come up with:

#! /usr/bin/ruby
class Flag
attr_reader :state

def initialize
@state = false
flip # Declarative statement
end

def flip
@state = !@state
end
end

flag = Flag.new()
puts flag.state

I have mashed things together a bit. The declarative statement (the flip method) is defined in the same class where it is used. I also dispensed with the declare a singleton nonsense by moving flip into the initialize method. Still, it's the same thing in principle, given that we have sufficiently flexible principles. BTW, when executed, the program prints true.

It would be nice if I could just break out the call to flip in a separate file, then include it, but I don't think that'l work. Nevertheless, I give it a try:



#! /usr/bin/ruby

class Flag
attr_reader :state

def initialize(rules)
@state = false
load rules
end

def flip
@state = !@state
end
end

flag = Flag.new('declarative_rules.rb')
puts flag.state

The declarative_rules.rb file looks like this:

flip

As expected, it does not work. The rules file executes as it loads, and at that time, there is of course no object to call the method on. However, the following does work:

#! /usr/bin/ruby

@state = false

def flip
@state = !@state
end

load 'declarative_rules.rb'

puts @state

The flip method is defined before I load the rules file, so the flip statement in the rules file can execute with no problem. The catch is that the @state property and the flip method are both defined in the top-level execution environment. This isn't good, because sometimes I do want to write Declan code inside an object. I just do not want to have to do it. However, I can move them out into a module of its own so I can mix it back in however I want. This is my mixin module:
@state = false

def flip
@state = !@state
end

And this is the final version of the program that executes the declarative code:

#! /usr/bin/ruby
require 'decltest4_mixin'
load 'declarative_rules.rb'
puts @state

Doesn't look like much, but it does tell me how to proceed when refactoring Declan. I'll move the definitions of the Declan statements from the Declan::Transformer class to a separate module that I can mix into any class that I want. I also know that I can dispense with the awkward singleton by moving the statements from the class body to the initialize method.

Now that I have done the spike, it seems very obvious. Can't understand why I didn't think of it right away...

Comments

Popular posts from this blog

Waterfall - The Dark Age of Software Development Has Returned!

Waterfall vs. Agile: Battle of the Dunces or A Race to the Bottom?

Interlude: The Cost of Agile