by Zoran Horvat
Aug 04, 2015
Module coupling is one of the most important metrics in software design. It gives us the clue how good we are at using types defined in other modules. There are two kinds of couplings - efferent and afferent.
In this article we will start from the definition of class coupling and then quickly progress through module coupling so that we can finally define instability metric.
Efferent coupling of a type is number of other types it knows about - it may derive from them, contain fields of those types, receive them as method arguments or return them as method results, use them in method implementations, throw them as exceptions, etc. Higher the efferent coupling, harder to maintain the type. This is because change in any efferent dependency potentially requires change in this type.
We find interfaces to have low efferent coupling. If the interface is only used by concrete types, its efferent coupling will remain zero. Of course, interface could use other types through method arguments and return values. That causes the interface efferent coupling to grow larger than zero. In that respect, we want the interface only to know about other interfaces, not other concrete types.
Afferent coupling is the opposite to efferent coupling. Afferent coupling of a type counts other types that know about it. Higher the afferent coupling of a type, greater the consequences are when we decide to change this type. Hence the rule of thumb - invest effort in good design of types with high afferent coupling, because subsequent changes might incur high cost.
Total coupling of a class is the sum of its efferent and afferent coupling.
Whenever a class is coupled to a class from another module, we say that its containing module is also coupled to the other module. Module coupling is measured by counting classes from other modules that it is coupled with. Of course, one class may couple with another class from the same module. Such pair of classes does not contribute to module coupling because it evens out.
In other words, we can measure efferent coupling of a module as the count of classes from other modules that classes from this module know about. Conversely, afferent coupling of a module is the count of classes it contains that classes from other modules know about.
Total coupling of a module is calculated by summing up its efferent and afferent coupling.
Instability of a class or a module is the ratio between its efferent coupling and total coupling. Obviously, instability is a value between 0 and 1, inclusive.
Instability of zero means that the class or module is absolutely stable. It depends on nobody, everybody depend on it. The term "stable" itself indicates the desired quality of such class or module. We want such class never to change, we want it to be absolutely stable, or otherwise we will have to maintain other classes that depend on it whenever we change it.
Instability equal to one means that the class or module is absolutely instable. It depends on other classes and must be changed when other classes are changed. On the other hand, changing this class means nothing to others. That is why we call it "absolutely instable" - we can change it liberally and no harm will be done to other parts of the system.
Picture below shows class diagram of one particular layered application. Presentation layer contains a view which supposedly displays the user view model. This view class directly references service class in the domain layer. UserServices class, on the other hand, references a repository from the infrastructure layer.
This class model is far from the best we could invent. Layers are tightly coupled to the lower regions of the application, which is a well known recipe for maintenance hell. On the other hand, Presentation and Domain layers exhibit circular dependency. Should we try to separate these two layers into two distinct assemblies, the application wouldn't build.
Anyway, this class diagram is a good example in which coupling and instability metrics can be used to support arguments in favor of refactoring the design. Let's see how it goes with coupling of these modules. We will assume that each layer is one module - so we have Presentation, Domain and Infrastructure modules in the application.
Presentation module is referencing the UserServices class only, making its efferent coupling 1. Its afferent coupling is also 1, because UserViewModel is referenced from inside the Domain layer.
Domain module has efferent coupling 2, because it is referencing two classes from the Presentation and the Infrastructure modules. Its afferent coupling is 1, caused by the Presentation module referencing one of its classes.
Infrastructure module at the bottom of the diagram only has afferent coupling of 1 and no efferent coupling, i.e. efferent coupling is zero.
The following table summarizes the coupling and instability of all three modules.
|Module||Efferent Coupling (Ce)||Afferent Coupling (Ca)||Instability Ce / (Ce + Ca)|
Now that we have the numbers, we can analyze them. Presentation and Domain modules are half-way there - neither stable nor instable. Things depend on them and if we make changes to them someone is going to be hurt. This situation is not the best possible for the Presentation layer. We want it to be stable. We want everyone to be fully subdued to the presentation. In the end, presentation is the only part of the application the user will ever see.
With instability of 0.5, Presentation layer is in danger of having to be changed when other parts of the system are changed. This doesn't really make sense, because other parts of the application are supposed to do things that are useful to the Presentation layer, not the other way around.
Infrastructure layer, on the other hand, is completely missing its purpose. With absolute stability, which is identified by instability value zero, it turns that everyone in this application have to adapt to the infrastructure. That is way out of our normal goals. We want the Infrastructure layer to be absolutely instable, with instability value 1, so that it has to adapt and change whenever the needs of the application are changed. Upper layers of the application should not have to change when anything in the Infrastructure is changed.
Now that we have detected the issues with previous design, we are ready to start refactoring. We will simply apply the Dependency Inversion Principle (DIP) to decouple the layers. Also, lower layers will have to depend on upper layers - not the other way around. Picture below shows the modified class diagram which follows DIP and presumably solves the problem.
At first glance, we can clearly see that modules have been mostly decoupled. All coupling is towards abstract types (interfaces), just as suggested by DIP. Also, we can see that this time lower layers are dependent on higher layers. This is also in favor of DIP. These observations will be clearly visible if we take a look at the following table, which shows coupling and instability metrics for different modules in the application after refactoring.
|Module||Efferent Coupling (Ce)||Afferent Coupling (Ca)||Instability Ce / (Ce + Ca)|
Starting from the Presentation module, we see dramatic change compared to previous table. This time, Presentation module is absolutely stable. This was our design goal and now that we have accomplished it, we should make every effort to design Presentation layer right. Any change in this layer may force other parts of the application to change.
On the other hand, instability of the Domain layer remained the same. Only this time, its dependencies have been rotated. Instead of depending on lower layers, Domain layer now depends on the higher layer.
Finally, Infrastructure layer has also suffered dramatic change. This time, it is absolutely instable. Changes made to higher layers will cause Infrastructure layer to adapt. That is precisely what we want to have in our application. We want the infrastructure to follow more abstract needs of the application. We don't want abstract needs of the application to be affected by infrastructure implementation.
In this article we have seen how coupling and instability metrics can be used to analyze certain aspects of the application design. Not only that we could see the positive change in the class diagram, but we could also confirm that refactoring goals have been accomplished by looking at coupling and instability metrics of the application modules.
In larger applications, changes in class diagram are often harder to see, because class diagrams may become very complicated. In that case we can rely on coupling and instability code metrics to give us more than satisfactory insight into the processes that are unfolding while we are refactoring the design.
If you wish to learn more, please watch my latest video courses
In this course, you will learn how design patterns can be applied to make code better: flexible, short, readable.
You will learn how to decide when and which pattern to apply by formally analyzing the need to flex around specific axis.
This course begins with examination of a realistic application, which is poorly factored and doesn't incorporate design patterns. It is nearly impossible to maintain and develop this application further, due to its poor structure and design.
As demonstration after demonstration will unfold, we will refactor this entire application, fitting many design patterns into place almost without effort. By the end of the course, you will know how code refactoring and design patterns can operate together, and help each other create great design.
In four and a half hours of this course, you will learn how to control design of classes, design of complex algorithms, and how to recognize and implement data structures.
After completing this course, you will know how to develop a large and complex domain model, which you will be able to maintain and extend further. And, not to forget, the model you develop in this way will be correct and free of bugs.
Zoran Horvat is the Principal Consultant at Coding Helmet, speaker and author of 100+ articles, and independent trainer on .NET technology stack. He can often be found speaking at conferences and user groups, promoting object-oriented and functional development style and clean coding practices and techniques that improve longevity of complex business applications.