Refactoring for design smells

6,188 views

Published on

Published in: Technology
0 Comments
5 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
6,188
On SlideShare
0
From Embeds
0
Number of Embeds
2,263
Actions
Shares
0
Downloads
90
Comments
0
Likes
5
Embeds 0
No embeds

No notes for slide
  • 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” difficulty.
  • 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].
  • Smells smell foul, let’s hurl them out.
  • Refactoring for design smells

    1. 1. © S G Ganesh, Girish Suryanarayana, Tushar Sharma Refactoring for Design Smells - With Examples from OpenJDK S G Ganesh Tushar Sharma Girish Suryanarayana
    2. 2. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 2 Outline 9-May-13 Introduction Abstraction smells Encapsulation smells Modularization smells Hierarchy smells Key takeaways and conclusion
    3. 3. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 3 Why care about smells? • Quality Attributes  Reusability  Flexibility  Understandability  Extendibility  … 9-May-13 Product Quality Design Quality Design Smells  Maintainability: Affected by flexibility & extendibility  Reliability: Impacted by poor understandability  … Indicators  Rigidity & Fragility  Immobility & Opacity  Needless complexity  Needless repetition  …
    4. 4. © S G Ganesh, Girish Suryanarayana, Tushar Sharma What is a smell? Smells have been defined differently We embrace all the following definitions! 9-May-13 “We suggest that, like any living creature, system designs are subject to diseases, which are design smells (code smells and anti-patterns). Design smells are conjectured in the literature to impact the quality and life of systems.” – Hassaine et al. [HKGH10] “Smells are certain structures in the code that suggest (sometimes they scream for) the possibility of refactoring.” – M. Fowler [Fow99] “Code and design smells are poor solutions to recurring implementation and design problems.” – Moha et. al. [MGDM10] “Design smells are the odors of rotting software.” – R C Martin [Mar03]
    5. 5. © S G Ganesh, Girish Suryanarayana, Tushar Sharma “Design smells” aka… 9-May-13
    6. 6. © S G Ganesh, Girish Suryanarayana, Tushar Sharma Symptoms of rotting design 9-May-13
    7. 7. © S G Ganesh, Girish Suryanarayana, Tushar Sharma Why care about design quality? 9-May-13 * http://sqgne.org/presentations/2012-13/Jones-Sep-2012.pdf Design quality is important • Our organization develops critical, large, and/or reusable software A good designer is one who knows the design solutions A great designer is one who understands the impact of design defects/smells and knows how to address them Design quality means: flexibility, extensibility, understanda bility, reusability,...
    8. 8. © S G Ganesh, Girish Suryanarayana, Tushar Sharma Capers Jones on design errors in industrial software 9-May-13 * http://sqgne.org/presentations/2012-13/Jones-Sep-2012.pdf
    9. 9. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 9 So, what causes design smells? 9-May-13 Design is impacted by changing requirements. Often, a holistic approach is not adopted resulting in different parts of design evolving in isolation Changing requirements Either design principles are not applied or are applied wrongly Misapplication of design principles Refactoring that is done incompletely or inappropriately Lack of proper refactoring Often teams lack competent resources or the budget for supporting the above practices Lack of competent resources
    10. 10. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 10 Smells as violations of fundamental principles 9-May-13 Why does smell indicate something problematic? Because they indicate violation 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
    11. 11. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 11 Principle-based approach to design smells 9-May-13 This presentation on design smells is based on research work completed over a period of two years The focus of our work is only on structural design smells Published in Journal of Object Technology (Vol. 12, No. 2, 2013) S G Ganesh, Tushar Sharma, Girish Suryanarayana. Towards a Principle-based Classification of Structural Design Smells. In Journal of Object Technology, vol. 12, no. 2, 2013, pages 1:1–29.doi:10.5381/jot.2013.12.2.a1 URL: http://www.jot.fm/issues/issue_2013_06/article1.pdf (open access) Creational Structural Behavioral Architectural Design Implementation
    12. 12. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 12 Abstraction smells 9-May-13
    13. 13. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 13 Encapsulation smells 9-May-13
    14. 14. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 14 Modularization smells 9-May-13
    15. 15. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 15 Hierarchy smells 9-May-13
    16. 16. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 16 A note on examples in this presentation 9-May-13 Only around 50% of the smells are described with examples Cannot cover all of them in detail due to lack of time All examples are from OpenJDK 7.0 (open source) All illustrations are mostly as UML diagrams so no need to know Java (though you’ll appreciate more if you know Java)
    17. 17. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 17 How smells are related to technical debt? “When, due to constraints, I design quickly and dirty, my project is loaded with technical debt” – Ward Cunningham, 1992 14 August 2013
    18. 18. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 18 Outline 9-May-13 Introduction Abstraction smells Encapsulation smells Modularization smells Hierarchy smells Key takeaways and conclusion
    19. 19. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 19 Incomplete abstraction 9-May-13 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
    20. 20. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 20 Incomplete abstraction – Example 9-May-13 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!
    21. 21. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 21 Can you find this smell in this GoF pattern? 9-May-13
    22. 22. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 22 How to refactor & in future avoid this smell? 9-May-13 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
    23. 23. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 23 Missing abstraction 9-May-13 This smell arises when an entity that ideally should be a first class abstraction is not represented using an abstraction. Two variants: a) Using a primitive type or a group of primitive types instead of creating a class or interface b) Using string(s) for encoding values instead of creating a class or interface
    24. 24. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 24 Missing abstraction – Example 9-May-13 From 1.0 version of Java, the stack trace was printed to the standard output as a string with printStackTrace() method Programs that needed programmatic access to the stack trace elements had to write code to process the stack trace, for example, to get the line numbers, or find if the bug is a duplicate of another already filed bug. So what? 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 elided }
    25. 25. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 25 Missing abstraction – Example 9-May-13 So what is the problem with this? Such programs depend on the format of the elements in the string. If the format of the elements in the stack trace is changed, it would break the existing code that "parses" the stack trace and depended on that string format. Solution? Java 1.4 provided programmatic access to the stack trace through the introduction of StackTraceElement class This fixed the “missing abstraction” smell! public class Throwable { public void printStackTrace(); // Since 1.4 public StackTraceElement[] getStackTrace(); // other methods elided } // Since 1.4 public final class StackTraceElement { public String getFileName(); public int getLineNumber(); public String getClassName(); public String getMethodName(); public boolean isNativeMethod(); }
    26. 26. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 26 Multifaceted abstraction 9-May-13 This smell arises when an abstraction has more than one responsibility assigned to it (violating Single Responsibility Principle).
    27. 27. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 27 Multifaceted abstraction – Example 9-May-13 java.sql.DataTruncation serves as either an exception or a warning depending upon the context where it is used! to indicate problems in “write” operations while executing SQL queries, it is thrown as an exception object to the caller to indicate problems in “read” operations while executing SQL queries, the DataTruncation object will not be thrown but will be attached to other objects so that the callers can retrieve it
    28. 28. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 28 Multifaceted abstraction – Example 9-May-13 Suggested refactoring: Split the current DataTruncation class into two separate classes. The DataTruncationWarning class would inherit from the SQLWarning class and would be used to indicate problems in the “read” operations. The DataTruncationException class would inherit from the SQLException class and be used to indicate problems in the “write” operations.
    29. 29. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 29 Abstraction smells 9-May-13
    30. 30. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 30 Outline 9-May-13 Introduction Abstraction smells Encapsulation smells Modularization smells Hierarchy smells Key takeaways and conclusion
    31. 31. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 31 Lenient encapsulation 9-May-13 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.)
    32. 32. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 32 Lenient encapsulation – Example 9-May-13 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
    33. 33. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 33 Encapsulation smells 9-May-13
    34. 34. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 34 Outline 9-May-13 Introduction Abstraction smells Encapsulation smells Modularization smells Hierarchy smells Key takeaways and conclusion
    35. 35. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 35 Insufficient modularization 9-May-13 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
    36. 36. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 36 Insufficient modularization – Example 9-May-13 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!
    37. 37. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 37 Cyclic-dependency modularization 9-May-13 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”)
    38. 38. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 38 Cyclic-dependency modularization 9-May-13 Cyclic hierarchy Cyclic references Central references
    39. 39. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 39 Modularization smells 9-May-13
    40. 40. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 40 Outline 9-May-13 Introduction Abstraction smells Encapsulation smells Modularization smells Hierarchy smells Key takeaways and conclusion
    41. 41. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 41 Polygonal hierarchy 9-May-13 This smell arises when a base abstraction is repeatedly inherited in descendant abstraction(s) forming “polygons” in the inheritance graph. Two forms: a) Polygonal hierarchy of interfaces – leads to a cluttering of the inheritance hierarchy b) Polygonal hierarchy of implementation – with multiple implementation inheritance, common base abstractions get duplicated
    42. 42. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 42 Polygonal hierarchy – Example 9-May-13 There are three (not two!) polygons in this hierarchy This repeated inheritance complicates the hierarchy For example, by extending AbstractQueue, ConcurrentLink edQueue already implements Queue, and hence it is redundant for the ConcurrentLinkedQueue to explicitly implement Queue Redundant inheritance path can be safely removed (without affecting clients!)
    43. 43. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 43 Complex hierarchy 9-May-13 This smell arises when the inheritance graph is tangled, or excessively wide, or deep, or skewed.
    44. 44. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 44 Complex hierarchy – Example 9-May-13
    45. 45. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 45 Complex hierarchy – Example 9-May-13 What is the problem with this hierarchy? It is 8 levels deep! the class has 15 base types; each of these types potentially introduces or overrides methods; hence it is difficult to understand methods available in the DatagramChannel and its semantics an abstract class, which means it needs to be extended! Summary: cognitively difficult task for users to extend and use the class
    46. 46. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 46 Broken hierarchy 9-May-13 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.
    47. 47. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 47 Broken hierarchy – Example 9-May-13 Polygonal hierarchy Broken hierarchy
    48. 48. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 48 Broken hierarchy – Example 9-May-13 A Stack is not a Vector, and hence this inheritance relationship is an example of broken hierarchy Because of this design mistake, it is possible to insert or remove elements anywhere from a Stack, since Vector allows such methods and Stack inherits them! If the subclass inherits from the super class for reusing the functionality, apply Fowler’s “replace inheritance with delegation” refactoring Stack-Vector example fits this requirement and hence can be suitably refactored using this technique
    49. 49. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 49 Cyclic hierarchy 9-May-13 This smell arises when a supertype refers to any of its subtypes. Here the term ``reference'' means: (a) a supertype contains an object of one of its subtypes (b) a supertype refers to the type name of one of its subtypes (c) a supertype accesses data members or calls methods of one of its subtypes. Such a reference can be either direct or indirect. Since subtype knowledge introduces a cycle in the inheritance graph annotated with class relationships, this smell is termed as ``cyclic hierarchy''.
    50. 50. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 50 Cyclic hierarchy – Example 9-May-13 The private modInverse() method in MutableBigInteger class implements an algorithm that requires the use of signed integer and hence it creates instances of SignedMutableBigInteger; this instance creation introduces a reference from the supertype method to the subtype resulting in cyclic hierarchy smell. One potential refactoring for addressing this smell is to re-implement the modInverse() method (say, using an alternative algorithm if available) in such a way that it does not require creating instances of SignedMutableBigInteger.
    51. 51. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 51 Outline 9-May-13 Introduction Abstraction smells Encapsulation smells Modularization smells Hierarchy smells Key takeaways and conclusion
    52. 52. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 52 Key takeaways and conclusion 9-May-13 Design quality is important in our organizational context (where we develop large, complex, and/or reusable software) A great designer is one who understands the impact of design defects/smells and knows how to address them Design smells are candidates for refactoring Design smells can be viewed as violation of underlying fundamental design principles Some design smells can’t be fixed For such smells, the only way is to avoid introducing them in the first place! Context is important for smells Given the liability of a smell, in certain contexts, a designer may make a conscious decision to live with that smell
    53. 53. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 53 References 9-May-13
    54. 54. © S G Ganesh, Girish Suryanarayana, Tushar SharmaSlide 54 Thank you! 9-May-13

    ×