Code Quality For Software Architects
From NeoWiki
- Use coupling metrics to support your system architecture
Andrew Glover, President, Stelligent Incorporated
25 Apr 2006
- Most well-designed software architectures are intended to support a system's extensibility, maintainability, and reliability. Unfortunately, inattention to quality issues can easily undermine a software architect's best effort. In this installment of In pursuit of code quality, quality expert Andrew Glover explains how to continuously monitor and correct quality aspects of code that can affect the long-term viability of your software architecture.
Last month, I showed you how to use code metrics to evaluate the quality of your code. While the cyclomatic complexity metrics introduced in that column focus on low-level details, such as the number of execution paths in a method, other types of metrics focus on more high-level aspects of code. This month, I'll show you how to use various coupling metrics to analyze and support your software architecture.
I'll start out with two of the more interesting coupling metrics, namely afferent coupling and efferent coupling. These integer-based metrics represent a count of related objects (i.e., objects that coordinate with each other to produce behavior). High numbers in either metric can signify architectural maintenance issues: High afferent coupling indicates an object has too much responsibility, and high efferent coupling suggests the object isn't independent enough. This month, I'll look at each of these problems and some ways to get around them.
Contents |
Afferent coupling
Having too much responsibility isn't necessarily a bad thing. For example, components (or packages) often are intended to be utilized throughout an architecture, which gives them high afferent coupling values. Core frameworks (like Struts), utilities like logging packages (like log4j), and even exception hierarchies usually have high afferent coupling.
In Figure 1, you can see a package, com.acme.ascp.exception, with an afferent coupling of 4. This isn't a surprise because the web, dao, util, and frmwrk packages would all expect to utilize a common exception framework.
Figure 1. Signs of afferent coupling
As you see in Figure 1, the exception package has an afferent coupling, or Ca, of 4, which in its case isn't such a bad thing. Exception hierarchies rarely change dramatically. Monitoring the afferent coupling of the exception package is a good idea, however, because drastic changes to the behavior or contract of exceptions in this package could cause ripple effects throughout its four dependent packages.
Measuring abstractness
By further examining the exception package and noting the ratio of abstract to concrete classes, you can derive another metric: abstractness. In this case, the exception package has an abstractness of zero because all its classes are concrete. This correlates with my earlier observation: The high degree of concreteness in the exception package means that any changes to exception will affect all related packages, namely com.acme.ascp.frmwrk, com.acme.ascp.util, com.acme.ascp.dao, and com.acme.ascp.web.
By understanding that afferent coupling denotes a component's responsibility and by monitoring this metric over time, you can shield a software architecture from entropy, which some say naturally occurs even in the most well-designed systems.
Support design flexibility
Many architectures are designed with flexibility in mind when utilizing third-party packages. Flexibility ideally is gained by using interfaces to shield the architecture from changes within third-party packages. For example, system designers could create an internal interface package to utilize third-party billing code but only expose interfaces to those packages that use the billing code. This, by the way, is similar to the way JDBC works.
Figure 2. Flexibility by design
As Figure 2 demonstrates, the acme.ascp application is coupled to a third-party billing package through the com.acme.ascp.billing package. This creates a level of flexibility: If another billing package from a third party becomes more advantageous to utilize, then only one package should be affected by the change. What's more, com.acme.ascp.billing's abstractness value is 0.8, which indicates it can be shielded from modifications through its interfaces and abstract classes.
If you were to switch third-party implementations, any refactoring would need to happen to only the com.acme.ascp.billing package. Even better, by designing-in this flexibility and understanding the implications of change, you can protect yourself from any damages from modifications through developer testing.
Before making changes to the internal billing package, you could analyze a code coverage report to determine if any tests actually tested the package. On finding some level of coverage, you could more closely examine those test cases to verify their adequacy. If you found no coverage, you would know that the level of effort to switch out and insert a new library would be riskier and could take longer.
Gathering all these factoids is very easy using code metrics. On the other hand, if you know nothing of a package's coupling related to its test coverage, then ascertaining the time to replace a third-party library is, at best, a guess!
Monitor for entropy
As I mentioned earlier, entropy has a way of working itself into even the most well-planned architectures. Either through team attrition or poorly documented intents, uninitiated developers can inadvertently import what appears to be a useful package, and before long, your system's afferent coupling values begin to grow.
For example, compare Figure 3 with Figure 2. Do you see the increased brittleness of the architecture? Not only does the dao package now directly utilize a third-party billing package, but another package that wasn't even intended to use any billing code directly references both billing packages!
Figure 3. Code entropy creeps in
Attempting to switch out the com.third.party.billing package for another one is going to be challenging indeed! Just imagine the test scaffolding that would be required to mitigate the risks of introducing defects and breaking various behavioral aspects of the system. In fact, architectures like this one rarely change because they can't support modification. Worse, even important modifications, such as upgrades to existing components, can cause things to break throughout the code base.
Efferent coupling
If afferent coupling is a count of components that depend on a particular component, then efferent coupling is the count of components that a particular component depends on. Think of efferent coupling as the inverse of afferent coupling.
The implications of efferent coupling are similar to those of afferent coupling, with regard to how changes affect code. For example, Figure 4, depicts the com.acme.ascp.dao package, which has an efferent coupling, or Ce, of 3:
Figure 4. Efferent coupling in the dao package
As Figure 4 shows, the com.acme.ascp.dao package depends on the org.apache.log4j, com.acme.ascp.util, and com.acme.ascp.exception components to fulfill its behavioral contract. As is true of afferent coupling, the level of dependence isn't a bad thing in and of itself. It's your knowledge of the coupling and how it could affect changes to related components that matters.
As with afferent coupling, the abstractness metric comes into play in efferent coupling. In Figure 4, the com.acme.ascp.dao package is completely concrete; hence its abstractness is 0. This means that components whose efferent coupling includes com.acme.ascp.dao could themselves become brittle because of com.acme.ascp.dao's efferent coupling on three additional packages. If one of them changes (say com.acme.ascp.util), a ripple effect could occur within com.acme.ascp.dao. Because dao is unable to hide implantation details through interfaces or abstract classes, any changes could then impact on its dependent components.
Coupling plus coverage equals ...
Examining efferent coupling's relationship data and relating it to code coverage facilitates smarter decision making. For instance, imagine that a new requirement is handed down to your development team. You're able to pinpoint changes related to this requirement to the com.acme.ascp.util package shown in Figure 4. Also, in the past few releases, the dao package, which depends on util and has zero abstractness, has suffered from a number of high-priority defects (most likely due to limited developer testing on this package, which interestingly is most likely because of high complexity values within the code).
You have an advantage in this situation because you understand the relationship between com.acme.ascp.util and com.acme.ascp.dao. Knowing that the dao package depends on util tells you that any modifications to support the new requirement in util could adversely affect the troublesome dao package!
Seeing this link assists you in risk assessment and even in a level of effort analysis. If you hadn't noticed the link, you might have guessed that a quick coding effort would be required to support the new requirement. Having seen the link, you can allocate the appropriate time or resources to mitigate any collateral damage that occurs in the dao package.
Monitor for dependency
Just as continuously monitoring afferent coupling can uncover entropy in an architectural design, so monitoring efferent coupling can assist you in spotting unwanted dependencies. For example, in Figure 5, it appears that at some point someone decided that the com.acme.ascp.web package had something to offer to com.acme.ascp.user. Somewhere in the user package, one or more objects are actually importing an object from the web package.
Figure 5. Efferent coupling in the user package
About the author
Andrew Glover is president of Stelligent Incorporated, which helps companies address software quality with effective developer testing strategies and continuous integration techniques that enable teams to monitor code quality early and often. He is the co-author of Java Testing Patterns (Wiley, September 2004).
- "When you have learned to snatch the error code from the trap frame, it will be time for you to leave.", thus spake the master programmer.