Refactoring for Separation Of Concerns: A real world example
I just did a major refactoring to Rhino DSL, to add orthongality to the DslEngine. It was an interesting refactoring, and something that is worth talking about.
Let us take a look at the before image, first:
We have two classes that are involved in creating and managing DSL. Notice the FileFormat, CreateInput, GetMatchingUrlsIn and NotifyOnChange methods on the DslEngine? Or the GetFromCache, RemoveFromCache, SetInCache methods?
The firsts are related to the storage of the DSL scripts on disk and the second batch are related to caching, they have nothing to do with the actual work performed by the DslEngine. Those are completely different responsibilities and concerns. I didn't feel the pain of mixing the responsibilities until I had to create a class that wanted to change the storage mechanism of the scripts. Looking at it, the cache methods especially really want to move to another class. You can tell by their names.
Then I realized that I had to override a bunch of methods and correlate results between them, and I found myself thinking, "Who the hell wrote this nightmare?" *.
Changing the storage mechanism, something that I certainly wanted to support, has gotten fairly involved, and I couldn't see anyone figuring out all the knobs they had to turn without reading the source, carefully. This is a bad situation to be in.
Hence, refactoring.
What was involved in this was mostly moving code around. The technicalities of it where mostly these:
- Ask R# to extract super class with the unrelated methods.
- Ask R# to extract an interface from the new super class
- Remove the inheritance association with the DslEngine
- Add a property for the new interface type
- Initialize the property to use a default implementation
- Fix code that broke as a result (mostly involved adding the property name before the method).
- Rename methods to match current location (Removing the "Cache" from the methods in the caching interface).
The result is this:
Now, changing the storage mechanism doesn't need to affect the DSL implementation. We have a more robust model, and a good example for the blog.
Of course, I just finished writing half a chapter about the old way of doing things... Time for a rewrite.
* I often think thoughts like this, the sad answer is often: "Me".
Comments
Nice drop on refactoring.
Could you tell us what led you to the initial design without applying separation of concern from straight?
Was it the urge to get your implementation usable to solve a specific need?
Was it "blindness"?
Was it something else?
When I design/sketch/develop some concept, I tend to extract as much as possible to the exposed contract, just leaving the bare minimum available to the outside world to reach the need.
But there is still a problem with me: it's that I'm not the author of kickass frameworks such as you :)
"Of course, I just finished writing half a chapter about the old way of doing things... Time for a rewrite."
Don't you know rewriter#? ;)
I had a different goal in mind when I wrote the code, and I didn't notice the problem until I tried to actually use the extension points that I provided.
Comment preview