Disclaimer: Here is the original source of the article. Reproduced here with permission.
Kent Beck coined the term “code smell” in the popular Refactoring book by Martin Fowler and defined it informally as “certain structures in the code that suggest (sometimes they scream for) the possibility of refactoring”. An excessive number of smells in a software system impair the quality of the software and makes the software hard to maintain and evolve.
Based on the scope of the impact made by a smell, we may perceive smells in three categories – implementation smells, design smells, and architecture smells. Implementation smells have limited scope, typically confined to a class or file, have a limited local impact, and require relatively the least effort to refactor. Long method, magic number, and empty catch block are the examples of implementation smells. Design smells impact a set of classes and thus refactoring a design smell may introduce a change in a few classes. Examples of design smells are insufficient modularization (god class), multifaceted abstraction (divergent change), and broken hierarchy (refused bequest). Further, architecture smells span multiple components and have a system level impact.
Let us understand various architecture smells that may arise in a software system. Here, an architecture component could be a namespace (or a package in Java world), or a project (assembly).
- Cyclic Dependency: This smell arises when two or more architecture components depend on each other directly or indirectly.
- Unstable Dependency: This smell arises when a component depends on other components that are less stable than itself.
- Ambiguous Interface: This smell arises when a component offers only a single, general entry-point into the component.
- God Component: This smell occurs when a component is excessively large either in the terms of LOC or number of classes.
- Feature Concentration: This smell occurs when a component realizes more than one architectural concern/feature.
- Scattered Functionality: This smell arises when multiple components are responsible for realizing the same high-level concern.
- Dense Structure: This smell arises when components have excessive and dense dependencies without any particular structure.
Let us analyze an open-source C# project using Designite and understand these smells in more detail. For this case study, I chose DotNetOpenAuth. Designite reports more than 84 thousand lines of code containing 117 namespaces and 684 classes in 36 assemblies (excluding test projects). Also, the tool reports 96 architecture smells as shown in the following figure.
There are 21 cyclic dependencies among namespaces in the analyzed projects. One instance of such a cycle involves
DotNetOpenAuth.OpenId.Extensions.UI, DotNetOpenAuth.OpenId.Extensions, DotNetOpenAuth.OpenId.Extensions.SimpleRegistration namespaces. It’s a cycle of length 3. Unit cycles (with length 2; A depends on B, B depends on A) are relatively easier to spot manually. However, cycles of length 3 or more are subtly hidden in your code and you need tools such as Designite to help you reveal such instances.
There are 41 instances of unstable dependencies. Stable Dependencies Principle (SDP) states that the dependencies between packages should be in the direction of the stability of the packages. A package should only depend upon packages that are more stable that it is. Unstable dependencies architecture smell occurs when the principle is not followed.
Here, stability (or rather instability) of a component is computed as follows:
I = Ce/(Ce + Ca)
I represent the degree of instability of the component.
Ca represents the afferent coupling (or incoming dependencies), and
Ce represents the efferent coupling (or outgoing dependencies)
In our analyzed example, one of the instances has been detected in
DotNetOpenAuth.ApplicationBlock namespace which depends on two less stable namespaces
DotNetOpenAuth.OAuth2, DotNetOpenAuth.OAuth. This makes
DotNetOpenAuth.ApplicationBlock namespace relatively harder to change because it depends on the relatively instable namespaces.
Ambiguous interfaces are interfaces that offer only a single, general entry-point into a component. This smell typically appears in event-based publish-subscribe systems where interactions are not explicitly modeled and multiple components exchange event messages via a shared event bus. Designite detects this smell when it finds a namespace which is not too small and contains only one public or internal method. In our running example,
ProviderEndpoint class in
DotNetOpenAuth.OpenId.Provider namespace provides one public method with the following signature.
public Task PrepareResponseAsync(CancellationToken cancellationToken = default(CancellationToken))
Having only one non-static public/internal method in the entire namespace (which is not a small one; 5 classes) and the presence of public events handlers make this namespace suffer from ambiguous interface architecture smell.
DotNetOpenAuth.Messaging contains 55 classes! Each component must contain a manageable number of classes and should not be too large in terms of lines of code. A large component is difficult to understand and thus harder to maintain. Designite detects god component architecture smell when a component has more than 30 classes or 27000 lines of code (following the recommendations by Martin Lippert et al. in Refactoring in Large Software Projects).
A feature concentration architecture smell occurs when a component is realizing more than one architectural concern/feature. In other words, the component is not cohesive. Akin to LCOM (Lack of Cohesion of Methods) that is applicable to classes, Designite computes LCC (Lack of Component Cohesion) to measure the component cohesion. To compute LCC, Designite identifies relationships among classes (association and inheritance) and club the related classes in groups. LCC is then computed by dividing the number of groups by total classes in the namespace.
DotNetOpenAuth in project
DotNetOpenAuth.Core has 11 classes. Designite identifies the following related groups in the namespace:
[Assumes, UriUtil], [IHostFactories, IRequireHostFactories], [MachineKeyUtil], [IEmbeddedResourceRetrieval, Util, Logger, Reporting, RequiresEx, Strings]. Each group of classes is not interacting with other groups in the same namespace. Although, you might decide to still keep them in the same namespace because they are semantically same but the smell makes you think and reconsider whether these groups should be kept in the same namespace. Based on the information, LCC of the component is 0.36 which is quite high and thus the tool identifies feature concentration smell in the component.
Scattered functionality indicates that two or more namespaces are realizing a same architectural concern (kind of opposite to the feature concentration smell). Designite checks access to external namespaces that occur together from a method. If such accesses happen many times in a component, it leads to scattered functionality architecture smell in the accessed components. It is an indication that possibly classes or methods must be moved from one component to another to reduce the coupling and enhance the cohesion of the components.
In our running example,
DotNetOpenAuth.Messaging namespace in
DotNetOpenAuth.Core project accesses
DotNetOpenAuth.Messaging.Bindings together many times. It indicates that both of these components probably share an architecture responsibility.
The last smell in our consideration is dense structure. This smell occurs when the components form a very dense dependency graph. Thus, only one instance at maximum can occur per analysis session. Designite forms a dependency graph among all the namespaces and computes the average degree of the graph. The average degree of a graph can be computed as follows:
Average degree = 2 * |E| / |V|
Where E is the set of all the edges among the vertexes and V is the set of all vertexes belonging to the graph.
The default threshold used by Designite to identify dense structure is average degree >= 5 (it is customizable). In our running example, the average degree is 5.44 that implies that each component is associated with more than 5 other components on an average which is large. It indicates that coupling among components is high and efforts must be dedicated to reducing it.
To analyze further, I took the dependencies revealed by Designite and fed into an R program to visualize the dependencies among components. The dependency graph looks quite complex that validates the presence of dense structure architecture smell.
Many of the above-mentioned smells use certain thresholds. There is no one single standard for choosing appropriate thresholds and hence the set of thresholds that seems good for one developer might not hold good with another developer or team. Fortunately, we can change these thresholds based on the needs in Designite. One can go to Analysis menu, choose Preferences, and change the thresholds. Even further, if you don’t want Designite to analyze and report a specific smell, you can disable the detection using Preferences dialog.
An important note about smells. Smells are indicators of potential quality issues. Therefore, a tool detecting a smell in your code doesn’t mean that you have to refactor the smell. The tool doesn’t understand the context of the code and therefore it is up to you as a developer or an architect to take a cue and analyze further whether the smell is a real quality issue or the architecture is reflecting your intentional decisions.