Subject : CODE REENGINEERING
Year : 2017
Abstraction Smell
Session 15 & 16
Learning Outcomes
LO 2: Apply Advanced refactoring and its application
COMP6047 - Algorithm and Programming
Sub Topics
- Missing Abstraction
- Imperative Abstraction
- Incomplete Abstraction
- Multifaceted Abstraction
- Unnecessary Abstraction
- Unutillized Abstraction
- Duplicate Abstraction
COMP6047 - Algorithm and Programming
Abstraction
The principle of abstraction advocates the simplification of entities through
reduction and generalization: reduction is by elimination of unnecessary details
and generalization is by identification and specification of common and
important characteristics.
Abstraction is a powerful principle that provides a means for effective yet simple
communication and problem solving. Company logos and traffic signs are
examples of abstractions for communication. Mathematical symbols and
programming languages are examples of abstraction as a tool for problem
solving.
Abstraction Key Enabling
Technique
Abstraction Key Enabling
Technique
Provide a crisp conceptual boundary and an identity. Each abstraction
should have a crisp and clear conceptual boundary and an identity.
Map domain entities. There should be a mapping of vocabulary from the
problem domain to the solution domain, i.e., objects recognized by the
problem domain should be also represented in the solution domain.
Ensure coherence and completeness. An abstraction should completely
support a responsibility.
Assign single and meaningful responsibility. Ensure that each abstraction
has a unique non-trivial responsibility assigned to it.
Avoid duplication. Ensure that each abstraction—the name as well as its
implementation—appears only once in design.
Design Smell and Key Enabling
Violated Enabling Technique Design Smell
Provide a crisp conceptual boundary Missing Abstraction
and an identity
Map Domain Entity Imperative Abstraction
Ensure coherence and correctness Incomplete Abstraction
Assign single and meaningful Multifaceted Abstraction, Unnecessary
responsibilities Abstraction, Unutilized Abstraction
Avoid Duplication Duplicate Abstraction
Missing Abstraction
Component Description
Definition This smell arises when clumps of data or encoded
strings are used instead of creating a class or an
interface.
Rationale • It can expose implementation details to
different abstractions, violating the principle of
encapsulation.
• When data and associated behavior are spread
across abstractions, it can
lead to tight coupling between entities,
resulting in brittle and non-reusable code.
Potential Causes • Inadequate design analysis
• Lack of refactoring
• Misguided focus on minor performance gain
Missing Abstraction Example I
Consider a library information management application. Storing and
processing ISBNs (International Standard Book Numbers) is very important in
such an application. It is possible to encode/store an ISBN as a primitive type
value (long integer/ decimal type) or as a string. However, it is a poor choice
in this application. Why? To understand that, let’s take a quick look at ISBNs.
The digits in an ISBN have meaning; for example, an ISBN-13 number
consists of these elements: Prefix Element, Registration Group Element,
Registrant Element, Publication Element, and Checksum. The last digit of an
ISBN number is a checksum digit, which is calculated as follows: starting
from the first digit, the values of the odd digits are kept the same, and the
values of even numbered digits are multiplied by three; the sum of all the
values modulo 10 is the value of the last digit. So, given a 10 or 13 digit
number, you can validate whether the given number is a valid ISBN number
Missing Abstraction Example I
Implementation of a library information management application involves
logic that accepts, validates, processes, or converts between ISBN numbers. It
is possible to encode ISBN numbers as strings or as a primitive type value in
such an application. However, in such a case, the logic that processes the
numbers will be spread as well as duplicated in many places. In the context of
a library information system that has considerable logic involving ISBN
numbers, not encapsulating ISBN numbers as class(es) indicates a Missing
Abstraction smell.
Missing Abstraction Example I Suggested
Refactoring
The example discussed the need to handle different kinds of ISBN numbers
such as ISBN-10 and ISBN-13. A potential refactoring would be to abstract
ISBN as an abstract class or interface with common operations in it. ISBN-10
and ISBN-13 can be subclasses that extend the ISBN supertype
Missing Abstraction Example II Suggested
Refactoring
Revisiting the example of the drawing application, a refactoring suggestion would
be to abstract the required fields into a new class—say Rectangle class or
SelectedRegion class—and move methods operating on these fields to the new
class.
Missing Abstraction Example III
Strings are often used to encode information. In the case of APIs (Application
Programming Interface), the problem with encoding data in strings is that
once the API is released, it is very difficult to change the encoding format
since clients that depend on the API will be affected. Let us take a look at a
detailed example from Java Development Kit (JDK).
From 1.0 version of Java, the stack trace was printed to the standard error
stream as a string with printStackTrace() method:
Client programs that needed programmatic access to the stack trace elements
had to write code to process the stack trace to get, for example, the line
numbers or find if the bug is a duplicate of another already-filed bug. Due to
this dependence of client programs on the format of the string, the designers of
JDK were forced to retain the string encoding format in future versions of
JDK.
Missing Abstraction Example III Suggested
Refactoring
The Java API was improved in version 1.4 to have programmatic access to the
stack trace through the introduction of StackTraceElement class. Note that
even though a new method is added, the method printStackTrace() and the
format of the stack trace has been retained to support the existing clients.
The StackTraceElement is the “Missing Abstraction” in the original design. It was
introduced in Java 1.4 as follows:
Impacted Quality
Understandability—When a key entity is not represented as an abstraction and
the logic that processes the entity is spread across the code base, it becomes
difficult to understand the design.
Changeability and Extensibility—It is difficult to make enhancements or changes
to the code when relevant abstractions are missing in design. First, it is difficult
even to figure out the parts of the code that need to be modified to implement a
change or enhancement.
Reusability and Testability—Since some abstractions that correspond to domain
or conceptual entities are missing, and the logic corresponding to the entities is
spread in the code base, both reusability and testability of the design are
impacted.
Reliability—An abstraction helps provide the infrastructure to ensure the
correctness and integrity of its data and behavior. In the absence of an
abstraction, the data and behavior is spread across the code base; hence,
integrity of the data can be easily compromised. This impacts reliability.
Practical Consideration (Avoid Over-
engineering)
default initialization of data values using constructors
validation of the data values
support for pretty printing the data values
acquired resources (if any) are to be released
Imperative Abstraction
Component Description
Definition This smell arises when an operation is turned into
a class. This smell manifests as a class that has only
one method defined within the class.
Rationale The founding principle of object-orientation is to
capture real world objects and rep- resent them as
abstractions. By following the enabling technique
map domain entities, objects recognized in the
problem domain need to be represented in the
solution domain, too.
Potential Causes • Procedural Thinking
Imperative Abstraction Example
Consider the case of a large-sized financial application. This application
employs classes named CreateReport, CopyReport, DisplayReport, etc. to
deal with its report generation functionality. Each class has exactly one
method definition named create, copy, display, etc., respectively, and suffers
from Imperative Abstraction smell. The data items relating to a report such as
name of the report, data elements that need to be displayed in the report,
kind of report, etc. are housed in a “data class” named Report.
Imperative Abstraction Example Suggested
Refactoring
For the report generation part of the financial application, a suggested
refactoring is to move the methods in each of the classes suffering from
Imperative Abstraction to the Report class itself. Moving all the report-related
operations to the Report class makes the Report class a proper “abstraction”
and also removes the Imperative Abstraction smell. The design becomes
cleaner and less complex.
Impacted Quality
Understandability—An abstraction with this smell does not have a direct
mapping to the problem domain;
Changeability and Extensibility—The presence of an Imperative Abstraction
smell does not impact the changeability and extensibility of the abstraction itself.
Reusability—Consider the report generation functionality that was discussed in
Example .
Testability—If an abstraction with Imperative Abstraction smell is self-suf- ficient,
it is easy to test it
Practical Consideration (Reification)
State pattern: Encoding a state-machine.
Command pattern: Encoding requests as command objects. A permitted
exception for this smell is when a Command pattern has been used to objectify
method requests.
Strategy pattern: Parameterizing a procedure in terms of an operation it uses.
Incomplete Abstraction
Component Description
Definition This smell arises when an abstraction does not
support complementary or interrelated methods
completely. For instance, the public interface of an
abstraction may provide an initialize() method to
allocate resources.
Rationale One of the key enabling techniques for abstraction
is to “create coherent and complete abstractions.”
One of the ways in which coherence and
completeness of an abstraction may be affected is
when interrelated methods are not supported by
the abstraction.
Potential Causes • Missing Overall Perspective
• Not Adhering to language or library convention
Incomplete Abstraction Example
An interesting instance of “Incomplete Abstraction” is observed in JDK’s
javax. swing.ButtonModel interface. It provides setGroup() method,
which according to its documentation, “identifies the group the button
belongs to—needed for radio but- tons, which are mutually exclusive
within their group.” The ButtonModel interface does not provide the
symmetric getGroup() method and hence suffers from Incomplete
Abstraction smell.
Incomplete Abstraction Example Suggested
Refactoring
The refactoring for the ButtonModel example
from JDK ideally involves defining the
getGroup() method in the ButtonModel
interface itself. However, since JDK is a public
API, adding a method to an interface would
break the existing classes that implement that
interface (remember that all methods declared
in an interface must be defined in a class that
implements the interface). Hence, to avoid
breaking existing clients, the getGroup()
method was added in its derived class
DefaultButtonModel in JDK version 1.3
Impacted Quality
Understandability—Understandability of the abstraction is adversely impacted
because it is difficult to comprehend why certain relevant method(s) are missing
in the abstraction.
Changeability and Extensibility—Changeability and extensibility of the
abstraction are not impacted.
Reusability—If some of the operations are not supported in an abstraction, it is
harder to reuse the abstraction in a new context because the unsupported
operations will need to be provided explicitly.
Reliability—An Incomplete Abstraction may not implement the required
functionality, resulting in defects.
Practical Consideration (Reification)
Disallowing certain behavior
Sometimes, a designer may make a conscious design decision to not provide
symmetric or matching methods. For example, in a read-only collection, only add()
method may be provided without the corresponding remove() method. In such a
case, the abstraction may appear incomplete, but is not a smell.
Using a single method instead of a method pair
Sometimes, APIs choose to replace symmetrical methods with a method that
takes a boolean argument (for instance, to enforce a particular naming
convention such as JavaBeans naming convention that requires accessors to have
prefixes “get,” “is,” or “set”).
Multifaceted Abstraction
Component Description
Definition This smell arises when an abstraction has more
than one responsibility assigned to it.
Rationale An important enabling technique to effectively
apply the principle of abstraction is to assign single
and meaningful responsibility for each abstraction.
In particular, the Single Responsibility Principle
says that an abstraction should have a single well-
defined responsibility and that responsibility
should be entirely encapsulated within that
abstraction.
Potential Causes • General purpose abstraction
• Evolution without periodic refactoring
• The burden of process
• Mixing up concern
Multifaceted Abstraction Example
In his book, Neal Ford mentions java.util.Calendar class as an example of a class
having multiple responsibilities [64]. A class abstracting real-world calendar
functionality is expected to support dates, but the java.util.Calendar class supports
time related functionality as well, and hence this class suffers from Multifaceted
Abstraction smell.
Multifaceted Abstraction Example Suggested
Refactoring
For the Calendar class, a possible refactoring is to extract time-related
functionality from the Calendar class into a new Time class and move the
relevant methods and fields into the newly extracted class. Java 8 has
introduced new classes supporting date and time (and other classes such as
clocks, duration, etc.) in a package named java.time so that future clients can
use this new package instead.
Impacted Quality
Understandability—A Multifaceted Abstraction increases the cognitive load due
to multiple aspects realized into the abstraction
Changeability and Extensibility—When an abstraction has multiple
responsibilities, it is often difficult to figure out what all members within the
abstraction need to be modified to address a change or an enhancement.
Reusability—Ideally, a well-formed abstraction that performs a single
responsibility has the potential to be reused as a unit. When an abstraction has
multiple responsibilities, the entire abstraction must be used even if only one of
the responsibilities needs to be reused.
Testability—When an abstraction has multiple responsibilities, these
responsibilities may be entwined with each other, making it difficult to test each
responsibility separately.
Reliability—The effects of modification to an abstraction with intertwined
responsibilities may be unpredictable and lead to runtime problems.
Practical Consideration (Reification)
None
Unnecessary Abstraction
Component Description
Definition This smell occurs when an abstraction that is actually not
needed (and thus could have been avoided) gets introduced in
a software design.
Rationale A key enabling technique to apply the principle of abstraction
is to assign single and meaningful responsibility to entities.
However, when abstractions are created unnecessarily or for
mere convenience, they have trivial or no responsibility
assigned to them, and hence violate the principle of
abstraction. Since the abstraction is needlessly introduced in
the design, this smell is named Unnecessary Abstraction.
Potential Causes • Procedural thinking in object oriented language
• Using inappropriate language features for convenient
• Over Engineering
Unnecessary Abstraction Example
Consider the case of an e-commerce application that has two classes: namely, Best-
SellerBook and Book. Whenever the client wants to create a best-seller book, it
creates an instance of a BestSellerBook. Internally, BestSellerBook delegates all
the method calls to the Book class and does nothing else. Clearly, the BestSeller-
Book abstraction is unnecessary since its behavior is exactly the same as the Book
abstraction.
Unnecessary Abstraction Example Suggested
Refactoring
For the case of the e-commerce application that has two classes; namely,
Best-SellerBook and Book, there are many possible refactoring solutions. One
solution, for instance, is to remove the BestSellerBook class and instead add
an attribute named isBestSeller (along with a getter and a setter) in the Book
class. Now, when the client code wants to indicate if a book is a bestseller,
it will set the attribute isBestSeller instead of creating an instance of the
erstwhile BestSellerBook class.
Impacted Quality
Understandability—Having needless abstractions in the design increases its
complexity unnecessarily and affects the understandability of the overall design.
Reusability—Abstractions are likely to be reusable when they have unique and
well-defined responsibilities. An abstraction with trivial or no responsibility is less
likely to be reused in a different context.
Practical Consideration (Reification)
Delegating abstractions in design patterns
Accommodating variations
Unutilized Abstraction
Component Description
Definition This smell arises when an abstraction is left unused (either not
directly used or not
reachable). This smell manifests in two forms:
Unreferenced abstractions—Concrete classes that are not
being used by anyone
Orphan abstractions—Stand-alone interfaces/abstract classes
that do not have any derived abstractions
Rationale One of the enabling techniques for applying the principle of
abstraction is to assign a single and meaningful responsibility
to an entity. When an abstraction is left unused in design, it
does not serve a meaningful purpose in design, and hence
violates the principle of abstraction.
Potential Causes • Speculative Design
• Changing Requirement
• Leftover garbage
• Fear of breaking code
Unnecessary Abstraction Example
This example is paraphrased from a bug report on unused classes in JDK.4
The pack- age sun.misc has classes that date back to early releases of JDK,
and they were used by other classes in JDK internally. Later, many of the
services provided by sun.misc package were provided as part of the public
API. Eventually, the original clients of sun.misc package started using the
services provided by the public API. Due to this, many of the original classes
in sun.misc package became unreferenced abstractions. One such example is
the internal class sun.misc.Service that was introduced in JDK 1.3, which was
made redundant by the introduction of the class java.util. ServiceLoader in
JDK version 1.6 (which is part of the public API). Hence, the original
sun.misc.Service is an Unutilized Abstraction.
Unnecessary Abstraction Example Suggested
Refactoring
All the uses of the sun.misc.Service class can be replaced by the
use of java.util. ServiceLoader class. Therefore, for all practical
purposes, there is no need for sun. misc.Service class and hence the
suggested refactoring is to remove it from the code base. In
fact, sun.misc.Service has been removed from JDK source code
and JDK 9 will not have it.5
Impacted Quality
Understandability—Presence of unused abstractions in design pollutes the
design space and increases cognitive load. This impacts understandability.
Reliability—The presence of unused abstractions can sometimes lead to run-
time problems. For instance, when code in unused abstractions gets accidentally
invoked, it can result in subtle bugs affecting the reliability of the software.
Practical Consideration (Reification)
Unutilized Abstractions in APIs
Class libraries and frameworks usually provide extension points in the form of
abstract classes or interfaces. They may appear to be unused within the library or
framework. However, since they are extension points that are intended to be
used by clients, they cannot be considered as Unutilized Abstractions.
Duplicate Abstraction
Component Description
Definition This smell arises when:
Identical name—This is when the names of two or more
abstractions are identical. While two abstractions can
accidentally have the same name, it needs to be analyzed
whether they share similar behavior.
Identical implementation—This is when two or more
abstractions have semantically identical member definitions
Rationale Avoid duplication is an important enabling technique for the
effective application of the principle of abstraction.
If two or more abstractions have an identical name, it affects
understandability of the design. Developers of client code will
be confused and unclear about the choice of the abstraction
that should be used by their code.
Potential Causes • Copy paste programming
• Ad hoc maintenance
• Lack of communication
• Classes declared non-extensible
Duplicate Abstraction Example
Classes java.util.Date and its derived class java.sql.Date share the same
name. The compiler does not complain that the base and derived classes
have the same name because these classes belong to different packages.
However, it is very confusing for the users of these classes. For example,
when both these classes are imported in a program, it will result in a
name ambiguity that must be resolved manually by explicitly qualifying
the class names.
Duplicate Abstraction Example Suggested
Refactoring
Further since java.sql.Date conforms to SQL DATE, it is preferable to rename
it as java.sql.SQLDate which will clearly differentiate it from the plain class
name java.util.Date
Impacted Quality
Understandability—Developers can become confused about which abstraction
to use when there are two or more abstractions with identical names or imple-
mentation.
Changeability and Extensibility—Change or enhancement involving one
abstraction potentially requires making the same modification in the duplicate
abstractions as well.
Reusability—Duplicate abstractions often have slightly different implementations
(especially Type 3 and Type 4 clones). The differences in implementations are
usually due to the presence of context
Reliability—When two abstractions have identical names, a confused devel- oper
may end-up using the wrong abstraction.
Practical Consideration (Reification)
Accommodating variations
Duplicate type names in different contexts
Lack of language support for avoiding duplication
References
Girish Suryanarayana, Ganesh Samarthyam and Tushar Sharma, Chapter 2 - Design Smells, In
Refactoring for Software Design Smells, edited by Girish Suryanarayana and Ganesh
SamarthyamTushar Sharma, Morgan Kaufmann, Boston, 2015, Pages 9-19, ISBN
9780128013977, http://dx.doi.org/10.1016/B978-0-12-801397-7.00002-3.
M. Fowler and K. Beck, "Bad Smells in Code," in Refactoring: Improving the Design of
Existing Code, Addison-Wesley, 2000
W. Stevens, G. Myers and L. Constantine, "Structured Design," IBM Syst J, vol. 13, no. 2, pp.
115-139, 1974.
COMP6047 - Algorithm and Programming