Just a quick blog post to present a very concrete occurrence of Evolutionary Design + Levelization in action. I just stumbled on this occurrence after many days of large-scale refactoring. It was like the ice on the cake that concludes a lot of work. I presented the theory behind it in two previous blog posts, that will be refactored into a white book during the next few months: Re-factoring, Re-Structuring and the cost of Levelizing and Evolutionary Design and Acyclic componentization (to be read in this order).
So basically a lot of refactoring is actually occurring in the NDepend code base. Hundreds of types are moved, new namespaces are created, new abstractions are created too to respect the Acyclic componentization principle. All this work is almost automatic, almost no brain is solicited. The work is only guided by primary design decisions like, this feature set cannot be directly used by this one, this feature set is about to be shared, this one will be top-level, this one will be low-level. The origin of these decisions is to append a huge new features set (already developed as a prototype and then refined) to the product. This new features set represents more than 10% of the actual entire product weight in terms of Lines of Code.
I was about to conclude one of the major step when I noticed that impacted components were not totally levelized. Two of them were actually dependent on each other. This problem was revealed by a pesky black cell on the Dependency Structure Matrix.
So both namespaces CQ.InternalAPI and CQ.InternalAPI.Edition were mutually dependent. So I opened the dependency (right click one of the black cell > Open this Dependency) and obtained the following Dependency Matrix.
An interface from the namespace CQ.InternalAPI.Edition is using the enumeration QueryEditionOptions from the namespace CQ.InternalAPI. Clearly, the enumeration QueryEditionOptions is related to the query edition semantic. So obviously, to remove the cycle I just have to bubble up the enumeration QueryEditionOptions from CQ.InternalAPI to CQ.InternalAPI.Edition. Because an enumeration is not using any other type than primitive .NET numeric types, I don’t even have to check if QueryEditionOptions is using anything from the namespace CQ.InternalAPI (else, the move wouldn’t necessarily remove the dependency from CQ.InternalAPI.Edition to CQ.InternalAPI.Edition).
All in all, it took less than 5 minutes to discover and fix the issue. But I found this tiny issue being a good occurrence to illustrate the power of evolutionary design applied conjointly with component levelization.
Before performing a major refactoring, one just has to do impact analysis. From this analysis one has enough information to take the adequate new design decisions. These design decisions modelize what will the application architecture look alike once the refactoring done. Applying the design decisions is then just a matter of doing automatic actions. This actions are mainly made of: moving types accross namespaces, create/delete namespaces, create/delete abstractions. The beauty is that as long as the structure is kept levelized, the process is powerful enough to spot automatically mistakes such as having QueryEditionOptions living outside the namespace CQ.InternalAPI.Edition.
As said in a previous post, by keeping components levelized, like in traditional building architecture, the structure itself put the pressure on low level components. Like in traditional building architecture, the structure won’t collapse. Applying this simple tenet for years, it now seems to me that this completely solves the problem of maintaining code over time.