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
DotNetOpenAuth.OpenId.Extensions.SimpleRegistrationnamespaces. 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
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
which depends on two less stable namespaces
DotNetOpenAuth.OAuth2, DotNetOpenAuth.OAuth. This
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
ProviderEndpoint class in
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
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
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.Core project accesses
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.