RigiditySingle change causes a cascade of subsequent changes in dependent modules.FragilitySingle changes causes breaks in many other, often unrelated areas. Modules constantly in need of repair, always on the bug list.ImmobilityContains parts that could be useful elsewhere, but too much work to separate those parts. Very common!Needless ComplexityContains elements that aren’t currently useful.Preparing for too many contingencies litters code with unused constructs!Needless RepetitionCut-and-paste can be disastrous code-editing operations! Miss an abstraction -> miss an opportunity to make system easier to understand and maintain.OpacityOpacity is the tendency of a module to be difficult to understand. Code seems clear when first written. Later even same developer may not understand it.
Fred Brooks in his book “The Mythical Man Month” describeshow the inherent properties of software (i.e. complexity, conformity, change-ability, and invisibility) make its design an “essential” diﬃculty.
An interesting instance of “incomplete abstraction” is observed in JDK’s javax.swing.tree package [Wak03]. The interface TreeNode defines basic methods by which an object can be used as a tree node in JTree. The interface MutableTreeNode is derived from TreeNode and satisfies the functionality required for a tree node object that can change. Further, DefaultMutableTree class realizes the MutableTreeNode interface and provides functionality of a general-purpose node in a tree (see Figure). While the interface MutableTreeNode defines methods for a changeable tree-node including setUserObject(), it does not provide a corresponding getter method i.e., getUserObject()! Instead, this is provided by the DefaultMutableTreeNode class which realizes that interface. Here, the interface MutableTreeNode is a good example of an “incomplete abstraction” whose incomplete functionality has to be “completed” by the derived class.The refactoring for the JTree example from OpenJDK will involve defining the getUserObject() method in the MutableTreeNode interface itself. However, since JDK is a public API, adding a method to an interface would break the existing classes which implement that interface (remember that all methods declared in an interface must be defined in a class that implements the interface). Hence, a workaround would be to define another interface with getUserObject() in addition to methods declared in MutableTreeNode. However, it would bloat the library, and users may get confused when they see two similar interfaces. Hence, it is easier and more practical (though inconvenient to users) to add this method in its derived class DefaultMutableTreeNode which is the default implementation of the interface. So, the lesson that we learn: It is difficult to evolve interfaces, and hence it is important to be aware of and avoid smells such as incomplete abstraction when designing APIs.
Do you remember the Abstract Factory pattern described in the GoF book [EGV94]? It is a creational pattern that allows families of objects to be created. Figure @@ shows the solution structure of the pattern. Interestingly, the abstract factory as defined by the pattern does not specifically include the responsibility to delete the objects they have created. Due to this, we have encountered several cases where an Abstract Factory pattern has been used correctly but object deletion has been overlooked by designers since it is not explicitly addressed in the pattern. Ideally, for effective object management, a single entity should be responsible for the creation and deletion of objects. It would thus be preferable to house both these responsibilities in a single type i.e., the Abstract Factory itself in this case. Otherwise, the Abstract Factory would exhibit the Incomplete Abstraction smell.The suggested refactoring for the Abstract Factory example would be to ensure that the responsibility of object deletion is also encompassed within the abstract factory class. This will allow creation and deletion of objects to be handled within a single class. This decreases the complexity of the object creation and deletion mechanism and improves the maintainability of the system. In the same way, Factory Method pattern [EGV94] could be enhanced as Factory Disposal Method pattern [POSA].