Design smells are certain structures in the design that indicate violation of fundamental design principles and negatively impact design quality.
Why do we have to care about design smells? Main reason is that smells negatively impact software quality, and poor software quality in turn indicates technical debt. In some cases, it can even severely impact the reputation of the organization. For instance, the presence of design smells in a framework or a library (i.e., software that exposes an Application Programming Interface(API)) that is going to be used by clients can adversely impact how the organization is perceived by the community. This is because it is hard to fix design smells in the API/framework once the clients start using the API.
In this article, let us discuss common causes of design smells (see figure).
Violation of design principles
Consider the Calendar class that is part of the java.util package. A class abstracting real-world calendar functionality is expected to support date-related functionality (which it does), but the java.util.Calendar class supports time-related functionality as well. An abstraction should be assigned with a unique responsibility. Since java.util.Calendar class is overloaded with multiple responsibilities, it indicates the violation of the principle of abstraction (in particular, violation of the Single Responsibility Principle). We name this the Multifaceted Abstraction smell because the class supports multiple responsibilities.
Inappropriate use of design patterns
Sometimes, architects and designers apply well-known solutions to a problem context without fully understanding the effects of those solutions. Often, these solutions are in the form of design patterns, and architects/designers feel pressured to apply these patterns to their problem context without fully understanding various forces that need to be balanced properly. This creates designs that suffer from symptoms such as too many classes or highly coupled classes with very few responsibilities.
Deficiencies in programming languages can lead to design smells. Consider the classes AbstractQueuedSynchronizer and AbstractQueuedLongSynchronizer from JDK. Both classes derive directly from AbstractOwnableSynchronizer and the methods differ in the primitive types they support (int and long). This resulted in an Unfactored Hierarchy smell. Since the generics feature in Java does not support primitive types, it is not possible to eliminate such code duplication when programming in Java.
Procedural thinking in OO
Often, when programmers with procedural programming background transition to object-oriented paradigm, they mistakenly think of classes as “doing” things instead of “being” things. This mindset manifests in the form of using imperative names for classes, functional decomposition, missing polymorphism with explicit type checks, etc., which result in design smells in an object-oriented context.
One of the reasons that developers may resort to hacks and thus introduce design smells instead of adopting a systematic process to achieve a particular requirement is viscosity.
Viscosity is of two types: software viscosity and environment viscosity:
- Software viscosity refers to the “resistance” (i.e., increased effort and time) that must be encountered when the correct solution is being applied to a problem. If, on the other hand, a hack requires less time and effort (i.e., it offers “low resistance”), it is likely that developers will resort to that hack, giving rise to design smells.
- Environment viscosity refers to the “resistance” offered by the software development environment that must be overcome to follow good practices. Often, if the development environment is slow and inefficient and requires more time and effort to follow good practices than bad practices, developers will resort to bad practices. Factors that contribute to the environment viscosity include the development.
Non-adherence to best practices and processes
Industrial software development is a complex affair that involves building of large-scale software by a number of people over several years. One of the ways such complexity can be better managed is through adherence to processes and best practices. Often, when a process or practice is not followed correctly or completely, it can result in design smells. For instance, a best practice for refactoring is that a “composite refactoring” (i.e., a refactoring that consists of multiple steps) should be performed atomically. In other words, either all or no steps in the refactoring should be executed. If this best practice is not adhered to and a composite refactoring is left half-way through, it can lead to a design smell.
[Source: “Refactoring for Software Design Smells: Managing Technical Debt”, Girish Suryanarayana, Ganesh Samarthyam, Tushar Sharma, ISBN – 978-0128013977, Morgan Kaufmann/Elsevier, 2014. URL: http://amzn.com/0128013974]