Evolutionary Design and Acyclic componentization

 

In my previous post on Re-factoring, Re-Structuring and the cost
of Levelizing
, I explained that increasing the value of the structure of a code base is
less 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 to
get rid of dependency cycles between your components (namespaces or
assemblies). Once a code base is levelized
(the proper term to say, once there are
no more dependency cycles between your components
), every refactoring task
is naturally easier because you can always define clearly which levels of the
code base are impacted.

 

Personally I am a fan of evolutionary design
as explained by Martin Fowler:  With
evolutionary design, you expect the design to evolve slowly over the course of
the programming exercise. There’s no design at the beginning. You begin by
coding a small amount of functionality, adding more functionality, and letting
the design shift and shape
.

 

One point is that it
is not possible to apply evolutionary design without a solid suit of automatic
test. 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 code
base. Once the code base and its automatic tests are in-sync (i.e when all
tests are green), they both define the same behavior. If the
behavior of the code
gets modified, some tests are not in-sync anymore with the code. If the
behavior delta
is a regression bug, the code needs to be aligned with tests. If the
behavior delta
is a new feature/requirement, the tests need to be aligned with the code.

 

The principle I would like
to expose here is: Applying Evolutionary
Design is easier once the components dependency graph has no cycles
. Levelized
components 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 good
complement to unit test to apply evolutionary design.

 

 

The Need to define new Abstractions

 

In the evolutionary
design discipline, amongst all refactoring motivations, I estimate that the need to define new
suited abstractions is essential
. Concretely, in evolutionary design you
are not supposed to create an interface implemented by one only class. The
class is used by the consumer code, and if in the future you’ll get the need to
provide 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 me
twice
. In other words: I was not supposed to anticipate the need for an abstraction the
first 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 abstraction
to abide by one simple new requirement is easy. It becomes problematic when a
massive new functional requirement will have impacts a bit everywhere in your code
base. You’ll need to create many interfaces to create what is named an abstract
façade
.

 

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

 

 

Creating new Abstractions: the real-world complex
scenario

 

Let’s focus on a real-world
example we had to face recently during the development of NDepend: the need was to let
NDepend work on other platform than .NET, concretely Java first (the XDepend product) and then some others (C++ is in the pipe). The bulk of the
code is platform agnostic and then, the idea makes sense in terms of Return over Investment. More precisely, the
code 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 represents
what 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 .NET
terminology (SELECT ASSEMBLIES…), the panel to let users define assemblies to
analyze, the specific parsing of .NET coverage files, the Reflector and
VisualStudio add-in, not to mention all the visual tool-tips and UI labels
containing .NET specific vocabulary (IL code, assembly, attribute…). Concretely,
in the metric view below, each blue rectangle represents a
method/type/namespace/assembly that had some code specific to the .NET platform:

 


 

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

 

 

 

The fact that the code
base was kept levelized since the beginning was a blessing to perform this
massive re-structuring. That is what I am about to explain in 3 points.

 

First, considering the sub-components (i.e the namespaces) of the new .NET specific assemblies, they were naturally levelized. At this point there were no questions like who’s high level, who’s
low level, who’s depend on what. All these questions were already implicitly answered
in the original design because it didn’t contain dependencies cycles. There were no questions also about which partitioning code in components, they already existed in the original design.

 

Second, because the original
design was levelized, the composition of interfaces in the abstract facade came
naturally as a hierarchy. Here also the answer to questions like which interface
present which feature and which property, were already contained in the
original design.

 

Third, the abstract façade
needed to remain at the bottom level. Every component use it but it cannot use
anything else than tier code (like primitive types). Actually we had a problem
here. 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 the
path library needed to be below the abstract facade. Fortunately, the path
library 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. As
roughly every components are using this path library, the path library has
never been allowed to use anything else than tier code. Thus, placing the path
library below the abstract façade was a matter of minutes. Dear reader, I am sure that you experienced in the past 
the joy of beging stuck many days to accomplish basically the same task on a real-world spaghetti/monster code base : rationalize dependencies to change the location of a component(s) in the overall structure of a code base (your best stories are welcomed in this post’s comments).

 

 

 

Conclusion

 

The lesson here is that keeping a code base levelized is an easy way to implicitly
anticipate future requirements
. Low-level components never get a
chance to bubble-up in the architecture not because someone decided so, but
because 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 primitive types, 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 the
need for most design decisions
. Good design is implicitly and continuously maintained. There
are no questions about what to do to implement new requirement. When planning new
code 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 information
and from the need to preserve levelization, you’ll infer the level and the
right location where this new code needs to be added. Maybe you’ll need to use the
injection of code or inversion of dependency patterns but only to
preserve levelization, not because it seems cool to do so. And releases after releases,
iterations after iterations, the design will evolve seamlessly toward something continuously
flawless and unpredictable.
Like in traditional building architecture, the structure won’t collapse.

This entry was posted in Uncategorized. Bookmark the permalink. Follow any comments here with the RSS feed for this post.