Girish Suryanarayana, Tushar Sharma, Ganesh Samarthyam
Aug 04, 2015
Girish Suryanarayana, Tushar Sharma, Ganesh Samarthyam
2
Outline
Introduction
Design smells
Abstraction smells
Encapsulation smells
Modularization smells
Hierarchy smells
Refactoring Process and
Tools
3
Why care about software quality?
Poor software quality
costs more than $150 billion
per annum in U.S.
and greater than $500 billion
per annum worldwide
- Capers Jones
Capers Jones on design errors in industrial software
* http://sqgne.org/presentations/2012-13/Jones-Sep-2012.pdf
0
20
40
60
80
100
120
IBM Corportation
(MVS)
SPR Corporation (Client Studies)
TRW Corporation
MITRE Corporation
Nippon Electric Corp
Pe
rce
nta
ge C
on
trib
uti
on
Industry Data on Defect Origins
Adminstrative Errors
Documentation Errors
Bad Fixes
Coding Errors
Design Errors
Requirements Errors
Up to 64% of software defects can be traced back to errors in software design in enterprise software!
5
Why care about design quality?
Poor software quality costs more than $150
billion per year in U.S. and greater than $500 billion
per year worldwide
The debt that accrues when you knowingly or unknowingly
make wrong or non-optimal design decisions
Software Quality
Technical Debt
Design Quality
Design Quality means changeability, extensibility, understandability, reusability, …
6
Technical debt
“Technical debt is the debt that accrues
when you knowingly or unknowingly make
wrong or non-optimal design decisions”
Reference: Refatoring for Software Design Smells– Girish et al Morgan Kaufman 2014
7
Why care about technical debt?
Global 'IT Debt' as $500 billion
for the year 2010, with
potential to grow to $1 trillion
by 2015
- Gartner
8
Why care about technical debt?
Reference: Zen and the art of software quality – Jim Highsmith Agile 2009 conference
9
What constitutes technical debt?
…
Code debt
Static analysis tool
violations
Inconsistent coding style
Design debt
Design smells
Violations of design rules
Test debt
Lack of tests
Inadequate test coverage
Documentation debt
No documentation for important
concerns
Outdated documentation
10
Outline
Introduction
Design smells
Abstraction smells
Encapsulation smells
Modularization smells
Hierarchy smells
Refactoring Process and
Tools
11
Why care about smells?
Impacted Quality
Reusability
Changeability
Understandability
Extensibility
Testability
Reliability
Product Quality
Design Quality
Design Smells
Impacted Quality
Maintainability:
Affected by
changeability &
extensibility
Reliability: Impacted
by poor
understandability
…
Indicators
Rigidity & Fragility
Immobility & Opacity
Needless complexity
Needless repetition
…
12
Why care about refactoring?
As an evolving program is
continually changed, its
complexity, reflecting
deteriorating structure,
increases unless work is done
to maintain or reduce it
- Lehman's law of Increasing
Complexity
13
What are design smells?
“Design smells are certain structures in the
design that indicate
violation of fundamental design principles
and
negatively impact design quality”
Reference: Refatoring for Software Design Smells– Girish et al Morgan Kaufman 2014
14
What is design quality?
Quality
Attribute Definition
Understandability The ease with which the design fragment can be comprehended.
Changeability The ease with which a design fragment can be modified (without causing
ripple effects) when an existing functionality is changed.
Extensibility The ease with which a design fragment can be enhanced or extended (without
ripple effects) for supporting new functionality.
Reusability The ease with which a design fragment can be used in a problem context other
than the one for which the design fragment was originally developed.
Testability The ease with which a design fragment supports the detection of defects
within it via testing.
Reliability
The extent to which the design fragment supports the correct realization of
the functionality and helps guard against the introduction of runtime
problems.
“Design smells” aka…
16
So, what causes design smells?
17
Most common cause of design smells
18
Another cause: not adhering to processes
19
Why should we focus on smells?
A good designer is one who knows the design solutions
A GREAT designer is one who understands the impact of
design smells and knows how to address them
20
Design smells as violations of fundamental principles
What do smells indicate?
Violations of
fundamental design
principles
We use Booch’s fundamental
principles for classification
and naming of smells
This helps identify cause of
the smell and potential
refactoring as well
21
Design principles used to classify design smells
Des
ign
Pri
nci
ple
s
Abstraction
Encapsulation
Modularization
Hierarchy
22
Design principles and enabling techniques
PHAME: Principles of Hierarchy, Abstraction, Modularization and Encapsulation
23
What is refactoring?
Refactoring (noun): a change
made to the internal structure of
software to make it easier to
understand and cheaper to
modify without changing its
observable behavior
Refactor (verb): to restructure
software by applying a series
of refactorings without
changing its observable
behavior
24
A note on examples in this presentation
Almost all examples are from real-world software
Most examples are from OpenJDK 7.0 (open source)
Examples from industrial software are “sanitized” – they do not reveal
anything about where they came from…
All illustrations are mostly as UML diagrams so no need to know Java
Though you’ll appreciate them more if you are from a Java background
A few code examples are in Java, because JAVA is the most widely
used language They are simple enough for people of any programming background to
understand
25
Scope/Focus: Only structural design smells
26
27
Outline
Introduction
Design smells
Abstraction smells
Encapsulation smells
Modularization smells
Hierarchy smells
Refactoring Process and
Tools
28
The principle of 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”
29
Enabling techniques for abstraction
30
public class Throwable {
// following method is available from Java 1.0 version.
// Prints the stack trace as a string to standard output
// for processing a stack trace,
// we need to write regular expressions
public void printStackTrace();
// other methods omitted
}
31
Missing abstraction
This smell arises when clumps of data or encoded strings are used
instead of creating a class or an interface.
32
Refactoring for missing abstraction smell public class Throwable {
// following method is available from Java 1.0 version.
// Prints the stack trace as a string to standard output
// for processing a stack trace,
// we need to write regular expressions
public void printStackTrace();
// other methods omitted
}
public class Throwable {
public void printStackTrace();
public StackTraceElement[] getStackTrace(); // Since 1.4
// other methods omitted
}
public final class StackTraceElement {
public String getFileName();
public int getLineNumber();
public String getClassName();
public String getMethodName();
public boolean isNativeMethod();
}
33
34
Imperative abstraction
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. At times, the class name itself may be identical to
the one method defined within it.
35
Refactoring imperative abstraction smell
36
37
Incomplete abstraction
• This smell arises when a type does not support a responsibility completely
• Specifically, the public interface of the type is incomplete in that it does not
support all behavior needed by objects of its type
38
Incomplete abstraction – Example
In this case, the MutableTreeNode
supports only setUserObject but no
corresponding getUserObject (which is
provided in its derived class!)
Hence, MutableTreeNode has
Incomplete Abstraction smell
How to fix it? Provide all the necessary
and relevant methods required for
satisfying a responsibility completely in
the class itself
In case of public APIs (as in this case), it
is often “too late” to fix it!
39
Another example
40
How to refactor & in future avoid this smell?
For each abstraction (especially in public interface) look out for symmetrical
methods or methods that go together
For example, methods for comparing equality of objects and getting
hash code (in Java/C#)
Look out for missing matching methods in symmetrical methods (see table)
min/max open/close create/destroy get/set
read/write print/scan first/last begin/end
start/stop lock/unlock show/hide up/down
source/target insert/delete first/last push/pull
enable/disable acquire/release left/right on/off
41
The Abstract Factory pattern
42
java.util.Calendar
In addition to methods supporting dates, the class has methods for supporting
time as well!
43
Multi-faceted abstraction
This smell arises when an abstraction has more than one
responsibility assigned to it.
44
Refactoring for multifaceted abstraction smell
45
public class FormattableFlags {
// Explicit instantiation of this class is prohibited.
private FormattableFlags() {}
/** Left-justifies the output. */
public static final int LEFT_JUSTIFY = 1<<0; // '-'
/** Converts the output to upper case */
public static final int UPPERCASE = 1<<1; // 'S'
/**Requires the output to use an alternate form. */
public static final int ALTERNATE = 1<<2; // '#'
}
46
class Currency { public static String DOLLAR = "\u0024"; public static String EURO = "\u20AC"; public static String RUPEE = "\u20B9"; public static String YEN = "\u00A5"; // no other members in this class }
47
Unnecessary abstraction
The smell occurs when an abstraction gets introduced in a software
design which is actually not needed and thus could have been avoided.
48
Unused abstract classes in an application
49
Unutilized abstraction
This smell arises when an abstraction is left unused (either not
directly used or not reachable).
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
50
Refactoring for unutilized abstraction
51
52
• executing objects of type ActionEvent at specified intervals
javax.swing.Timer
• scheduling a thread to execute in the future as a background thread
java.util.Timer
• sending out an alarm to wake-up the listeners that have registered to get timer notifications
javax.management.timer.Timer
53
/*
* Note on casts to double below. If the arithmetic of
* x+w or y+h is done in float, then some bits may be
* lost if the binary exponents of x/y and w/h are not
* similar. By converting to double before the addition
* we force the addition to be carried out in double to
* avoid rounding error in the comparison.
*
* See bug 4320890 for problems that this inaccuracy causes.
*/
/*
* Note on casts to double below. If the arithmetic of
* x+w or y+h is done in int, then we may get integer
* overflow. By converting to double before the addition
* we force the addition to be carried out in double to
* avoid overflow in the comparison.
*
* See bug 4320890 for problems that this can cause.
*/
Rectangle2D class Rectangle class
54
Duplicate abstraction
This smell arises when two or more abstractions have identical
names or identical implementation or both.
55
56
Types of clones
• exactly identical except for variations in whitespace, layout, and comments
Type 1
• syntactically identical except for variation in symbol names, whitespace, layout, and comments
Type 2
• identical except some statements changed, added, or removed
Type 3
• when the fragments are semantically identical but implemented by syntactic variants
Type 4
57
Refactoring for Date example
58
Sum
mar
y o
f Sm
ells
Vio
lati
ng
Ab
stra
ctio
n
59
Outline
Introduction
Design smells
Abstraction smells
Encapsulation smells
Modularization smells
Hierarchy smells
Refactoring Process and
Tools
60
The principle of encapsulation
“The principle of encapsulation advocates separation of concerns and
information hiding through techniques such as hiding implementation
details of abstractions and hiding variations”
61
Enabling techniques for encapsulation
62
63
Lenient encapsulation
This design smell arises when the hiding is deficient so that clients
can access the implementation aspects of the abstraction directly
(An abstraction should expose only the interface to the clients
and hide the implementation details.)
64
Lenient encapsulation – Example
The java.awt.Rectangle class has 4
pubic fields: x, y, width, and height
in addition to public getter and
setter methods for these fields!
Smell since the accessibility of
its members is more permissive
(i.e., are declared public) than
actually required (i.e., they are
required to be declared private)
Refactoring suggestion: Make all
data members private
Lenient encapsulation
Insufficient modularization
65
66
Case study: Use of global variable “current” in Linux
67
68
69
Leaky encapsulation
This smell arises when an abstraction “exposes” or “leaks”
implementation details through its public interface.
70
Refactoring leaky encapsulation smell
71
72
73
Missing encapsulation
This smell occurs when the encapsulation of implementation
variations in a type or hierarchy is missing.
74
How about this refactoring?
75
Refactoring missing encapsulation smell
76
Refactoring missing encapsulation smell
77
switch (transferType) {
case DataBuffer.TYPE_BYTE:
byte bdata[] = (byte[])inData;
pixel = bdata[0] & 0xff;
length = bdata.length;
break;
case DataBuffer.TYPE_USHORT:
short sdata[] = (short[])inData;
pixel = sdata[0] & 0xffff;
length = sdata.length;
break;
case DataBuffer.TYPE_INT:
int idata[] = (int[])inData;
pixel = idata[0];
length = idata.length;
break;
default:
throw new UnsupportedOperationException("This method has not been "+ "implemented
for transferType " + transferType);
}
78
Unexploited encapsulation
This smell arises when client code uses explicit type checks (using
chained if-else or switch statements that check for the type of the
object) instead of exploiting the variation in types already
encapsulated within a hierarchy.
79
Refactoring unexploited encapsulation smell
protected int transferType; protected DataBuffer dataBuffer;
pixel = dataBuffer.getPixel();
length = dataBuffer.getSize();
switch (transferType) {
case DataBuffer.TYPE_BYTE:
byte bdata[] = (byte[])inData;
pixel = bdata[0] & 0xff;
length = bdata.length;
break;
case DataBuffer.TYPE_USHORT:
short sdata[] = (short[])inData;
pixel = sdata[0] & 0xffff;
length = sdata.length;
break;
case DataBuffer.TYPE_INT:
int idata[] = (int[])inData;
pixel = idata[0];
length = idata.length;
break;
default:
throw new UnsupportedOperationException("This method
has not been "+ "implemented for transferType " +
transferType);
}
80
Summary of Smells Violating Encapsulation
81
Outline
Introduction
Design smells
Abstraction smells
Encapsulation smells
Modularization smells
Hierarchy smells
Refactoring Process and
Tools
82
The principle of modularization
“The principle of modularization advocates the creation of
cohesive and loosely coupled abstractions through
techniques such as localization and decomposition”
83
Enabling techniques for modularization
84
85
Broken modularization
This smell arises when members of an abstraction are broken and
spread across multiple abstractions (when ideally they should
have been localized into a single abstraction).
Two forms of this smell:
• Data and methods that ideally belong to an abstraction are
split among two or more abstractions.
• Methods that ideally belong to abstraction are split among
two or more abstractions.
86
Suggested refactoring for broken modularization
87
Refactoring for broken modularization smell
88
89
90
Insufficient modularization
This smell arises when an existing abstraction could be further
decomposed thereby reducing its interface size, implementation
complexity or both.
Two variants: a) When an abstraction has a large number of members in its interface, its
implementation, or both
b) When an abstraction has one or more methods with excessive complexity
91
Insufficient modularization – Example
The abstract class java.awt.Component is an
example of insufficient modularization
It is a massive class with 332 methods
(of which 259 are public!)
11 nested/inner classes
107 fields (including constants)
source file spans 10,102 lines of code!
The Component serves as a base class and
the hierarchy is deep
Derived classes inherit the members =>
life is quite difficult!
92
93
94
95
96
Cyclically-dependent modularization
This smell arises when two or more class-level abstractions depend
on each other directly or indirectly (creating a tight coupling among
the abstractions).
(This smell is commonly known as “cyclic dependencies”)
97
Refactoring cyclically-dependent modularization
98
Refactoring cyclically-dependent modularization
99
Refactoring cyclically-dependent modularization
100
Refactoring cyclically-dependent modularization
101
Refactoring cyclically-dependent modularization
102
Refactoring cyclically-dependent modularization
103
Refactoring cyclically-dependent modularization
104
Practical consideration: unit cycles
105
106
107
Hub-like modularization
This smell arises when a class-level abstraction has dependencies
with large number of other class-level abstractions (high incoming
as well as outgoing dependencies).
108
Summary of Smells Violating Modularization
109
Outline
Introduction
Design smells
Abstraction smells
Encapsulation smells
Modularization smells
Hierarchy smells
Refactoring Process and
Tools
110
The principle of hierarchy
“The principle of hierarchy advocates the creation of a
hierarchical organization of abstractions using techniques such
as classification, generalization, substitutability, and ordering”
111
Enabling techniques for hierarchy
112
113
114
Unfactored hierarchy
This smell arises when the types in a hierarchy share unnecessary
duplication in the hierarchy.
Two forms of this smell:
• Duplication in sibling types
• Duplication in super and subtypes
115
A refactoring for missing intermediate types
116
Refactoring for unfactored hierarchy
117
Refactoring for unfactored hierarchy
118
public Insets getBorderInsets(Component c, Insets insets){
Insets margin = null;
// Ideally we'd have an interface defined for classes which
// support margins (to avoid this hackery), but we've
// decided against it for simplicity
//
if (c instanceof AbstractButton) {
margin = ((AbstractButton)c).getMargin();
} else if (c instanceof JToolBar) {
margin = ((JToolBar)c).getMargin();
} else if (c instanceof JTextComponent) {
margin = ((JTextComponent)c).getMargin();
}
// rest of the code omitted …
119
Missing hierarchy
This smell arises when an abstraction uses conditional logic to
determine behavior where a hierarchy could have been formed to
exploit variation in behavior.
120
Refactoring for missing hierarchy
121
Refactoring for missing hierarchy
margin = c.getMargin();
if (c instanceof AbstractButton) {
margin = ((AbstractButton)c).getMargin();
} else if (c instanceof JToolBar) {
margin = ((JToolBar)c).getMargin();
} else if (c instanceof JTextComponent) {
margin = ((JTextComponent)c).getMargin();
}
122
123
124
Deep hierarchy
This smell arises when an inheritance hierarchy is excessively deep.
125
Refactoring for deep hierarchy
126
Refactoring for deep hierarchy
/**
* Pluggable look and feel interface for JButton.
*/
public abstract class ButtonUI extends ComponentUI {
}
127
128
Wide hierarchy
This smell arises when an inheritance hierarchy is “too” wide and
shallow indicating missing intermediate abstractions.
129
Suggested refactoring for wide hierarchy
130
131
Rebellious hierarchy
This smell arises when a subtype rejects the methods
provided by its supertype(s).
132
How about this refactoring for rebellious hierarchy?
133
How about this refactoring for rebellious hierarchy?
134
How about this refactoring for rebellious hierarchy?
135
Dealing with addComponent() in Composite pattern
136
137
138
139
Broken hierarchy
This smell arises when the base abstraction and its derived
abstraction(s) conceptually do not share “IS-A” relationship
(resulting in broken substitutability).
This design smell arises when inheritance is used wrongly instead of
using composition.
140
Liskov’s Substitution Principle (LSP)
It should be possible to replace objects of supertype with
objects of subtypes without altering the desired behavior of
the program
Barbara Liskov
141
Refactoring broken hierarchy
142
Refactoring broken hierarchy
143
144
Multipath hierarchy
This smell arises when a subtype inherits both directly
as well as indirectly from a supertype leading to
unnecessary inheritance paths in the hierarchy.
145
Suggested refactoring for multipath hierarchy
146
How about multiple interface inheritance?
147
Real-world example
148
149
150
Unnecessary hierarchy
This smell arises when an inheritance hierarchy has one or more unnecessary
abstractions. Includes the following cases:
• all the subtypes are unnecessary (i.e., inappropriate use of inheritance)
• supertype has only one subtype (i.e., speculative generalization)
• intermediate types are unnecessary
151
Refactoring unnecessary hierarchy
152
Refactoring unnecessary hierarchy
153
Refactoring unnecessary hierarchy
154
Refactoring unnecessary hierarchy
155
Practical considerations: language limitations
156
157
Speculative hierarchy
This smell arises when one or more types in a hierarchy are provided speculatively
(i.e., based on imagined needs rather than real requirements)
158
Refactoring for speculative hierarchy
159
160
Cyclic hierarchy
This smell arises when a supertype in a hierarchy
depends on any of its subtypes.
161
Suggested refactoring for cyclic hierarchy
162
Sum
mar
y o
f Sm
ells
Vio
lati
ng
Hie
rarc
hy
163
How to improve design quality in practice?
164
Refactoring process model
165
Suggested Reading
166
More on design smells?
167
Key take-aways
Technical debt accrues when you knowingly or unknowingly make wrong or non-optimal design decisions
Design smells indicate violation of fundamental design principles
Also, design smells negatively impact design quality
We need to care about technical debt
Accumulating debt will lead to “technical bankruptcy”
Refactoring is the primary means to repay technical debt
168
Key take-aways
Apply fundamental design principles (such as abstraction, encapsulation, modularization, and hierarchy) to create high-quality software
Smells are related: they tend to co-occur, amplify the effect of other smells, or can indicate deeper design problems
Use the IMPaCT process model to identify/mark, prioritize, and refactor design smells in practice
169
Design principles and enabling techniques
170
171
Thank you!
Girish Suryanarayana, Tushar Sharma, Ganesh Samarthyam