I figured I might as well continue on the string literal kick I started with my last post and talk about another situation where we've eliminated string literals. Take the following code from inside a MonoRail action method:
PropertyBag["User"] = ourUser;
PropertyBag["TwoStates"] = new string[] { "WA", "CA" };
PropertyBag["IsAdmin"] = true;
String literals, used as keys into dictionaries carry a cetain degree of code smell (for us) and we try to avoid them. It reminds me too much code that does something similar, but in the opposite direction:
int id = Int32.Parse(this.Params["id"])
Thankfully, this kind of code is eliminated when using MonoRail and its SmartDispatchController. We found ourselves thinking of how the same code would look in a SWF application. We would be populating views, only those views would be interfaces that the various forms/controls implemented. Well, this is exactly what we wanted in MonoRail, to wrap the PropertyBag with an interface!
After a few hours playing with Reflection.Emit, I had a code generator that would take an interface:
public interface IEditMyProfileView
{
string Name { get; set; }
DateTime Birthday { get; set; }
IList<TimeZone> TimeZones { get; set; }
}
And produce a class like the following:
public class EditMyProfileViewPropertyBagManipulator : IEditMyProfileView {
private IDictionary _bag;
public EditMyProfileViewPropertyBagManipulator(IDictionary bag) {
_bag = bag;
}
public string Name {
get { return (string)_bag["Name"]; }
set { _bag["Name"] = value; }
}
public DateTime Birthday {
get { return (DateTime)_bag["Birthday"]; }
set { _bag["Birthday"] = value; }
}
public IList<TimeZone> TimeZones {
get { return (IList<TimeZone>)_bag["TimeZones"]; }
set { _bag["TimeZones"] = value; }
}
}
This has the advantage of keeping our interactions with the PropertyBag as type safe as they can be. Changing the interface, breaks the build. Where as changing the type of a value inserted into the PropertyBag will (hopefully) only break tests if even that. This is another example of us trying to turn run-time failures into compile-time failures.
This kind of thing also makes tests more elegant. Instead of:
Assert.Equals("Jacob", PropertyBag["Name"]);
In our controller tests, we can continue to leverage the use of RhinoMocks to ensure the views are properly initialized:
using (_mocks.Unordered()) {
_view.Name = "Jacob";
}
_mocks.ReplayAll();
_controller.Action();
_mocks.Verify(_view);
All we've done here is created a mock from the view interface, rather than emitting the wrapper class. Again, another added benefit is if the view changes, compiling the tests will also break. The sooner things break after a change the better, and the build is pretty soon. Although, with ReSharper, it's nice to see red squiggles appear as that's even sooner.