Confounded by configuration
Sweet love, I see, changing his property, Turns to the sourest and most deadly hate. - William Shakespeare.
I recently wrote a simulation game as a Java applet for the website of a well known motor racing team. The configuration of the game - track, car performance, local weather and even the GUI layout and branding - was read from property files when the game started up.
The way I designed it, any object that needs to be configured implements the Configurable interface:
public interface Configurable { public void configure( PropertySet properties ) throws ConfigureException; }
Configurable objects construct themselves into some neutral, initial state which is then overridden by the values read from the property set (a set of name-value pairs) passed to the configure method.
The init method of the applet loads the properties, creates the objects it needs and configures them (or catches and records errors). Then the start method checks that everything is properly configured and ready to go and kicks off the animation thread.
A nice, simple mechanism? No. Another sodding mistake!
This architecture makes it much too hard to write unit tests. Any configurable object that needs to be used in a test must be configured, but that can only be done indirectly by creating a property set for it. There are several ways of creating property sets, but all of them have drawbacks. I can build property sets explicitly in the set-up phase of each test, but that makes the tests very verbose. I could load propertys set from files, but that splits test suites into separate code and data files, which makes editing tests a real pain. I could load a property set from a string constant is has the same drawback as building a property set explicitly, except with a worse syntax and more throws clauses. The problem is only compounded by composite configurable objects that must create and configure their sub-objects as directed by their own configuration.
As a work around, I could expose configurable properties of the objects as getters and setters, but methods that are only used for testing make me feel uncomfortable; they are a definite sign of a bad design.
The problem is with the architecture itself. The mistake was to hide configuration within the objects being configured. I should have pulled it out into one or more objects that interpret configuration data by creating objects with the appropriate state. I would no longer need the Configurable interface, because configurable properties would be passed to the objects' constructors. Tests could use the same constructors to create objects. The configuration interpreter would be easy to test by using a factory to create objects and using a mock factory object in tests. The mock factory would also make the configuration of composite objects trivial to test: creating a composite object would involve asking the factory to create a sub-object multiple times.
This is basically the GoF Builder pattern.
Oh well, I learn from my mistakes. Thank goodness.