In my previous post on Re-factoring, Re-Structuring and the costof Levelizing, I explained that increasing the value of the structure of a code base isless costly than expected. The point is to focus a while on Re-Structuring without changing any behavior (Re-Factoring). The biggest motivation for re-structuring code is toget rid of dependency cycles between your components (namespaces orassemblies). Once a code base is levelized(the proper term to say, once there areno more dependency cycles between your components), every refactoring taskis naturally easier because you can always define clearly which levels of thecode base are impacted.



Personally I am a fan of evolutionary designas explained by Martin Fowler: Withevolutionary design, you expect the design to evolve slowly over the course ofthe programming exercise. There's no design at the beginning. You begin bycoding a small amount of functionality, adding more functionality, and lettingthe design shift and shape.



One point is that itis not possible to apply evolutionary design without a solid suit of automatictest. The risk of introducing regressions bugs would be too high. This is why I understand automatic tests as a way to mirror and scaffold the behavior of a codebase. Once the code base and its automatic tests are in-sync (i.e when alltests are green), they both define the same behavior. If the behavior of the codegets modified, some tests are not in-sync anymore with the code. If the behavior deltais a regression bug, the code needs to be aligned with tests. If the behavior deltais a new feature/requirement, the tests need to be aligned with the code.



The principle I would liketo expose here is: Applying EvolutionaryDesign is easier once the components dependency graph has no cycles. Levelizedcomponents are of course not a replacement for a solid test suit, it doesn't deal with correctness and behavior. But the point of this blog post is to show that levelized components it is a goodcomplement to unit test to apply evolutionary design.





The Need to define new Abstractions



In the evolutionarydesign discipline, amongst all refactoring motivations, I estimate that the need to define newsuited abstractions is essential. Concretely, in evolutionary design youare not supposed to create an interface implemented by one only class. Theclass is used by the consumer code, and if in the future you’ll get the need toprovide a second or more implementations, then it will be time to create the interface,its associated factory, and the logic to plug with the correct implementation.Typically fool me once, don’t fool metwice. In other words: I was not supposed to anticipate the need for an abstraction thefirst time, but I won’t hesitate to create it when I will face the need a second time.





Creating new Abstractions: the easy scenario



Creating one abstractionto abide by one simple new requirement is easy. It becomes problematic when amassive new functional requirement will have impacts a bit everywhere in your codebase. You’ll need to create many interfaces to create what is named an abstractfaçade.



The typical case study isthe need for some new RDMS. So far the application consumed only SQL Serverdata, but now it has also the need to consume Oracle data. This scenario is sotypical that ADO.NET supports it out of the box. All the implementations toaccess a particular RDMS (what is named a dataprovider) can be encapsulated behind an abstract façade obtained from the System.data.Common.DBProviderFactoryclass. This data provider case studyis seamless because the implementation of a data provider is cohesive and welldecoupled from the rest of the .NET framework.





Creating new Abstractions: the real-world complexscenario



Let’s focus on a real-worldexample we had to face recently during the development of NDepend: the need was to letNDepend work on other platform than .NET, concretely Java first (the XDepend product) and then some others (C++ is in the pipe). The bulk of thecode is platform agnostic and then, the idea makes sense in terms of Return over Investment. More precisely, thecode specific to the .NET platform represents in terms of Lines of Code 11.8%of the whole code base and we can visualize it through the metric view:








But this view representswhat we obtained once we regrouped in a dedicated assembly the code specific to.NET. This code is isolated from the rest of the code base behind an abstract façade.At the beginning, the code specific to .NET was spawned all over the code base.This included the code needed to analyze .NET assemblies, the CQL specific .NETterminology (SELECT ASSEMBLIES…), the panel to let users define assemblies toanalyze, the specific parsing of .NET coverage files, the Reflector andVisualStudio add-in, not to mention all the visual tool-tips and UI labelscontaining .NET specific vocabulary (IL code, assembly, attribute…). Concretely,in the metric view below, each blue rectangle represents amethod/type/namespace/assembly that had some code specific to the .NET platform:








We were in the typicalevolutionary design problematic: since the beginning we didn’t bother withthe fact that NDepend might be used on another platform than .NET. Basically thenew design we decided to put in place looked like this:







The fact that the codebase was kept levelized since the beginning was a blessing to perform thismassive re-structuring. That is what I am about to explain.



First, the grouping of the code specificto .NET in a dedicated assembly was naturally levelized andcomponentized. At this point there were no questions like who’s high level, who’slow level, who’s depend on what. All these questions were already implicitly answeredin the original design because it didn't contain dependencies cycles.



Second, because the originaldesign was levelized, the composition of interfaces in the abstract facade camenaturally as a hierarchy. Here also the answer to questions like which interfacepresent which feature and which property, were already contained in theoriginal design.



Third, the abstract façadeneeded to remain at the bottom level. Every component use it but it cannot useanything else than tier code (like primitive types). Actually we had a problemhere. We defined a library to represent some strong-typed file and directory paths:NDepend.Helpers.FileDirectoryPath.We wanted the abstract façade able to expose such typed path objects. So thepath library needed to be below the abstract facade. Fortunately, the pathlibrary didn’t use anything else than primitive types like string and char.This was not by chance but because the original code base was levelized. Asroughly every components are using this path library, the path library hasnever been allowed to use anything else than tier code. Thus, placing the pathlibrary below the abstract façade was a matter of minutes.







Conclusion



The lesson here is that keeping a code base levelized is an easy way to implicitlyanticipate future requirements. Low-level components never get achance to bubble-up in the architecture not because someone decided so, butbecause above components won’t let it bubble-up. Like in traditional building architecture, the structure itself put the pressure on low level components. In our example, the path library has never been allowed to use anything else than the .NET framework, not because we created a rule for that, but because it is roughly used everywhere in the code and we forbid dependencies cycles.



A nice consequence is that keeping a code base levelized discards theneed for most design decisions. Good design is implicitly and continuously maintained. Thereare no questions about what to do to implement new requirement. When planning newcode to implement the un-forecasted requirement, you just have to consider its fan-in/fan-out(who will use this new code and who this new code will use). From this informationand from the need to preserve levelization, you’ll infer the level and theright place where this new code needs to be added. Maybe you’ll need to use theinjection of code or inversion of dependency patterns but only topreserve levelization, not because it seems cool to do so. And releases after releases,iterations after iterations, the design will evolve seamlessly toward somethingflawless and unpredictable. Like in traditional building architecture, the structure won't collapse.




More...