Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
University of Wisconsin Milwaukee UWM Digital Commons heses and Dissertations 8-1-2012 Integrating "Code Smells" Detection with Refactoring Tool Support Kwankamol Nongpong University of Wisconsin-Milwaukee Follow this and additional works at: htp://dc.uwm.edu/etd Part of the Computer Sciences Commons Recommended Citation Nongpong, Kwankamol, "Integrating "Code Smells" Detection with Refactoring Tool Support" (2012). heses and Dissertations. Paper 13. his Dissertation is brought to you for free and open access by UWM Digital Commons. It has been accepted for inclusion in heses and Dissertations by an authorized administrator of UWM Digital Commons. For more information, please contact kristinw@uwm.edu. Integrating “Code Smells” Detection with Refactoring Tool Support by Kwankamol Nongpong A Dissertation Submitted in Partial Fulfillment of the Requirements for the Degree of Doctor of Philosophy in Engineering at The University of Wisconsin-Milwaukee August 2012 ABSTRACT Integrating “Code Smells” Detection with Refactoring Tool Support by Kwankamol Nongpong The University of Wisconsin-Milwaukee, 2012 Under the Supervision of Professor John Tang Boyland Refactoring is a form of program transformation which preserves the semantics of the program. Refactoring frameworks for object-oriented programs were first introduced in 1992 by William Opdyke [73]. Few people apply refactoring in mainstream software development because it is time consuming and error-prone if done by hand. Since then, many refactoring tools have been developed but most of them do not have the capability of analyzing the program code and suggesting which and where refactorings should be applied. Previous work [49, 91, 19, 25] discusses many ways to detect refactoring candidates but such approaches are applied to a separate module. This work proposes an approach to integrate a “code smells” detector with a refactoring tool. To the best of our knowledge, no work has established connections between refactoring and finding code smells in terms of program analysis. This work identifies some common analyses required in these two processes. Determining which analyses ii are used in common allows us to reuse analysis information and avoid unnecessary recomputation which makes the approach more efficient. However, some code smells cannot be detected by using program analysis alone. In such cases, software metrics are adopted to help identify code smells. This work also introduces a novel metric for detecting “feature envy”. It demonstrates that program analysis and software metrics can work well together. A tool for Java programs called JCodeCanine has been developed using the discussed approach. JCodeCanine detects code smells within a program and proposes a list of refactorings that would help improve the internal software qualities. The programmer has an option whether to apply the suggested refactorings through a “quick fix”. It supports the complete process allowing the programmer to maintain the structure of his software system as it evolves over time. Our results show that restructuring the program while trying to preserve its behavior is feasible but is not easy to achieve without programmer’s declared design intents. Code smells, in general, are hard to detect and false positives could be generated in our approach. Hence, every detected smell must be reviewed by the programmer. This finding confirms that the tool should not be completely automated. An semi-automated tool is best suited for this purpose. iii c Copyright by Kwankamol Nongpong, 2012 All Rights Reserved iv ACKNOWLEDGMENTS I would like to thank my major professor, Professor John Tang Boyland, for his insights and supports throughout the program. Professor Boyland has been so understanding and supportive through my ups and downs. Without his time and dedication, this work would not have been complete, concise and comprehensive. I am thankful to the dissertation committee, Professor Ethan Munson, Dr. Adam Webber, Professor Mariam Zahedi and Professor Yi Ming Zou. Their valuable comments help cultivate this dissertation. I thank William Retert who helped develop an analysis. I am also grateful to all members of the UWM’s SLAP group for fruitful discussions on the research. Our weekly meetings have broadened my view to different research areas. I would also like to thank all undergraduate students who willingly donated their code to be used in testing and evaluating this work. Special thanks go to Songsak Channarukul and Tapanan Yeophantong for their encouraging words and suggestions. Last but not least, I would like to thank my parents and relatives for their endless supports and encouragements. I also thank my sister who always takes my phone calls despite the time zone differences. v Contents 1 Introduction 1 1.1 The Problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.2 Proposed Solution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 1.3 Contributions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 1.4 Outline of the Thesis . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 2 Related Work 7 2.1 Refactoring Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 2.2 Finding Refactoring Candidates or Code Smells . . . . . . . . . . . . 11 2.3 Code Smell Detection Tools . . . . . . . . . . . . . . . . . . . . . . . 15 2.4 Refactoring and Type Constraints . . . . . . . . . . . . . . . . . . . . 17 3 Refactoring Complexities 19 3.1 Opdyke’s Behavior Preservation Properties . . . . . . . . . . . . . . . 20 3.2 Low-Level Refactorings . . . . . . . . . . . . . . . . . . . . . . . . . . 22 3.2.1 22 Rename . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . vi 3.2.2 Extract Method . . . . . . . . . . . . . . . . . . . . . . . . . . 24 3.2.3 Reverse Conditional . . . . . . . . . . . . . . . . . . . . . . . 29 3.2.4 Consolidate Duplicated Conditional Fragments . . . . . . . . . 30 3.2.5 Swap Statements . . . . . . . . . . . . . . . . . . . . . . . . . 31 High-Level Refactorings . . . . . . . . . . . . . . . . . . . . . . . . . 36 3.3.1 Inline Method . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 3.3.2 Move Method . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 3.3.3 Convert Inheritance into Aggregation . . . . . . . . . . . . . . 43 3.3.4 Move Members between Aggregate and Component Class . . . 45 3.3.5 Create Abstract Superclass . . . . . . . . . . . . . . . . . . . . 49 3.4 Discussion and Summary . . . . . . . . . . . . . . . . . . . . . . . . . 51 3.5 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 3.3 4 Code Smells 4.1 4.2 4.3 54 Duplicated Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 4.1.1 Detecting Duplicated Code . . . . . . . . . . . . . . . . . . . . 55 4.1.2 Refactorings for Duplicated Code . . . . . . . . . . . . . . . . 56 Feature Envy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 4.2.1 Detecting Feature Envy . . . . . . . . . . . . . . . . . . . . . 57 4.2.2 Refactorings for Feature Envy . . . . . . . . . . . . . . . . . . 58 Data Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 4.3.1 60 Detecting Data Class . . . . . . . . . . . . . . . . . . . . . . . vii 4.3.2 4.4 4.5 Refactorings for Data Class . . . . . . . . . . . . . . . . . . . 63 Switch Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 4.4.1 Detecting a Switch Statement . . . . . . . . . . . . . . . . . . 65 4.4.2 Refactorings for Switch Statement . . . . . . . . . . . . . . . . 67 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 5 Metric for Feature Envy Detection 70 5.1 Coupling Measures . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 5.2 Cohesion Measures . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 5.3 Feature Envy Metric . . . . . . . . . . . . . . . . . . . . . . . . . . . 74 5.3.1 Internal Process . . . . . . . . . . . . . . . . . . . . . . . . . . 77 5.3.2 Choosing w and x . . . . . . . . . . . . . . . . . . . . . . . . . 81 5.3.3 Incorporating an Analysis . . . . . . . . . . . . . . . . . . . . 81 5.3.4 Metric Validation . . . . . . . . . . . . . . . . . . . . . . . . . 83 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85 5.4 6 The Framework 6.1 6.2 87 Existing Components . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 6.1.1 Fluid . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88 6.1.2 Eclipse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 6.1.3 Incompatibilities between Fluid and Eclipse . . . . . . . . . . 89 Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90 6.2.1 90 The Architecture . . . . . . . . . . . . . . . . . . . . . . . . . viii 6.3 6.4 6.2.2 Refactoring Manager . . . . . . . . . . . . . . . . . . . . . . . 94 6.2.3 Code Smells Detector . . . . . . . . . . . . . . . . . . . . . . . 95 6.2.4 Code Smells Resolution . . . . . . . . . . . . . . . . . . . . . . 95 6.2.5 Annotation Suggester . . . . . . . . . . . . . . . . . . . . . . . 96 JCodeCanine’s Key Features . . . . . . . . . . . . . . . . . . . . . . . 96 6.3.1 Code Smells Detection and Resolution . . . . . . . . . . . . . 97 6.3.2 Stand Alone Refactoring . . . . . . . . . . . . . . . . . . . . . 97 6.3.3 Annotation Suggestions . . . . . . . . . . . . . . . . . . . . . 100 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101 7 Empirical Results 103 7.1 Code Smells . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105 7.2 Refactorings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107 7.3 Code Qualities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 7.4 Discussion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 7.5 7.4.1 Duplicated Code . . . . . . . . . . . . . . . . . . . . . . . . . 114 7.4.2 Feature Envy . . . . . . . . . . . . . . . . . . . . . . . . . . . 114 7.4.3 Data Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114 7.4.4 Switch Statement . . . . . . . . . . . . . . . . . . . . . . . . . 115 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116 8 Conclusion 117 ix A Software Metrics 121 B Case Studies 123 B.1 Definitely a Feature Envy . . . . . . . . . . . . . . . . . . . . . . . . 123 B.2 Calls on Objects of the Interface Type . . . . . . . . . . . . . . . . . 124 B.3 Calls on Objects of Library Types . . . . . . . . . . . . . . . . . . . . 125 B.4 May-be a Feature Envy . . . . . . . . . . . . . . . . . . . . . . . . . . 126 B.5 Exception Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127 B.6 Switch Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128 x List of Figures 3.1 Rename Method Refactoring . . . . . . . . . . . . . . . . . . . . . . . 24 3.2 Careless Method Renaming . . . . . . . . . . . . . . . . . . . . . . . 25 3.3 Extract Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 3.4 Method Extraction that Requires a Return Statement . . . . . . . . . 27 3.5 Method Extraction inside a Loop . . . . . . . . . . . . . . . . . . . . 28 3.6 Reverse Conditional . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 3.7 Consolidate Duplicate Conditional Fragments . . . . . . . . . . . . . 31 3.8 Bad Statement Swap . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 3.9 Careless Inline Method . . . . . . . . . . . . . . . . . . . . . . . . . . 38 3.10 Class Hiearchy Analysis . . . . . . . . . . . . . . . . . . . . . . . . . 39 3.11 Move Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 3.12 Aggregate/Component Relationship . . . . . . . . . . . . . . . . . . . 45 3.13 Nonexclusive Components (1) . . . . . . . . . . . . . . . . . . . . . . 47 3.14 Nonexclusive Components (2) . . . . . . . . . . . . . . . . . . . . . . 47 3.15 Moving members . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 xi 3.16 Create Abstract Superclass . . . . . . . . . . . . . . . . . . . . . . . . 50 4.1 Feature envy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 4.2 Remove Feature Envy by Refactorings . . . . . . . . . . . . . . . . . 59 4.3 Data Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 4.4 Special Method Patterns . . . . . . . . . . . . . . . . . . . . . . . . . 62 4.5 Algorithm to detect data class . . . . . . . . . . . . . . . . . . . . . . 63 4.6 Poor Use of Switch Statement . . . . . . . . . . . . . . . . . . . . . . 65 4.7 False Positive for Switch Statement . . . . . . . . . . . . . . . . . . . 66 4.8 Algorithm to detect switch statement . . . . . . . . . . . . . . . . . . 66 4.9 Remove Switch Statement . . . . . . . . . . . . . . . . . . . . . . . . 68 4.10 A More Desirable Result . . . . . . . . . . . . . . . . . . . . . . . . . 69 5.1 Feature Envy Candidate . . . . . . . . . . . . . . . . . . . . . . . . . 80 5.2 Non Feature Envy Instance . . . . . . . . . . . . . . . . . . . . . . . 82 5.3 Maybe Feature Envy . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 6.1 Architecture of JCodeCanine . . . . . . . . . . . . . . . . . . . . . . . 92 6.2 Process Cycle in JCodeCanine . . . . . . . . . . . . . . . . . . . . . . 93 6.3 A Snapshot of Feature Envy Detected . . . . . . . . . . . . . . . . . . 98 6.4 A Snapshot of Data Class Detected . . . . . . . . . . . . . . . . . . . 98 6.5 Apply Refactoring through Quick Fix . . . . . . . . . . . . . . . . . . 99 6.6 Semantic Conditions Violated if bought() is Renamed to updateStock()100 xii 6.7 Scenario where Annotation Suggester is Invoked . . . . . . . . . . . . 101 B.1 Feature Envy Instance . . . . . . . . . . . . . . . . . . . . . . . . . . 124 B.2 Feature Envy on Objects of Library Type . . . . . . . . . . . . . . . 126 B.3 Indirect Usage of Library Class . . . . . . . . . . . . . . . . . . . . . 126 B.4 May-be a Feature Envy . . . . . . . . . . . . . . . . . . . . . . . . . . 127 B.5 Exception Subclass Detected as a Data Class . . . . . . . . . . . . . . 128 xiii List of Tables 3.1 Refactorings and Analyses . . . . . . . . . . . . . . . . . . . . . . . . 41 5.1 Cohesion Measurements on Instructor’s Code . . . . . . . . . . . . . 75 5.2 Variables in the Metric . . . . . . . . . . . . . . . . . . . . . . . . . . 80 6.1 Fluid and Eclipse Incompatibilities . . . . . . . . . . . . . . . . . . . 90 7.1 Numbers of Code Smells Detected, False Positives and Accuracy . . . 106 7.2 Number of Suggested Refactorings . . . . . . . . . . . . . . . . . . . . 108 7.3 Objectives for Different Metrics . . . . . . . . . . . . . . . . . . . . . 111 7.4 Comparison of Software Metric Measurements: Low (1) . . . . . . . . 111 7.5 Comparison of Software Metric Measurements: Low (2) . . . . . . . . 112 7.6 Comparison of Software Metric Measurements: Trade-off . . . . . . . 112 7.7 Impacts on Software Quality (+ Positive, - Negative, = No impact) . 112 xiv 1 Chapter 1 Introduction This chapter states the current problem in software development process as well as the motivation of this work. It further introduces the idea of this work and describes why the problem is not trivial and how we tackle the problem. The main contributions are also discussed in this chapter. 1.1 The Problem A software system becomes harder to maintain as it evolves over a period of time. Its design becomes more complicated and difficult to understand; hence it is necessary to reorganize the code once in a while. The most important thing when reorganizing code is to make sure that the program behaves the same way as it did before the reorganization has taken place. Semantic preserving program transformations are known as refactorings. The idea of refactoring is first introduced by William Opdyke 2 in 1992 [73]. The behavior preservation criterion is also discussed in his work. In the past, refactorings were not taken into the mainstream development process because applying refactorings by hand is error-prone and time consuming. The benefits of refactorings are not obvious to many developers because refactoring neither adds new features to the software nor improves any external software qualities. Therefore, many system developers give refactorings low priority. They are afraid that doing so would slow down the process and/or break their working code. Though refactoring does not help improve external software qualities, it helps improve internal software qualities such as reusability, maintainability and readability. It is inarguable that software design changes frequently during the development. Performing refactoring introduces a good coding discipline as it encourages reuse of existing code rather than rewriting new code from scratch. Refactoring is usually initiated/invoked by the developer. Most software developers only refactor their code when it is really necessary because this process requires in-depth knowledge of the software system. While many experienced developers can recognize the pattern and know when to refactor, novice programmers may find this process very difficult. Even with the knowledge of refactorings, it is not easy for the developer to determine which part of their code can benefit from refactorings. Many programmers learn from their experience. New generation programmers are more fortunate since Martin Fowler and Kent Beck address this issue in their book on Refactoring [33]. 3 They provide a list of troubled code patterns which could be alleviated by refactorings. Such patterns are widely known as code smells or bad smells. Recent work by Mantyla and others [58] attempts to make Fowler’s long monotonous list of smells more understandable. In their work, smells are classified into 7 different categories. The taxonomy helps recognize relationships between smells and make them more comprehensible to the developer. Despite the presence of such guidelines, finding code smells is not trivial. First and foremost, the developer has to recognize those patterns. The problem is, even if he can recognize them, he may not realize it when he finds one. Such a task becomes much more difficult for a large scale software system. The process of detecting and removing code smells with refactorings can be overwhelming. Without experience and knowledge of the design of the particular software, the risks of breaking the code and making the design worse are high. Applying refactoring carelessly can inadvertently change program behavior. When refactoring is carefully applied, we not only preserve the program behavior but also avoid introducing new bugs. Many refactoring tools have been developed [44, 2, 86, 43, 24]. There are also a number of works on finding refactoring candidates [49, 81, 19]. Nonetheless, these two frameworks usually work separately. It is unfortunate to see two related frameworks work on their own and not utilize the benefits to their maximum potentials. The relationship between code smells and refactorings are obvious but not many people have put them together. 4 1.2 Proposed Solution Code smells detection and refactoring are connected. While code smells represent design flaws in the software, refactoring is the process which restructures and transforms the software. In other words, code smells tell what the problems are and refactoring can then be used to correct such problems. Integrating these two processes would provide the complete process of locating the design flaws and improving software design. The integrated framework also provides other benefits which include: 1. Clearer Connection between Smells and Refactorings: It is evident that code smells and refactorings are related. However, the connections are abstract and usually obscured by their complexities. Putting them in the same framework presents their relationships in a more concrete way. 2. Analysis Information Reuse: Checking conditions before refactoring and detecting code smells require similar analyses (as discussed further in chapters 3 and 4). It is unnecessary to perform an analysis for information that we already have. Reusing analysis information makes the framework more efficient. However, to be able to correctly reuse the information, we have to keep track the parts of the program that change. Then, we must determine which analysis needs to be rerun to address those changes. The overhead of this framework will be keeping tracks of changes made on the code. I believe that such overhead is a small sacrifice for improved efficiency. 3. Continuous Programming Flow : With the combined framework, the developer 5 can check for code smells and remove them without disrupting the flow of their coding. It encourages the developer to make changes incrementally. Some code smells introduce design change and require the developer’s assistance. Not all code smells can be automatically detected. Hence, this work focuses only on those that can be detected automatically. A set of code smell detection analyses developed in this research is discussed in chapter 4. 1.3 Contributions The major contributions of this research are: 1. It defines conditions that must be checked to ensure behavior preservation before refactoring. 2. It identifies analyses required for the condition check. 3. It identifies analyses required to detect code smells. 4. It shows relationships between code smells and refactorings (in terms of analysis used). 5. It introduces metrics to detect code smells. Though this work implements a tool for Java programs, the theoretical ideas can be adapted not only to other object-oriented languages, but also to other programming language paradigms. 6 1.4 Outline of the Thesis Related work is discussed in Chapter 2. In this chapter, we provide observations on current refactoring tools. Existing techniques for finding refactoring candidates are reviewed. Other analyses that could be used to ensure semantic preservation are mentioned in this chapter. Chapter 3 discusses low-level and high-level refactorings, their complexities and their semantic preserving conditions. It explains the importance of semantic checks and shows examples of how careless refactoring could affect the observable behavior. Chapter 4 describes each code smell and the approach that this work uses for smells detection. Refactorings that can be applied to remove smells are also discussed in this chapter. In Chapter 5, we discuss some existing cohesion and coupling metrics and why they are unsuitable for feature envy detection. A novel metric to detect feature envy is introduced in this chapter. Chapter 6 describes the overall framework of our implementation, JCodeCanine which is a tool that analyzes Java source code. It detects code smells discussed in chapter 4 and suggests a list of refactorings that could address the design flaws. Chapter 7 provides discussion on empirical results. It looks at JCodeCanine’s efficiency in various aspects including the comparison of code quality before and after smells detection and refactoring application. Chapter 8 concludes the present work and some open problems for future work. Appendix B presents a few case studies for code smells detection. 7 Chapter 2 Related Work According to Opdyke [73], each refactoring basically consists of preconditions, mechanics and postconditions. All preconditions must be satisfied before applying refactoring. Likewise, all postconditions must be met after refactoring is applied1 . These conditions ensure that the program behavior is preserved. Opdyke also categorizes refactorings into low-level and high-level refactorings. Low-level refactorings are related to changing a program entity (e.g., create, move, delete). High-level refactorings are usually sequences of low-level refactorings. He also provides proofs of behavior preservation for many refactorings. The behavior preservation proofs of some lowlevel refactorings are trivial but implementing them is not as trivial. This issue will be discussed in Chapter 3. 1 The use of term “postcondition” in this research is different from the standard use of postconditions. Here we have postconditions apply to perform checks that are difficult to do before the transformation takes place. 8 2.1 Refactoring Tools In early 1990s, Don Roberts and his colleagues developed a refactoring tool called the Smalltalk Refactoring Browser [76]. This refactoring browser allows the user to perform many interesting refactorings automatically (e.g., Rename, Extract/Inline Method, Add/Remove Parameter). However, this early tool was not popular because it was a stand-alone tool separate from the integrated development environment (IDE). Developers found it inconvenient to switch back and forth between the IDE (develop code) and Refactoring Browser (refactor code). Thus later refactoring tools have been integrated in the IDEs. The following are refactoring tools for Java. IntelliJ IDEA [44] This is an expensive commercial IDE. This tool also supports Rename and Move Program Entities (e.g., package, class, method, field), Change Method Signature, Extract Method, Inline Method, Introduce Variable, Introduce Field, Inline Local Variable, Extract Interface, Extract Superclass, Encapsulate Fields, Pull Up Members, Push Down Members and Replace Inheritance with Delegation. RefactorIt RefactorIt is a commercial software that supports many automatic refactorings [2]. It can cooperate with Sun ONE Studio, Oracle 9i JDeveloper and Borland JBuilder. The supported refactorings are: Rename, Move Class, Move Method, Encapsulate Field, Create Factory Method, Extract Method, Extract Superclass/Interface, Minimize Access 9 Rights, Clean Imports, Create Constructor, Pull Up/Push Down Members. JRefactory [86] JRefactory is a tool that is first developed by Chris Seguin. However, Mike Atkinson has taken over the leadership role since late 2002. This tool allows easy application of refactorings by providing user interface based on UML diagrams as visualization of Java classes. It can cooperate with JBuilder and Elixir IDEs. JRefactory supports the following refactorings: Move Class, Rename Class, Add an Abstract Superclass, Remove Class, Push Up Field, Pull Down Field, Move Method. jFactor [43] jFactor for VisualAge Java is a commercial product that provide a set of refactorings. Extract Method, Rename Method Variables, Introduce Explaining Variable, Inline Temp, Inline Method, Rename Method, Pull Up/Push Down Method, Rename Field, Pull Up/Push Down Field, Encapsulate Field, Extract Superclass/Interface. Transmogrify [85] Transmogrify is a Java source analysis and manipulation tool. This tool is under development and currently is focused on the refactoring tool. It is available as a plug-in for JBuilder and Forte4Java. It supports a limited set of refactorings such as Extract Method, Replace Temp with Query, Inline Temp, Pull Up Field. 10 Eclipse [24] Eclipse is a generic development environment by IBM. It also has refactoring support and some analysis. This project is open source. The current version of Eclipse (Helio version 3.6.1) supports many types of refactorings which include Move Method, Rename Method, Encapsulate Fields and etc. Microsoft Visual Studio Microsoft Visual Studio is an integrated development environment. It supports many primitive refactorings. Generally, refactoring tools provide a list of refactorings in which the user can choose from the menu. Once the user chooses which refactoring to apply, the tool performs analyses in the background checking the required conditions. If those conditions are met, then refactoring can be performed. A few tools, like the refactoring support in Eclipse, allow the user to preview the resulting code before committing changes to the code. The preview feature gives the user a better idea of which part of his code will be affected by such a refactoring and whether it corresponds with his intention. There are many refactoring tools for other programming languages like C++. For instance, Xrefactory also known as xref [95] is a refactoring browser for Emacs, XEmacs and jEdit. CppRefactory [84] is another open source refactoring tool that automates the refactoring process in a C++ project. Though the refactoring framework was originally proposed for the object-oriented programming language, many researchers apply the idea of refactoring to other language paradigms as well. Li and his colleagues propose refactoring tool support for functional languages [56]. Saadeh and Kourie [79] developed a refactoring tool for Prolog. The general idea is similar 11 to that of object-oriented languages but the conditions and mechanics are different. Although most tools discussed in this research apply refactorings by directly manipulating the source code, many software designers think about refactorings at the design level. Researchers who are interested in design-level transformations include Griswold and Bowdidge [40]. They state that it is rather difficult to conceptualize program structure by just observing the program text. Instead of using program text, a graphical representation of program structure is used as it permits direct manipulation of the program structure at design level. Gorp et al. [37], Enckevort [26] and Saadeh et al. [78] and propose techniques to apply refactorings to UML diagrams. We omit further discussion regarding design-level transformations, since our work uses source code manipulation approach. Like Opdyke and Fowler, we believe that refactoring tool cannot be completely automated. A good refactoring tool should give the developers the final authority. It must interact with the developer because inferring design intent is difficult. Some refactorings may introduce a design change which requires software developer’s insights because he has the best knowledge of the program context. A tool can facilitate the process by suggesting a set of refactorings and helps ensure that each refactoring is applied correctly. 2.2 Finding Refactoring Candidates or Code Smells Kataoka et al. [49] propose that program invariants can be used to find refactoring candidates. First, they define patterns of invariants that identify a potential candidate 12 for each refactoring. If the program invariant match the pattern, it is considered a refactoring candidate. For instance, a parameter can be removed, if it is not used, it is a constant, or its value can be computed from other source. The invariant patterns p for Remove Parameter refactoring are: • p = constant • p = f(a, b, . . . ) Their approach is independent of the technique to find invariants. Either dynamic or static analysis techniques can be used. The static approach requires the programmer to explicitly annotate his program with his design intent and most programmers consider it troublesome to carry out this task. Sometimes invariants are implicit. An alternative to expecting programmers to annotate code is to automatically infer invariants. Invariant inference can be done by performing statically [29] or dynamically [27]. With the dynamic approach, the program is instrumented to trace variables of interest. However, the results from dynamic approach depend on the quality of test suites. They are, in general, true only for a set of some program inputs. On the contrary, static analyses are sound with respect to all possible executions. Hence, it is desirable to combine static with dynamic approach but so far, no one has succeeded. A detailed evaluation of static and dynamic invariant inference tools is given by Nimmer and Ernst [70]. Kataoka’s approach is applicable to a limited number of refactorings. Not all refactoring candidates are discoverable from invariants. Determining other refactoring 13 candidates requires other techniques. Generally, semantic analysis of the program is required. Melton and Tempero [64] suggest an approach that identifies refactoring opportunities using dependency graphs. According to their statement, long cycles are hard to understand, test and reuse. If someone wants to understand a class in a cycle, he is required to understand every other class in the dependency cycle as well. Hence, cycles should be detected and removed. If a class is involved in many cycles, it is desirable to break the cycle by extracting an interface from such a class. Many researchers use metrics to identify refactoring candidates [9, 81, 42, 80, 63, 82]. Bieman and Kang define cohesion as a degree to which modules belong together [9]. Simon and others [81] propose a distance cohesion metric which represents how close two or more program entities are. They use program visualization as an aid in interpretation of the results. The distance cohesion metric can be applied to a limited set of refactorings such as Move Method, Inline/Extract Class. Singh and Kahlon [82] propose metrics model to identify code smells which include cohesion and encapsulation metrics. They argue that encapsulation should be measured from the unity and the visibility of class members and introduce two new metrics for information hiding and encapsulation. In this work, a statistical analysis is applied on a set of software metrics which is then grouped by Mantyla’s bad smell categorization [59]. The results show that their new encapsulation and information hiding metrics play a big role in identifying smelly classes. 14 Tourwè and Mens [91] introduce an approach that is independent of language syntax by using logic meta-programming. Similar to any logic programming, the key components of this approach are facts and rules. Each program has its own set of facts. Facts (e.g., inheritance hierarchy) can be derived automatically from the code. Rules for finding refactoring candidates, on the other hand, must be defined manually. However, they are true and can be used for any program. This approach can be applied to any language; nonetheless, the results from this approach are in an intermediate form. They must be converted to another form so that they can be used easily in the successive steps. Such a conversion causes some overhead (mapping from program representation to facts and vice versa). The accuracy of results depends on the quality and the completeness of defined rules. Moreover, the intermediate representation may not map well back to the source. For instance, comments may be lost during the mapping. While many works focus on detection algorithms for specific smells, Moha et al. propose a technique to detect design defects using high-level abstraction through UML class diagrams [65, 66]. They define a meta-model for defects’ specification and algorithms to detect design defects from the meta-model. Their defect correction technique is based on a rule-based language. A number of researches aim to achieve better accuracy in detecting code smells and/or finding refactoring candidates by performing both structural and semantic analyses [23, 21, 15, 72]. Conceptual relation or semantic information are extracted by information retrieval techniques (Shingles, Latent Semantic Indexing) and natural 15 language processing techniques. However, some semantic information can only be retrieved at run-time which makes semantic analysis (especially through dynamic metric) a lot more expensive than syntactic or structural-based analysis. Unlike any previously discussed approaches that consider only the current version of the source code, a number of research works consider code history through change metrics [19, 80]. Demeyer et al. [19] focus on the reverse engineering effort by determining where the implementation has changed. They study the history of the software and find refactorings between successive versions of software based on change metrics. Three categories of metrics are considered: 1) method size, 2) class size and 3) inheritance. After deriving the metrics for each version of the software, numbers from those metrics are then compared. Any substantial differences imply that there were major changes between versions and that refactorings may have been applied. The cons to this approach is that software logs must be available; hence, unversioned software will not benefit from this approach. However, for versioned software, it helps understand design changes in a software system and learn how it evolves. 2.3 Code Smell Detection Tools There are a number of tools that support automatic code inspection. The well-known C analyzer LINT [47] and its Java variant JLINT [3] can check for type violations, null references, array bounds errors, etc. These tools focus on improving code quality from a technical perspective. JDeodorant [93, 31] is another code smell detection tool which specifically identifies type-checking bad smells in Java source code. Code 16 smells that the early version of JDeodorant can detect Replace Conditional with Polymorphism and Replace Type Code with State/Strategy. JDeodorant, however, only identifies code smells. It does not provide any suggestions or recommendations on how such smells should be removed. Recent version of JDeodorant [31] also identifies God Classes, suggests where to apply Extract Class refactoring and allows the programmer to perform class extraction in the process. The work by Eva van Emden and Leon Moonen [25] and RevJava [30] are more closely related to this work as they focus on improving code quality from a program design and programming practice perspective. RevJava is a Java analysis tool that performs design review and and architectural conformance checking. Based on predefined and user-defined design rules, the system analyzes Java bytecode, checks if any rules are violated and reports them to the user. However, RevJava is not suitable for large software systems because it has no support for visualization of rule violations. Emden and Moonen [25] categorize code smells into two kinds of aspects: primitive smell aspects and derived smell aspects. Primitive smell aspects can be observed directly from the code. Derived smell aspects, on the other hand, are inferred from other aspects according to a set of inference rules. In this architecture, the detection of primitive smell aspects and the inference of derived smell aspects are treated as separate units, and consequently, they are extendable. The programmer can add new code smells by extending the inference rules. Unlike RevJava, Emden’s work analyzes Java source code because some coding standards cannot be checked on bytecode, since the bytecode contains less information 17 than the original source code. The code smells detection process is: 1) find entities of interest, 2) inspect them for primitive smells, 3) store information in a repository and 4) infer derived smells from the repository. After the smells have been detected, they are presented to the user by visualizing the source model using graphs. 2.4 Refactoring and Type Constraints Type constraints are usually used to type check the program. Some researchers adopt this idea and add type constraints to the condition check in order to ensure behavior preservation in refactoring. Tip et al. [90, 89] use type constraints for a set of refactorings that is related to generalization, e.g., Pull Up Members and Extract Interface. The type constraints are used to verify the refactoring’s preconditions. Research work by Balaban et al. uses type constraints for library class migration [6]. All methods in legacy classes (e.g., Hashtable and Vector) have been superseded by classes (HashMap and ArrayList respectively) which provide similar functionalities except they allow unsynchronized access to their elements. When replacing legacy classes with unsynchronized, synchronization wrapper. In their research, a set of type constraint rules to migrate program that uses legacy class are defined. The programmer must define a set of migration specifications. Type constraint rules are generated from the given program and migration specifications. The runtime of the analysis is exponential. They also provide an algorithm for Escape Analysis that is used to check for thread safety. 18 It is inarguable that type constraints is useful for many refactorings. However, its usage restricts to only refactorings that involve types and those that could introduce type violation if applied carelessly. 19 Chapter 3 Refactoring Complexities Refactorings are structural changes made to a program that preserve program semantics. While many works [33, 88, 10] look at how refactoring could improve the structure and design of a program, this work focuses on the latter issue i.e., how to check that the change is semantics preserving. Behavior preservation is important because if it is not assured, the program could produce different results after the changes. Refactoring, if applied correctly, is ideal for software evolution, since it is guaranteed that no new bugs are introduced. This chapter presents and analyzes a number of refactorings. While the refactorings have been discusssed elsewhere, this work appears to be the first to discuss the analyses necessary to automate the refactorings. Most refactorings discussed in this chapter are from the refactoring book by Martin Fowler [33]. Two additional simple low-level refactorings were noticed while we were examining an actual case of software evolution: Reverse Conditional and 20 Consolidate Duplicated Conditional Fragments. This chapter categorizes refactorings as low-level (Section 3.2) and high-level (Section 3.3). Fowler provides the definition of each refactoring but does not discuss the complexities with respect to behavior preservation. His suggestion is to test the code and compare the output after each refactoring. This is an ad-hoc approach. Such an approach is not fool-proof because it relies solely on test cases. Moreover, it could put programmers in a situation where changes have been made but the refactored code produces different results. Testing code after refactoring is necessary but insufficient. This chapter starts with seven properties defined by Opdyke that must be used to ensure behavior preservation. It describes characteristics and complexities of each refactoring. Refactoring complexities will be discussed based on properties identified by Opdyke (Section 3.1). An explanation of why and which analysis is required for each refactoring in order to keep semantics unchanged (Opdyke’s 7th property) is also presented in this chapter. Most code examples are from real world projects. Some are from the Fluid Framework and some are CS552 students and instructors who generously donated their code. 3.1 Opdyke’s Behavior Preservation Properties Opdyke [73] has determined a set of properties of programs that must be checked to ensure behavior preservation. Such properties are: 1. Unique Superclass 21 2. Distinct Class Names 3. Distinct Member Names 4. Inherited Member Variables Not Redefined 5. Compatible Signatures in Member Function Redefinition 6. Type-Safe Assignments 7. Semantically Equivalent References and Operations The first six properties are syntactic while the seventh property is semantic. The compiler can usually detect any violations of syntactic properties but not the semantic property. Checking syntax errors after refactoring is necessary but insufficient to guarantee behavior preservation. In order to ensure that a program after refactoring is semantically equivalent to the program before refactoring, the preconditions for each refactoring must be carefully defined. One of the most well-known refactoring tools in the market is refactoring support in Eclipse IDE. Unfortunately Eclipse’s refactoring support only checks the syntax of the code after applying refactoring. It does not check the conditions related to program semantics. It employs only the first six properties which do not fully ensure behavior preservation. Therefore, using Eclipse’s refactoring tool is unsafe because it may change the semantics of the program. It is conceivable that Eclipse will integrate code smell detection with the refactoring tool in the future. In this case, this dissertation still has a positive contribution in the semantic analyses. Such 22 semantic conditions can be used in addition to the syntactic checks. Furthermore, the contributions from this work are also applicable to other object-oriented languages and can also be used as a guideline for those who want to implement code smell warnings for other types of refactorings. 3.2 Low-Level Refactorings The definition of low-level refactoring in this work is somewhat different from that of Opdyke’s. While Opdyke defines low-level refactorings as those that for which it is trivial to show that they are behavior preserving, this work considers a refactoring to be low-level if it does not involve complicated program analyses to ensure behavior preservation. 3.2.1 Rename Renaming is the most frequently used refactoring. It usually takes place when we find that the name of a program entity does not represent its purpose. It is common that programmers do not the name right the first time. Complexities Renaming may sound simple but implementing such a refactoring while trying to preserve semantics is not trivial. Suppose a programmer wants to rename a method. Putting behavior preservation aside, one of the complexities involves updating all references to use the new name. With behavior preservation in mind, it is necessary 23 to check if the class already has a method with that name before renaming. If such a method exists, two things could happen, 1) if it has the same signature as the one to be renamed, a compile error occurs. 2) if they have different signatures, there may be no compile error but this renaming causes design change, because it introduces overloading which may cause overloading resolution to give a different result. More complexities arise since inheritance must also be taken into consideration. In the object-oriented programming world, looking at the class that defines the method alone is not sufficient. Though it may look like everything works the same way within one class, other classes in the same hierarchy may be affected by that change. We have to go up and down the inheritance hierarchy because renaming without such information can create overriding. Introducing an overloading/overriding method may change semantics of the program. Figure 3.2 demonstrates careless method renaming. Though renaming print method to display in the class FarewellMessage does not introduce compile errors. Method displayAndExtra in figure 3.2b behaves differently. Unlike the former code, it shows message Hello instead of Good Bye. Required Analysis None. 24 private void updateWarehouse() { ... writeWarehouse(); } private void updateWarehouse() { ... saveWarehouse(); } private void exitWarehouse() { ... writeWarehouse(); System.exit(0); } private void exitWarehouse() { ... saveWarehouse(); System.exit(0); } private void writeWarehouse() { ... } private void saveWarehouse() { ... } (a) before (b) after Figure 3.1: Rename Method Refactoring 3.2.2 Extract Method When a method is too long or is doing too much, we can extract a region of code and make a new method for it. Not only does extracting method make the code more readable, but it also promotes code reuse. An indirect result of extracting a method is improving the code maintainability. Sometimes a method is extracted when the programmer foresees substantial future changes such as adding more responsibilities to a method. Complexities Extract Method is done within a class so it is less complicated than refactorings that involve more than one classes. The newly extracted method must have access to all local variables it uses. Therefore, such variables must be passed into the extracted method as parameters. Fields are exceptions, since they are accessible through out the class. Figure 3.3 shows how a method is extracted. The programmer wants to extract 25 public class Message { void display() { System.out.println(‘‘Hello’’); } } public class FarewellMessage extends Message { void print() { System.out.println(‘‘Good Bye’’); } void displayAndExtra() { display(); //print Hello ... print(); //print Good Bye } } (a) before public class Message { void display() { System.out.println(‘‘Hello’’); } } public class FarewellMessage extends Message { void display() { System.out.println(‘‘Good Bye’’); } void displayAndExtra() { display(); //print Good Bye ... display(); //print Good Bye } } (b) after Figure 3.2: Careless Method Renaming 26 public void init() { JTextField textField = _itemPrice; int iCol = 5; textField.setColumns(iCol); textField.setFont(new Font(textField.getFont().getFontName(), Font.BOLD, textField.getFont().getSize())); textField.setBackground(pricePanel.getBackground()); textField.setEditable(false); textField.setBorder(BorderFactory.createEmptyBorder()); pricePanel.add(textField); } (a) before public void init() { JTextField textField = itemPrice; int iCol = 5; setLabel(pricePanel, textField, iCol); pricePanel.add(textField); } private void setLabel(JPanel pricePanel, JTextField textField, int iCol) { textField.setColumns(iCol); textField.setFont(new Font(textField.getFont().getFontName(), Font.BOLD, textField.getFont().getSize())); textField.setBackground(pricePanel.getBackground()); textField.setEditable(false); textField.setBorder(BorderFactory.createEmptyBorder()); } (b) after Figure 3.3: Extract Method a portion of code where several properties of textField are set. Since the extracted code refers to local variables: pricePanel, textField, iCol, they are added to the setLabel method signature. More complexity arises if the extracted code contains an assignment to a local variable. If that local variable is not used after the extracted code, no other steps are required. If it is used, the extracted method must return the value of that local 27 static void shipment(Warehouse w, BufferedReader br) { System.out.println("Please enter order"); Order o = new Order(); o.read(br); Order l = o.ship(w); System.out.println("Back-ordered: "); l.write(System.out); } (a) before static void shipment(Warehouse w, BufferedReader br) { Order o = readOrder(br); Order l = o.ship(w); System.out.println("Back-ordered: "); l.write(System.out); } static Order readOrder(BufferredReader br) { System.out.println("Please enter order"); Order o = new Order(); o.read(br); return o; } (b) after Figure 3.4: Method Extraction that Requires a Return Statement variable. This requirement is established to ensure that the original method still has access to the same object after the extraction. However, extracting code that has more than one assignment cannot be done because a method can only return one value. Consequently, it is necessary to check if there is any assignment to local variables in that region of code and if so, we have to further check the number of assignments. If there is only one assignment, we can proceed with the extraction process. If there is more than one assignment, the code cannot be extracted to a new method. Assignments to fields do not have this problem and do not require special treatments, since all methods have access to the class fields. Figure 3.4 illustrates the situation when a “return” statement has to be added to the extracted method. 28 int currentYear = 2012; int totalCost = 0; for (int i = 0; i < employeeList.size(); ++i) { Employee e = employeeList.get(i); int cost = e.getManMonth() * e.getStandardRate(currentYear); totalCost = totalCost + cost; } //to be extracted (a) before int currentYear = 2012; int totalCost = 0; for (int i = 0; i < employeeList.size(); ++i) { Employee e = employeeList.get(i); totalCost = totalCost + e.getManMonthCost(currentYear); } // A newly extracted method in Employee class public int getManMonthCost(int year) { return e.getManMonth() * e.getStandardRate(year); } (b) after Figure 3.5: Method Extraction inside a Loop Required Analysis Live Variable Analysis: Live variable analysis [69] is used to determine useless variables. It determines whether a variable will be used in the future. If the variable is not used, it is considered “dead” and can be removed. For method extraction, live variable analysis is used in a different aspect. We use the analysis to identify a set of variables that are used in the extracted code. The result of the analysis represents all live variables that must be passed as parameters into the newly extracted method. Furthermore, we need live variable analysis to determine variable definition inside a loop. If a variable is redefined inside the loop, the extracted method must return the value for that variable as demonstrated in Figure 3.5 29 if (!isStronger(a, b)) { winner = b; } else { winner = a; } (a) before if (isStronger(a, b)) { winner = a; } else { winner = b; } (b) after Figure 3.6: Reverse Conditional 3.2.3 Reverse Conditional Reverse conditional refactoring is one of the refactorings that we identified during the early stages of this research. At that time, we looked at the history of two classes written in Java, studied changes made in 35 versions over 3 years of development. We then determined which changes could be done using refactoring. The code is written and modified by three different people and the final length of the code is XXX lines so it is a non-trivial project. It is noteworthy to remark that the name Reverse Conditional is defined by Bill Murphy and Martin Fowler. Not long after we discovered this refactoring, they found such a refactoring and gave it an official name on www.refactoring.com. Reverse Conditional is a very simple refactoring since it does not require any analysis. Proving that this refactoring preserves program behavior is trivial. It is simple to perform and is useful, since it can improve the readability of the code. Both Figure 3.6(a) and Figure 3.6(b) give the same results, the latter code makes more sense than the former code. Complexities None. 30 Required Analysis None. 3.2.4 Consolidate Duplicated Conditional Fragments This is another refactoring we discovered that is not in Fowler’s book. It is recommended when similar fragments of code exist in every branch of a conditional statement. It is an instance of code hoisting. Code hoisting is an optimization to reduce code size by eliminating statements that occur in multiple code paths from a single common point in the CFG. Though performing code hoisting on conditional statements does not improve code speed as it does for loop invariants, it simplifies the code and makes the code more manageable. Figure 3.7 shows how statements are consolidated. Complexities In order to safely move the duplicated code fragment out of a conditional statement, we must be certain that such fragment is not affected by the code nor have effects on the code inside the branches. In other words, the code inside the branches and the duplicated code fragment must be independent of each other. The code in Figure 3.7 requires no analysis and can be moved out because the statement is at the end of each branch. Hence, there is no statement to check for conflicts in effects. If the statement to be moved is at other locations, it is a general case of statement reordering which requires Effects Analysis. The complexity of statement reordering will be further 31 depth = 0; if (first < second) { ... depth = depth + 1; } else { ... depth = depth + 1; } (a) before depth = 0; if (first < second) { ... } else { ... } depth = depth + 1; (b) after Figure 3.7: Consolidate Duplicate Conditional Fragments described in Section 3.2.5. Required Analysis Effects Analysis. 3.2.5 Swap Statements Swapping statements is primarily changing the order of statements. Figure 3.8 demonstrates a careless swap. Swapping two statements inside the while loop without checking dependencies causes method factorial to act differently. Using the code in our example to compute factorial(3), Figure 3.8(a) returns 6 whereas Figure 3.8(b) returns 2. 32 // factorial(3) = 6 int factorial(int x) { int fact = 1; int y = x; while (y > 0) { fact = fact * y; y = y - 1; } return fact; } (a) before // factorial(3) = 2 int factorial(int x) { int fact = 1; int y = x; while (y > 0) { y = y - 1; fact = fact * y; } return fact; } (b) after Figure 3.8: Bad Statement Swap Complexities Swapping two statements sounds straightforward but when semantic preservation must be taken into account, it is no longer trivial. Analysis is needed because two statements can be swapped only if they do not “interfere” with each other. Behavior preservation cannot be guaranteed if a statement does not terminate or throws an exception. The semantic conditions in this research are based on an assumption that both statements terminate normally in all cases. 33 Required Analysis Effects Analysis: Boyland and Greenhouse introduce an effects system for objectoriented programming [39]. They define two kinds of effects: read effects and write effects. Read effects are those that may read the contents of a mutable state. Write effects are those that may change or read the contents of a mutable state. Statements interfere with each other if they are flow dependent (write, read), output dependent (write, write) or anti-dependent (read, write). In other words, two effects conflict if at least one is a write effect and they involve targets that may overlap. A target is a mutable variable or a mutable state of an instance of a class. Let s1 and s2 be two consecutive statements, x be an arbitrary variable or a mutable object. Statements s1 and s2 do not interfere with each other if: ∀x : s1 and s2 that have an overlapped target x Effect(s1 ) 6∋ (write, x) ∧ Effect(s2 ) 6∋ (write, x) Let’s revisit Figure 3.8 and see how effects analysis plays a crucial role in this refactoring. According to the condition discussed previously, if statements fact = fact - 1 and y = y - 1 have effects on a target variable, both statements must not write it. However, when we compute the read/write effects of the two statements, we get the following results: [5] : fact = fact ∗ y Effect([5]) = {(read, fact), (read, y), (write, fact)} 34 [6] : y = y − 1 Effect([6]) = {(read, y), (write, y)} As seen above, statement [6] writes y while statement [5] reads it which violates the semantic preservation condition. Hence, these two statements cannot be swapped without affecting program behavior. Our condition is not too restrictive. Though the condition requires two statements to be consecutive, it is generalized and can be applied to non-consecutive statements. Swapping non-consecutive statements is literally performing a series of swaps on two consecutive statements. Suppose there is a series of statements (s1 ; s2 ; . . . si ;) and we want to swap statements si with s1 . There will be 2i − 3 swaps i.e., i − 1 swaps from moving si in front of s1 and i − 2 swaps from moving s1 after si−1 . If the statement is being moved downward in the CFG, we have to compute the effects of such a statement and those below it. Similarly, if it is being moved up the CFG, we have to check its effects against the effects of all statements above it. Below exhibits steps taken when swapping si with s1 . 35 s1 ; s2 ; ... si−1 ; si ; si+1 ; ... sn−1 ; sn ; s1 ; s2 ; ... si ; si−1 ; si+1 ; ... sn−1 ; sn ; .. . s1 ; si ; s2 ; ... si−1 ; si+1 ; ... sn−1 ; sn ; si ; s1 ; s2 ; ... si−1 ; si+1 ; ... sn−1 ; sn ; si ; s2 ; s1 ; ... si−1 ; si+1 ; ... sn−1 ; sn ; .. . si ; s2 ; ... s1 ; si−1 ; si+1 ; ... sn−1 ; sn ; si ; s2 ; ... si−1 ; s1 ; si+1 ; ... sn−1 ; sn ; It is worth noting that the implementation does not have to perform the intermediate swaps. It only has to check the effects 2i − 3 times. After all effects are checked, we can change the order of s1 and si in one swap. Special case: If the statement is inside a loop (for, while) and we want to move it up (outside), we must check all effects against itself and their conditions. Moreover, Reaching Definition Analysis [69] is needed to determine loop invariant. However, if a statement is being moved outside an if statement, an extra check is needed i.e., it must occur in every branch. See Consolidate Conditional Fragment in Section 3.2.4. 36 3.3 High-Level Refactorings High-level refactorings are those that are difficult to implement and require program analysis to ensure behavior preservation. Since the definition of high-level refactoring in this work is different than Opdyke’s, many may refer some refactorings in this section as low-level. 3.3.1 Inline Method Method inlining is basically an inverse of method extraction. This particular refactoring inlines all invocations of a method and remove its declaration. Sometimes a programmer considers inlining a method if he finds that such a method does not have a lot of responsibilities. There are no apparent rules as to when to extract or inline a method. The decision is made based on each individual’s preference. Complexities Inline method involves an opposite issue of that of Extract Method. It is normal that a method to be inlined has a number of local variable definitions. When inlining, such variables will be introduced to the target method. Hence, our behavior preserving task involves checking if the target has already defined variables with the same names. If the variable names conflict, they have to be renamed. Otherwise, it will introduce variable re-definitions and cause compile errors (if in the same scope) or cause the program to behave differently (if in a different scope). Though method inlining involves changing the code in a single class, it could affect 37 other parts of the program depending on the type of the methods. No further checks are required for private method because it is invisible to other classes. Hence, the changes have no effects on them. The same issue applies to final methods. When a method is declared final, no other classes can override it so it is safe to perform inlining. However, if a method could be overridden, there is a high risk in changing the behavior. If the subclasses override the method that is being inlined, their behavior will definitely be affected. Consider Figure 3.9 which shows an instance of the problems. Suppose the programmer wants to inline method restockItem in class Merchant. It is obvious that there is no behavioral change for class Merchant. However, Retailer, a subclass of Merchant redefines (overrides) restockItem method. Inlining restockItem into checkStock affects Retailer’s behavior. Retailer.checkStock puts 5 items on the shelves before inlining but it puts 20 items after the change. Required Analysis None, if the method to be inlined is final or private i.e., the method cannot be overridden. If it is neither final nor private, an approach similar to devirtualization is needed. Devirtualization is an optimization technique to reduce the overhead of virtual (dynamic) method call by replacing a virtual method call with a particular method of a class. Though Inline Method refactoring does not focus on optimization, their mechanics and complexities are the same. Class Hierarchy Analysis (CHA) [17] is one of the most well-known techniques for 38 // Merchant.checkStock - restock 20 items if no item is in stock class Merchant { void checkStock(Item item) { if (getQuantity(item) == 0) restockItem(Item item); } void restockItem(Item item) { _shelves.put(item, 20); } } // Retailer.checkStock - restock 5 items if no item is in stock class Retailer extends Merchant { void restockItem(Item item) { _shelves.put(item, 5); } } (a) before // Merchant.checkStock - restock 20 items class Merchant { void checkStock(Item item) { if (getQuantity(item) == 0) _shelves.put(item, 20); } } // Retailer.checkStock - now restocks 20 item!! class Retailer extends Merchant { void restockItem(Item item) { shelves.put(item, 5); } } (b) after Figure 3.9: Careless Inline Method 39 Figure 3.10: Class Hiearchy Analysis devirtualization. It is a static analysis that determines a set of possible targets of a dynamic method call with the class hierarchy of the whole program. If it is possible to determine that there is no overridden method, the dynamic method call can be replaced with inlined code. CHA can be performed without programmer intervention. Ishizaki et al. claim the accuracy of class hierarchy analysis can be improved by adding type analysis and preexistence analysis [45]. Figure 3.10 illustrates a result of class hierarchy analysis for Merchant and Retailer code. Let’s consider the situation where a programmer wants to inline method restockItem into checkStock. Using CHA, we are able to determine that class Retailer overrides method checkStock which reveals that the inlining should not take place. A thorough discussion on method inlining can be found in the work by Detlefs and Agesen [20]. Sometimes the system’s performance gets worse because type casts 40 are usually inserted to preserve typability. Glew and Palsberg [35] discuss type-safe inlining. There are many techniques for devirtualization not discussed here. 3.3.2 Move Method Move Method is used to move a method to a different class than the one it is defined in. A method is usually moved when the programmer finds that such a method does not belong in the class in which it is defined. It is collaborating too much with other classes. Such collaboration includes using or being used by more features of other classes than the class it lives in. Consequently, classes become too highly coupled which is poor design. Moving a method to the superclass/subclass is also known as Pull Up Method/ Push Down Method. An example of a method move between unrelated classes is depicted in Figure 3.11. Complexities One of the issues when moving a method is to avoid name conflict. Similar to renaming, it is necessary to check that the target class does not have a method with the same name. Checking for name conflicts must also be done on the entire inheritance hierarchy to ensure behavior preservation. The reasons behind this issue are already discussed in Section 3.2.1. The other issue involves the possibility that the method being moved overrides that of its superclass. If it is overridden, any subclasses that override the method will 41 Refactoring Rename Move Method Extract Method Inline Method Reverse Conditional Consolidate Duplicated Conditional Fragments Swap Statements Move Field to Component Class Move Method to Component Class Move Field to Aggregate Class Live Variable Class Hierarchy Effects Effects Uniqueness Uniqueness Uniqueness Analysis None None Analysis Analysis None Analysis Analysis Analysis Analysis Analysis Table 3.1: Refactorings and Analyses be disturbed. A more detailed discussion was provided in Section 3.3.1. To ensure behavior preservation, our work will not allow the move if it is an overriding method. After a method is moved, it must still be accessible from its callers. To avoid any compile errors, the modifier of the moved method must be changed accordingly. For simplicity, this work always makes the moved method public. Maruyama and Takayuki also describe the complexities and security issues concerning Pull Up Method and Push Down Method [61]. Their concern is mainly on preventing unauthorized external code to access sensitive data. The degree of confidentiality or access level are measured and if the access level of the modified source code is decreased or downgraded, they consider the modified code to be vulnerable to attackers. Required Analysis None. 42 public class Driver { ... private static void writeWarehouse(String strFileName) throws IOException { File objFile = new File(strFileName); Warehouse objWh = Warehouse.getInstance(); if (objFile.exists() && objFile.canWrite()) { PrintStream psWrite = new PrintStream(new FileOutputStream(objFile)); objWh.write(psWrite); } else { objWh.write(System.out); } } } (a) before public class Driver { ... public void init() { ... Warehouse.writeWarehouse(‘‘warehouse.dat’’); } } public class Warehouse { public static void writeWarehouse(String strFileName) throws IOException { File objFile = new File(strFileName); if (objFile.exists() && objFile.canWrite()) { PrintStream psWrite = new PrintStream(new FileOutputStream(objFile)); this.write(psWrite); } else { this.write(System.out); } } } (b) after Figure 3.11: Move Method 43 3.3.3 Convert Inheritance into Aggregation Inheritance represents “is-a” relationship while aggregation/components represents “has-a” or “whole/part” relationship. According to Foote and Opdyke [32], Convert Inheritance into Aggregation refactoring supports software evolution and reuse. Changing inheritance into aggregation offers many benefits. Such benefits include “part” encapsulation where Gamma et al. call it “black-box reuse” [34]. The term black-box is derived from the fact that the internal structure of objects is invisible to the clients. The implementation of a subclass may become so dependent with its superclass that changes in the superclass will cause the subclass to change. With aggregation, there are fewer implementation dependencies. Moreover, an aggregate could have more than one instance of a component class. While inherited parts are static, aggregation allows a class to change their components at runtime. Each programmer has different viewpoints and they do not always choose the correct mechanism for modeling a particular relationship [73, 46]. However, as a program evolves, the relationships between classes become more evident. Convert Inheritance into Aggregation refactoring allows the programmer to convert a subclass/superclass relationship into an aggregation. Every behavior inherited by the subclass from its superclass before this refactoring will be delegated to a new component, which is an instance of the old superclass. Consider a class TwoDimensionalArray that defines a field elements and methods to access and manage its elements and a class Matrix that implements matrix operations. The original design is that the Matrix class inherits the TwoDimensionalArray 44 class. It is later realized that not all matrices should be conveyed in two-dimensional array, for instance using other kinds of representation for a sparse matrix would be more efficient. A matrix is not a representation. It “has” a representation. Hence, it is advisable to convert the original design from inheritance to aggregation. Complexities Converting inheritance into aggregation is complicated because it involves changing an inheritance hierarchy. The first thing to consider when inheritance changes is the accessibility to inherited methods and fields as they must still be accessible from the aggregate class when the changes occur. Method and field references must be updated not only in the aggregate class, but also in its clients. Updating references to inherited fields and methods can be tricky. There is a possibility that the aggregate class has a subclass that overrides a method of the component. Consider a situation where a class UpperTriangularMatrix which is a subclass of Matrix overrides the method putElement so that it prevents putting values other than 0 below the diagonal. The scope of reference updates must be expanded to cover all subclasses of the aggregate. Opdyke does not mention this issue in his work. Required Analysis None. 45 public class Automobile { Engine autoEngine; int numOfPassenger; Tire leftFrontTire; Tire rightFrontTire; Tire leftRearTire; Tire rightRearTire; ... } Figure 3.12: Aggregate/Component Relationship 3.3.4 Move Members between Aggregate and Component Class As discussed in the previous section, aggregation/components represents “has-a” or “whole-part” relationship. The idea of the Whole-Part pattern is to introduce a component (the whole) that encapsulates smaller objects (the parts) which prevents clients from accessing these parts directly. For instance, an automobile is composed of a body, four tires, a steering wheel and an engine. The car itself is an aggregate component while the tires, steering wheel and engine are parts (components) of the car. Figure 3.12 shows aggregate and component classes. Automobile is an aggregate class, while Engine and Tire are component classes. It is sometimes necessary to move class members between aggregate and component classes. The original design may be improper or unsuitable with the current requirements. It may grant or restrict too much client’s access. Moving members to a component class restricts access from other clients. On the contrary, moving members to an aggregate class removes permits direct access from clients. 46 Moving members between aggregate and component classes is not easy to accomplish without error. It is a special case of Move Method so it inherits all complexities that were discussed in Section 3.3.2. Typically, the part can only belong to one whole at a time (Figure 3.13). In addition, each part in one whole must be unique (Figure 3.14). Checking these conditions requires uniqueness analysis. Performing these changes automatically with precondition checks eliminates the possibility of inadvertently changing behavior. • Move Field to Component requires uniqueness analysis on the component field, not the field being moved before moving members from an aggregate to a component class. It is required to determine whether a variable qualifies as a component member variable i.e., every object assigned to it is not also assigned to another component variable. • Move Method to Component does not require a component to be an exclusive component of an aggregate class. Methods are different from fields as they are considered as services. However, it does require that a reference to the component class must be reachable whenever a method is called. In addition, the method must still be able to refer to an instance of the aggregate after the move. They are the same requirements as Move Method discussed in Section 3.3.2. The reference in the latter case could be carried out by adding an extra parameter to the method which creates delegation. • Move Field into Aggregate is an inverse operation of Move Field to 47 public class AutomobileFactory { Automobile car = new Automobile(); Tire temp = new Tire(); car.leftFrontTire = temp; car.rightFrontTire = temp; } Figure 3.13: Nonexclusive Components (1) public class AutomobileFactory { Automobile car1 = new Automobile(); Automobile car2 = new Automobile(); Tire temp = new Tire(); car1.leftFrontTire = temp; car2.leftFrontTire = temp; } Figure 3.14: Nonexclusive Components (2) Component which also requires uniqueness analysis. For the same reason, variables from the component class can be moved to an aggregate class only if the component is exclusive. If an aggregate has more than one instance of a component, moving a field to an aggregate will require adding a variable for each instance. Complexities Moving members between aggregate and component classes is a variant of Move Method and Move Field. In addition to complexities discussed in Section 3.3.2, it requires further analysis. It is more complicated than a normal move because it is required that such a component be exclusive. If a component is nonexclusive, the member cannot be moved because doing so will change program behavior. Consider the code shown in Figure 3.15(a), the Automobile class contains 4 tires and one warranty expiration for each tire. For design reasons, the programmer wants 48 public class Automobile { Engine autoEngine; int numOfPassenger; Tire leftFrontTire; Tire rightFrontTire; Tire leftRearTire; Tire rightRearTire; WarrantyInfo warrantyExpirationLeftFrontTire; WarrantyInfo warrantyExpirationRightFrontTire; WarrantyInfo warrantyExpirationLeftRearTire; WarrantyInfo warrantyExpirationRightRearTire; ... } public class Tire { ... } (a) before public class Automobile { Engine autoEngine; int numOfPassenger; Tire leftFrontTire; Tire rightFrontTire; Tire leftRearTire; Tire rightRearTire ... } public class Tire { ... WarrantyInfo warrantyExpiration; } (b) after Figure 3.15: Moving members 49 to move warranty expiration to the Tire class. Since Tire is a component class of Automobile class, we have to check if all instances of rightFrontTire, leftFrontTire, rightRearTire and leftRearTire are unique. If they pass uniqueness analysis, warranty information can be moved to the Tire class safely as seen in Figure 3.15(b). Figure 3.13 and Figure 3.14 illustrate two scenarios of nonexclusive components. In Figure 3.13, the same tire is assigned as the left front tire and right front tire. In this case, both leftFrontTire and rightFrontTire are not exclusive components. Let’s look at Figure 3.14. This time the same tire is assigned as the left front tire of two different cars. car1.leftFrontTire and car2.leftFrontTire are not unique. Therefore, leftFrontTire of class Automobile cannot be designated as an exclusive component. Required Analysis Uniqueness analysis is an analysis that determines whether a variable or an object is unique at a specific program point [5, 11]. 3.3.5 Create Abstract Superclass Using an abstract superclass is a classic design pattern [34]. An abstract superclass is desirable when two sibling classes implement and use common features. An abstract superclass supports code reuse and indirectly reduces a number of duplicated code. Opdyke and Johnson [74] have shown that it is feasible to create abstract superclass by a series of atomic refactorings: Move Method and Move Field which 50 public class Car { int mpg; double fuelInTank; public Car() { mpg = 35; fuelInTank = 6.056; } public void pumpGas(double g) { fuelInTank = fuelInTank + g; } public void drive(double miles) { double used = miles / mpg; fuelInTank = fuelInTank - used; } abstract class Automobile { int mpg; double fuelInTank; public void pumpGas(double g) { fuelInTank = fuelInTank + g; } public void drive(double miles) { double used = miles / mpg; fuelInTank = fuelInTank - used; } } public class Truck { int mpg; double fuelInTank; } public Truck() { mpg = 25; fuelInTank = 11.355; } public void pumpGas(double g) { fuelInTank = fuelInTank + g; } public void drive(double miles) { double used = miles / mpg; fuelInTank = fuelInTank - used; } } class Car extends Automobile { public Car() { mpg = 35; fuelInTank = 6.056; } } class Truck extends Automobile { public Truck() { mpg = 25; fuelInTank = 11.355; } } (a) before (b) after Figure 3.16: Create Abstract Superclass could be done after a new class with a unique name is created. Complexities One of the complexities of Create Abstract Superclass is finding common code that can be migrated to the abstract superclass. Another complexity relates to how we deal with common abstractions. Since it 51 involves moving common methods to the abstract superclass, it inherits some complexities of Move Method which have been discussed in Section 3.3.2. However, some of the condition checks could be skipped because the newly created abstract class is empty and has no members defined. There are no name conflicts in fields and methods. In addition to Move Method, fields that are referenced by the common code must also be moved to the superclass. Figure 3.16(a) shows that class Car and Truck implement similar sets of methods. This situation is an instance of duplicated code. Introducing an abstract class Automobile which centralizes the responsibilities obviously yields a better design. The changes that had to be done in two places formerly, can be taken care of in just one place. Required Analysis Duplicated Code Analysis. Furthermore, they must already have a common superclass or no other superclass than java.lang.Object. 3.4 Discussion and Summary In this chapter, we provide the descriptions and complexities of each refactoring as well as the analyses that it requires to ensure behavior preservation. For some refactorings, the behavior preservation condition check is more complicated than applying 52 refactoring itself to the code. Semantic properties for some refactorings require extensive program analysis. For instance, Swap Statements and Consolidate Conditional Fragments require effects analysis. Moving fields between aggregate and component requires uniqueness analysis. In general, the following conditions must be considered when refactoring. Redefinitions When a new entity is created, it is necessary to determine if the name conflicts with the existing declarations to avoid redefinitions. • local variables: could cause compile errors and semantic change depending of the scope. • fields: could cause compile errors and hiding • methods: could introduce overriding and overloading. Effects of the changes We need to take into consideration how the changes affect other parts of the program. In object-oriented programming, it is inadequate to analyze only the class that is being changed because it could be inherited. For instance, inlining a method that could be overridden requires some devirtualization analysis such as class hierarchy analysis. For many refactorings, the complexities come from ensuring reachability and updating references. 53 3.5 Summary This chapter has shown that a simple refactoring could change the program semantics. Program behavior is sensitive to changes even small ones. Every change made to the program should not be taken for granted. 54 Chapter 4 Code Smells Code smells are design flaws that can be solved by refactorings. They are considered as flags to the developer that some parts of the design may be inappropriate and that it can be improved. For the purpose of this work, we discuss a few representative code smells. There are a lot of code smells not mentioned nor developed in this work. A thorough catalog of code smells can be found in Fowler’s refactoring book [33]. As this work focuses on program analysis, code smells discussed in this work include those that require analyses. Though this work develops only a subset of the code smells, it provides some grounds which can be adapted to other types of code smells. 4.1 Duplicated Code Duplicated code (code clone) is one of the most common problems in software development. Previous work [55, 4] suggests that about 5-10% of the source of large 55 scale programs is duplicated code. A number of recent studies show that many wellknown open source systems have substantial duplicated code problems. The Java JDK (2002) is 21-29% duplicated [48]. The Linux kernel (2002) is estimated at 1525% duplicated [1]. The GNU compiler (1999) is about 19% duplicated [22]. Duplicated code is usually caused by copy-and-paste action with an intent to reuse the code. This technique is easy and cheap during software development but it is considered bad practice. It makes software maintenance more complicated in many ways: 1) If there exist bugs or errors in the original code, they will be propagated with every duplication. 2) More generally, when an instance of duplicated code needs to be changed, all other duplicated instances must also be modified. 3) Furthermore, duplicated code makes performing code auditing more difficult. Code auditing is an analysis of source code with attempts to reduce software vulnerabilities. 4.1.1 Detecting Duplicated Code Researchers have developed a number of approaches for so called “clone detection” [8, 22, 52, 48]. These approaches are: • text-based [22]. This approach is language independent but it can only detect exact textual matches. Similar code with different variable names is considered different. • graph-based [52, 54]. This approach uses program dependence graphs (PDGs). Krinke [54] finds similar code based on identifying maximal similar subgraphs. 56 Komondoor and Horwitz [52] use program slicing which allows non-contiguous clones to be detected. • token-based [48]. Token-based approach lies somewhere in between the textbased and syntax-based. The source code are tokenized using a lexer. It is able to detect non-exact matches. It is one of the most effective aproach that balances soundness and speed. • syntax-based [8]. Baxter and others use abstract syntax trees to detect exact match or near-miss clones. This approach can be applied to arbitrary program fragments. However, it cannot detect a clone where statements are arranged in different order. Each of the discussed approaches has its pros and cons. This work chooses to use the syntax-based method because it is simple to implement in our AST-based analysis framework. 4.1.2 Refactorings for Duplicated Code Duplicated code can be removed by different types of refactorings depending on where duplicates are found. 1. If they are in different methods of the same class, duplicated code can be removed by Extract Method. 2. If they are in two sibling classes, use Extract Method and/or Pull Up Method. 57 private void updateItemPanel() { Item item = getItem(); int q = getQuantity(); if (item == null) { itemPanel.clear(); } else { itemPanel.setItem(item); int inStock = Warehouse.getInstance().getQuantity(item); itemPanel.setInstock(q <= inStock && 0 < inStock); } } Figure 4.1: Feature envy 3. If they are in two unrelated classes, use Extract Class and/or Extract Method. 4.2 Feature Envy Feature Envy occurs when a method seems to be more interested in some other class than the one it is defined in. It designates improper coupling between classes. An instance of Feature Envy is shown in Figure 4.1. Method updateItemPanel is defined in class OrderItemPanel. However, it is mostly interested in ItemPanel, since it invokes ItemPanel’s methods on the field itemPanel several times. Such invocations could be moved to the ItemPanel class in order to centralizes manipulations to the defining class. 4.2.1 Detecting Feature Envy Feature Envy is a sign of improper coupling and cohesion. With this knowledge, cohesion and coupling measures seem to be the best candidates in finding feature envy in a class. We have done experiments on a number of cohesion and coupling metrics. 58 Unfortunately, none of them performed well when it comes to detecting feature envy. The results from cohesion and coupling measures are too broad as they can only tell which class is not cohesive and/or highly coupled with other classes. They do not tell which method has feature envy. Therefore, we developed a new metric to detect feature envy. The new metric will be discussed in more details in chapter 5. 4.2.2 Refactorings for Feature Envy Move Method is required for all instances of feature envy. The purpose of this refactoring is to put a method in a class that is more suitable. The pre-condition for Move Method and its complexities are discussed in section 3.3.2. In addition to Move Method, Extract Method is needed if the problematic code does not cover the entire method. It is used as an intermediate step before the actual move. Let’s revisit an example in Figure 4.1. Method updateItemPanel has a portion of code that seems to be more interested in ItemPanel. It calls clear, setItem and setInstock on the field itemPanel. Since those calls are inside an if statement, the if that covers method invocations on itemPanel could be extracted as shown in Figure 4.2(a) and moved to ItemPanel class as showned in Figure 4.2(b). In this particular example, ItemPanel is a component of OrderItemPanel. Hence, a special case of Move Method that is Move Method to Component is used. 59 private void updateItemPanel() { Item item = getItem(); int q = getQuantity(); doUpdate(item, q); } private void doUpdate(Item item, int quantity) { if (item == null) { itemPanel.clear(); } else { itemPanel.setItem(item); int inStock = Warehouse.getInstance().getQuantity(item); itemPanel.setInstock(q <= inStock && 0 < inStock); } } (a) Extract Method doUpdate public class OrderItemPanel { ... private void updateItemPanel() { Item item = getItem(); int q = getQuantity(); itemPanel.doUpdate(item, q); } } public class ItemPanel { public void doUpdate(Item item, int quantity) { if (item == null) { clear(); } else { setItem(item); int inStock = Warehouse.getInstance().getQuantity(item); setInstock(q <= inStock && 0 < inStock); } } } (b) Move Method doUpdate to Component ItemPanel Figure 4.2: Remove Feature Envy by Refactorings 60 4.3 Data Class A class that contains nothing but fields and get/set methods is called data class. When a class has no responsibility other than handing its data to the outsiders, it implies that its data is being manipulated by other classes. In practice, data should be encapsulated and not be exposed to others. Consider Figure 4.3(a), class ItemList only defines a constructor and a getItemList method which their only references are in OrderItemWindow class. OrderItemWindow solely controls itemList after creating an instance and obtaining the data (objItemList.getItemList). The whole class can be moved to OrderItemWindow and deleted as seen in Figure 4.3(b). 4.3.1 Detecting Data Class Figure 4.5 describes an algorithm that this research uses to detect a data class. Each class is evaluated by its members. If a class only has fields and “special” methods, it is considered to be a data class. Special methods include constructors and set/get methods. The tree structures of set/get methods are established as a benchmark in Figure 4.4. A method is determined by comparing its AST with the pre-defined patterns. If they match, it is marked as a special method. Fields and constructors do not need special treatments because they can be detected firsthanded by their node types. 61 abstract public class OrderItemWindow { ... protected static JList itemList; public OrderItemWindow(JFrame objParentFrame, Order objOrder, OrderItem objOrderItem) { ... ItemList objItemList = new ItemList(); itemList = objItemList.getItemList(); } } public class ItemList extends JList { private JList itemList; DefaultListModel listModel = new DefaultListModel(); public ItemList() { Iterator iter = Catalog.getInstance().getIterator(); while(iter.hasNext()) listModel.addElement((Item)iter.next()); itemList = new JList( listModel); } public JList getItemList() { return itemList; } } (a) before abstract public class OrderItemWindow { ... protected static JList itemList; public OrderItemWindow(JFrame objParentFrame, Order objOrder, OrderItem objOrderItem) { ... Iterator iter = Catalog.getInstance().getIterator(); DefaultListModel listModel = new DefaultListModel(); while (iter.hasNext()) listModel.addElement((Item) iter.next()); itemList = new JList( listModel); } } (b) after Figure 4.3: Data Class 62 (a) set method (b) get method Figure 4.4: Special Method Patterns 63 methods = {a method defined in a class} for each method ∈ methods do { if method’s structure matches special method’s patterns return false } return true Figure 4.5: Algorithm to detect data class 4.3.2 Refactorings for Data Class There are two options to handle a data class. We could delete it after moving its class members to other class or assign more responsibilities to it. Delete Data Class - For all fields and methods declared in the data class, we apply Move Field and Move Method to relocate the data class’s members to other classes that uses them. Then, the data class is deleted after the move is complete. However, the target classes could be in the same inheritance hierarchy. Adding features to every class will introduce field redefinitions and method overriding. To conservatively prevent any syntactic and semantic problems, features should be added to only one class. Determining which class in the hierarchy is the most suitable can be very complicated. Keep Data Class - Adding more responsibilities to the data class is one other alternative. Move Method that uses members of the data class from other classes. Nonetheless, a data class could be a growing class whose features have yet to be implemented. The tool implemented in this research does not take that into 64 consideration. It is left up to the developer to make the final decision whether the suspected class is indeed a data class. 4.4 Switch Statement A switch statement is basically another syntactic form of if-else statement. It makes the code more readable. In imperative programming, programmers usually use a switch statement to make polymorphic calls. However, it is different in object-oriented programming as polymorphism is handled automatically by the compiler also using dynamic dispatch. In object-oriented programming, a switch statement should not be used in place of polymorphism. It is believed that replacing conditionals with polymorphism could cause the performance to degrade. Polymorphism resolution introduces extra processor time to consult the method lookup table (Java) or virtual function table (C++). Therefore, programmers usually argue that the cost of refactoring is too high. Demeyer determines the performance cost in replacing a conditional with polymorphism [18]. According to his experiments on C++ programs using the best possible optimization, virtual functions and switch statements have similar overhead. He also concludes that refactorings that move behavior close to data improve maintainability without sacrificing performance. 65 class Student { public static int UNDERGRAD = 1; public static int GRADUATE = 2; public static int DISSERTATOR = 3; static double segregated_fee = 300; private int num_credits; private int classification; public Student(int c, int cls) { num_credits = c; classification = cls; } public double getTuition() { switch (getClassification()) { case UNDERGRAD: return segregated_fee + 300 * num_credits; case GRADUATE: return segregated_fee + 350 * num_credits; case DISSERTATOR: return segregated_fee + 200 * num_credits; } } public int getClassification() { return classification; } } Figure 4.6: Poor Use of Switch Statement 4.4.1 Detecting a Switch Statement For most code smells, the most difficult part is determining how to locate them and the easier part is the removal. Unlike other code smells, removing a switch statement is not as easy as locating it. Finding a switch statement in general is not difficult. We can perform a tree walk on the AST. Then, each switch statement found is added to a set of smells. However, reporting all switch statements found in the code is not desirable. There will be a lot of false positives. A false positive, in this case, is a switch statement that is not used to represent polymorphism. For instance, using a switch statement in an abstract factory method is appropriate. An abstract factory is one 66 public Student newStudent(int classification) switch (classification) { case UNDERGRAD: return new Undergraduate(); case GRADUATE: return new Graduate(); case DISSERTATOR: return new Dissertator(); } } { Figure 4.7: False Positive for Switch Statement badSwitch = ∅ for each node in the AST node is a switch statement for each case in switch if a new expression is not a child node of statements’ case badSwitch = badSwitch ∩ node Figure 4.8: Algorithm to detect switch statement of design patterns defined by the Gang of Four [34]. As shown in Figure 4.7, method newStudent creates a new instance of Student based on the given classification which is perfectly legitimate. Our goal here is to reduce the number of false positives. Determining which switch statement is bad is challenging. To avoid mistakenly detecting an abstract factory as bad use of a switch statement, this work ignores those that contain new expressions. An algorithm to detect a switch statement is trivial but is shown in Figure 4.8 for completeness. 67 4.4.2 Refactorings for Switch Statement Fowler has defined two composite refactorings called Replace Conditional with Polymorphism and Replace Type Code with Subclasses. Such refactorings are to be used to remove bad switch statements. For Replace Conditional with Polymorphism, a list of statements in each case are extracted to create an overriding method in the corresponding subclass. A switch statement is removed and the original method is made abstract. Nonetheless, it may be necessary to first apply Replace Type Code with Subclasses, if no subclasses are defined. This refactoring creates a subclass for each case in switch statement. Figure 4.9 illustrates the code after applying both refactoring in attempts to remove a switch statement from Figure 4.6. It is worth noting that the design may not be optimal after the switch statement is removed. More transformations may be necessary depending on the body of each case in the switch statement. For instance, the code in Figure 4.9 can be further improved by adding a new field fee per credit which leads to duplicates in getTuition method. Such methods can be move to class Student as discussed in Section 4.1. The final code after the transformations is depicted in Figure 4.10. This research only attempts to remove a switch statement that is used in place of polymorphism. We do not intend to provide the algorithm that will yield an optimal design. Further improvements on the code’s structure are left to the developer to carry out himself. 68 abstract class Student { static double segregated_fee = 300; private int num_credits; abstract public static double getTuition(); } class Undergraduate extends Student { ... public static double getTuition() { return segregated_fee + 300 * num_credits; } } class Graduate extends Student { ... public static double getTuition() { return segregated_fee + 350 * num_credits; } } class Dissertator extends Student { ... public static double getTuition() { return segregated_fee + 200 * num_credits; } } Figure 4.9: Remove Switch Statement 4.5 Summary This chapter has described the code smells studied for this dissertation as well as the sequence of refactorings that can be applied to remove each code smell. We also provide algorithms for detecting each smell. In particular, this work uses an AST-based algorithm to detect duplicated code which cannot detect clones created by statement reordering. Data class and switch statement detections are also performed on the AST. As data classes contains only the fields, accessors and mutators, the detection algorithm is rather straightforward. Detecting wrong use of switch statements, on the other hand, is very complicated and difficult. The approach for feature envy detection 69 abstract class Student { static double segregated_fee = 300; private int num_credits; private double fee_per_credit; public static double getTuition() { return segregated_fee + fee_per_credit * num_credits; } } class Undergraduate extends Student { public Undergraduate(int c) { fee_per_credit = 300; num_credits = c; } } class Graduate extends Student { public Graduate(int c) { fee_per_credit = 350; num_credits = c; } } class Dissertator extends Student { public Dissertator(int c) { fee_per_credit = 200; num_credits = c; } } Figure 4.10: A More Desirable Result is discussed in the next chapter. As we mentioned in the beginning of this chapter that this work focuses on a partial set of code smells. Many researchers work on detection algorithms for other smells. For instance, a number of research works [96, 63] discuss how to identify fragments of code in the long method needed to be extracted. Their approach does not just detect the code smell but also scope down the region of problematic code such which is more useful to the developer. It does not only identify where the problem is but it also provides suggestions on how to fix it. 70 Chapter 5 Metric for Feature Envy Detection In object-oriented systems, classes group data and related operations within a specific domain concept, and support object-oriented features such as data abstraction, encapsulation and inheritance. Many researchers have proposed metrics to measure object-oriented software qualities. Such qualities include but are not limited to coupling and cohesion. This work focuses on code smells that indicate poor OO designs with respect to coupling and cohesion therefore only measurements of coupling and cohesion will be discussed here. Du Bois and his colleagues [10] provide discussions on how refactoring can improve coupling and cohesion in software systems. 5.1 Coupling Measures Low coupling between objects is desirable for modular programming. A measure of coupling is useful in identifying an improper relationship between objects. In order to improve modularity and promote encapsulation, the relationship between classes 71 should be kept to a minimum. The higher the coupling, the higher the sensitivity to changes in other parts of the system. A small premeditated change in a highly coupled system could progress into a long series of unanticipated changes which makes it more difficult to maintain the system. Previously, coupling is defined subjectively which make it difficult to use in practice. Chidamber and Kemerer [14] was among the first who defined a metric to measure the coupling between objects. Specifically, their metric are called CBO (coupling between objects). According to their definition, two classes are coupled when methods declared in one class use methods or instance variables defined by the other classes. CBO is well known and is widely used in many software industries. Myer introduced a much more complicated metric. Coupling is defined in six levels. Such coupling levels are used to measure the interdependence of two modules [68]. Page-Jones extends Myer’s work by ordering the coupling levels based on their effects on maintainability, understandability and reusability [75]. If two modules are coupled in more than one way, they are strongly connected and are considered to be coupled at the highest level. In addition to the Myer’s six coupling levels, Offutt et al. added the zeroth level of coupling for modules that are independent [71]. There are many other approaches to measure coupling not discussed in this work. Many metrics discussed here can be automated but their computations are done on the source code and require the code to be written beforehand. Some metrics can be computed from the design of the software which allow the software qualities to be measured before starting the implementation [51, 87, 50]. 72 5.2 Cohesion Measures The concept of cohesion is the practice of keeping things that are related together. A good software design should obey the principle of high cohesion. A highly cohesive module is easy to maintain and reuse. Cohesiveness of methods within a class is desirable because it promotes encapsulation. The measurement of disparateness of methods helps identify design flaws. Lack of cohesion in a class implies that it should probably be split into two or more subclasses. One of the well-known cohesion metrics is introduced by Chidamber and Kemerer [14]. They proposed a metric called lack of cohesion in methods (LCOM). LCOM evaluates the internal cohesion based on method similarity. The method similarity is measured by considering the number of disjoint sets of instance variables used by methods in a given class (access relationship). Let M be a set of methods Mi , i = 1..n and Ii be a set of instance variables used by method Mi . If P = {(Ii , Ij )|Ii ∩ Ij = ∅} and Q = {(Ii , Ij )|Ii ∩ Ij 6= ∅}, then       |P | − |Q| LCOM =      0 if |P | > |Q| otherwise There are several concerns with LCOM. The most serious problem is LCOM measurement is not very discriminating. If a class has LCOM = 0, it could be interpreted in many ways: 1) it is a highly cohesive class, 2) it is not a very cohesive class, or 3) it is a class with no cohesion. 73 Many researchers define variants of LCOM to overcome the problems in the Chidamber and Kermerer’s LCOM [41]. We only discuss one variant suggested by Henderson-Sellers [41] because this metric will be used in the evaluation of our approach. Henderson-Sellers’ definition of LCOM is called LCOM*. Perfect cohesion in LCOM* is when all methods access all attributes. Let M = {Mi=1 m } be a set of methods in a class, A = {Aaj=1 }, be a set of attributes, and µ(Aj ) be the number of methods accessing each attribute Aj . LCOM ∗ =  P a 1 a j=1  µ (Aj ) − m 1−m Not only does LCOM* address the interpretation issues in its predecessor i.e., LCOM, but it is also easier to calculate. Briand’s RCI used DD-interactions (flow dependence) and DM-interactions (read dependence) to depict relationships among the class members [13]. Cohesion is then computed by dividing the number of actual DD- and DM-interactions by the number of all possible DD- and DM-interactions of the given class. Zhou proposed a novel graph representation, class member dependence graph or CDMG, to describe the data and control flow relationships among the members of a class [98]. Unlike other approaches, CMDG describes more types of relationships: read access relationships, write access relationship, call relationship and flow relationship. Zhou proposed the cohesion measure DRC using CMDG which he claims can measure the cohesiveness objectively. 74 Zhou’s recent work [97] emphasizes that special methods such as access methods, delegation methods, constructors and destructors have no influence on the cohesion of a class. Hence, they should be excluded from the abstract of a class. The cohesion measures will be masked if special methods are not excluded from the consideration. LCOM and their variants do not take into account the effects that the special methods may have on the values of cohesion measures. RCI does exclude some but not all special methods i.e., it excludes only constructors and access methods from the calculations. Only CBMC and DRC considers all types of special methods. While LCOM uses method similarity and RCI uses type and attribute reference, DRC uses dependence relationships. Both LCOM and RCI consider attribute reference and/or method invocation but only DRC considers flow dependences. DRC uses dependence relationships. It considers the direction of dependences between methods and attributes (read/write) as well as potential dependences. 5.3 Feature Envy Metric As discussed in Section 5.1 and Section 5.2, many software quality metrics have been developed but it is not an easy task to decide which one is suitable and can efficiently detect code smells. Despite a large number of existing software metrics, most of them are designed for software quality measurement and are not appropriate for code smell detection. At first, we intended to use both cohesion and coupling metrics to detect feature envy. The idea is to first calculate cohesiveness of a class. Then, if the cohesion is low, 75 Package default inventory inventory.gui Class Driver Driver.Controller Driver.AbstractDataAccess Driver.FileAccess Driver.DirectAccess AbstractOrderController Catalog Item Order OrderItem Warehouse ItemPanel OrderItemDialog OrderItemPanel OrderView OrderView.OrderListModel OrderView.CellRenderer DRC Cohesion (1) 0.15625 0 0.04082 0.08000 0 0.06250 0.03571 0.14063 0.03360 0.13889 0.09375 0.16327 0.20661 0.19008 0.10185 0 0.07813 LCOM* 0.2 0 0 0 0.5 0 0 0.10600 0 0.3000 0 0.33300 0.50000 0.56200 0.80400 0 0.77800 Table 5.1: Cohesion Measurements on Instructor’s Code the coupling metric for that class is computed to determine which class it is coupled with. Based on this idea, we have implemented Zhou’s DRC cohesion measure [98] because DRC collects and uses more information from a class. Considering that DRC uses such fine-grained information, we believe it will give more precise measures. However, our experiments have shown that DRC cohesion measure cannot handle inheritance very well. The cohesiveness of a class is very low if it uses most features from its superclasses. According to Briand’s unified framework [12], there are two approaches to compute metrics when inheritance is involved. We can either 1) ignore inheritance by excluding inherited members from the analysis, or 2) include inherited members in the analysis. Both approaches have been implemented and tested. The main problem is that DRC computes class cohesion based on the number 76 of members in the class. This overgeneralizes the problem. In a way, DRC is too pessimistic when involving inheritance. It favors classes that use features internally while penalizing classes that use inherited features. This metric indirectly discourages code reuse through inheritance which is one of the key features of object-oriented programming. Table 5.1 shows that many classes are not cohesive based on DRC measurements i.e., cohesion equals 0. All classes whose DRC cohesion are zero were further investigated and it is found that most classes only use features from their superclasses. For instance, OrderListModel is a subclass of java.swingx.AbstractListModel. It does not define any new fields and defines 3 methods: two of which are get methods and one method make a call to a method defined in its superclass. Preliminary results have also shown that it is not sufficient to look solely at the value of a class cohesiveness as it may be misleading. A high value is good but a low value does not necessarily means poor design. In addition to the cohesion measure, the CBO coupling measure by Chidamber and Kermerer was also implemented. Unfortunately, coupling measures, in general, can give you a rough idea about which classes are tightly coupled but they do not tell which part of the code causes the improper coupling. They do not pinpoint where feature envy occurs. Since cohesion and coupling metrics failed to locate feature envy, we found it necessary to develop a new metric. One of the reasons they are not suitable for the job could be because coupling and cohesion are computed at class level while feature 77 envy happens at method level. Even though we did not choose to adopt any discussed metrics in the implementation, many metrics are useful and used as a foundation for the new metric. Ideas we apply to the new metric are: 1. exclude special methods i.e., access methods and constructors. It has been reported by many that special methods would mask the real result [98]. 2. exclude inherited members from the analysis. The reason we opt to exclude inherited members is because inherited members are considered parts of the class. Including inherited members will needlessly complicate the analysis. 5.3.1 Internal Process The granularity of the current cohesion metrics is at the class level while Feature Envy needs a metric whose granularity is at the method level. The new metric is developed based on this nature. Feature envy happens when a method of a class make a lot of method calls to another class, which implies that it is more interested in the other class than the one it belongs to. With such characteristics, we find that in order to locate feature envy: 1. metric must be computed on every method of a class, 2. in each method, information about method calls must be collected The next step is to locate the source of the problem by finding which outside methods are called and which class they belong to. Calls are then grouped by object in order to determine which objects are called more often than others. Once we 78 identify the most frequently used object, we know where feature envy occurs. Such information is critical because removing feature envy requires moving the problematic code to its new home. For all named objects (i.e., fields, parameters and local variables) in each method, the number of method calls on such objects are counted. That number is then plugged into the formula in order to be normalized into a range of [0,1]. Normalization makes it easy to determine the severity of feature envy. We learned from existing metrics that the computed values can be vary and it is difficult to compare metric values if they are unbounded. In this formula, weight is given based on how many calls are made on that particular object. Let m be the number of calls on obj inside the method mtd, n be the total number of calls (on any object defined or visible) in the method mtd, w be the weight and x be the base where w, x ∈ (0, 1). The implementation in this work uses w = 0.7, x = 0.3. The discussion on the values chosen for w and x is provided in section ??. FeatureEnvy(obj, mtd) = w m + (1 − w)(1 − xm ) n The above formula consists of two parts. The first part of the formula computes the percentage of calls which represents how frequent obj ’s methods is used comparing to other objects. In other words, the first part calculates the severity of feature envy within the method. The higher the percentage the greater the value of the first term. The second part represents the significance of such an envy relative to those in other 79 methods. The higher the value of m, the less the value of xm which makes the second term higher. In other words, the second term is introduced to favor objects that are called on more frequently. For instance, consider m = n = 2 and m = n = 4. In both cases, 100% of calls in the method are on one receiver. However, the latter case should be given more attention because features of such an object are used more frequently. The weight w in this formula serve as how much magnitude you want to give the internal correlation against external correlation. This work uses w = 0.5, which means they are given equal significance. x serves as an exponent that maps the value into a [0,1] range. This work uses x = 0.5. After the values of w and x are chosen, we have to set the threshold or cut-off value for feature envy candidates that would be presented to the developer. The purpose of specifying the cut-off value is to show only feature envy that is considered “serious”. Each developer may have different opinions or views on the severity of a feature envy. More importantly, the value of the threshold is subjective. This tool uses 0.5 which means an object that were called on a lot i.e., at least one half of total number of calls outside the class will be reported to the users. Table 5.2 shows representative values for feature envy with respect to the number of method calls made inside a given method. Consider the code in Figure 5.1 which is a real example of students’ and instructor’s code from a UWM CS course. Two objects item and itemPanel are used in method updateItemPanel. After the feature envy metric is computed, we have the following results. 80 w 0.5 x 0.5 0.5 0.5 0.5 0.5 0.5 0.5 m 0 1 0 1 2 0 1 2 3 0 1 2 3 4 n 1 1 2 2 2 3 3 3 3 4 4 4 4 4 Feature Envy 0 0.75 0 0.5000 0.8750 0 0.4167 0.7083 0.9375 0 0.3750 0.6250 0.8125 0.9688 Table 5.2: Variables in the Metric private void updateItemPanel() { Item item = getItem(); int q = getQuantity(); if (item == null) { itemPanel.clear(); } else { itemPanel.setItem(item); int inStock = Warehouse.getInstance().getQuantity(item); itemPanel.setInstock(q <= inStock && 0 < inStock); } } Figure 5.1: Feature Envy Candidate CallSet(item, updateItemPanel) = {} CallSet( itemPanel, updateItemPanel) = {clear, setItem, setInStock} n=6 FeatureEnvy(item, updateItemPanel) = 0 FeatureEnvy( itemPanel, updateItemPanel) = 0.5(3/6) + 0.5(1 − 0.53 ) = 0.6875 81 5.3.2 Choosing w and x We perform a number of experiments on the values of w and x. At first, we started with w = x = 0.5. One obvious problem we found with this set of values is for the case of m = n = 1 which represents delegation. In such a case, the computed metric value is 0.75 which is considerably high. However, delegation is a legit method of sending messages between objects and should not be considered as feature envy. Delegation is one of the main contribution to the number of false positives. Therefore, the values of w and x need to be adjusted. Upon thorough investigations, we found that we gave too much significance on the external correlation. Each time the values of w and x are adjusted, the number of false positives is recorded. We choose those values by looking at the case when the least number of false positives is produced. After updating the values, the number of false reports is reduced by almost 80%. However, the values that we use in this work may not perform well with other sets of source code. We believe that the results may be better if we apply machine learning techniques in adjusting the values of w and x. 5.3.3 Incorporating an Analysis Unfortunately, looking at the computed value alone is insufficient. We have to determine if an object in question is written within a block of code. It is necessary to determine if an object is written within a block of code because if it is so, the object could be different at different points in the program which means that it is not really a feature envy. If the object is different than the last occurrence(s), we cannot 82 1: 2: 3: 4: 5: 6: 7: 8: public void setOrder(Order o) { if ( order != null) { order.deleteObserver(this); } order = o; order.addObserver(this); update(o, o); } Figure 5.2: Non Feature Envy Instance move the code because it is semantically wrong to combine activities on two different objects and make them a uniform operation for an arbitrary object. Doing so will cause the program to behave differently. Since the metric gathers information based on names not the pointer (or reference), it could not tell whether the object has been redefined. Further analysis is required to reduce the number of false positives. Let’s look at the code in Figure 5.2. The feature envy metric value of 0.7083 for order which seems like a good candidate for feature envy. However, if we look at the code, we will see that order is written on line 5. order before line 5 and order at and after line 5 are in fact different objects. Since they are different, they are not feature envy. CallSet( order, setOrder) = {deleteObserver, addObserver} n=3 FeatureEnvy( order, setOrder) = 0.7083 A less obvious example is previously discussed and shown in Figure 5.1. In this example, the metric gives 0.6875 which is considered significant (above the threshold). However, it cannot be assured if itemPanel remains the same objects throughout 83 class Inventory { public ItemPanel itemPanel; private void updateItemPanel() { Item item = getItem(); int q = getQuantity(); if (item == null) { itemPanel.clear(); } else { itemPanel.setItem(item); int inStock = Warehouse.getInstance().getQuantity(item); itemPanel.setInstock(q <= inStock && 0 < inStock); } ... } class ItemPanel { public Inventory i; public ItemPanel(Inventory j) { i = j; } public clear() { i. itemPanel = new ItemPanel(i); } ... } // itemPanel is reassigned!! Figure 5.3: Maybe Feature Envy the entire method by just looking at the code in class Inventory. Methods clear, setItem and setInstock could be changing itemPanel. Therefore, it is insufficient to perform just intraprocedural analysis. We need to expand our scope and perform further analysis on methods in questions. For instance, method ItemPanel.clear() could be assigning a new object to itemPanel as illustrated in Figure 5.3. 5.3.4 Metric Validation Many researchers have proposed desirable properties of software metrics. Particularly, for a metric to be useful, it must be valid, reliable, robust and practical. Our metric 84 will be discussed according to properties summarized by Henderson-Sellers [41]. Validity Henderson-Sellers defines two types of validity: internal and external. Internal validity addresses how well a measure captures the “meaning” of things that we want to measure. Furthermore, a new measure should correlate with the old one. It is evident that our measure relates to other existing measures. A class with many instances of feature envy implies that it is highly coupled with other classes. External validity relates to generalization issues. In other words, the metric must be generalized beyond the samples that have been measured. Generally, external validity cannot be experimentally determined and it can hardly be achieved. Our metric is originally designed for Java. It is not applicable to other programming language paradigms but it can be adapted for use in other object-oriented programming languages. Reliability A metric is reliable if it produces consistent results. Consistency involves stability and equivalence. Stability means it is deterministic, in other words produces the same results given the same input. In our case, provided that the same w and the same x are used on the same input, the metric will produce the same results. Moreover, two feature envy instances have equivalent level of severity, if the computed values are the same. 85 Robustness Tsai et al. [92] define robustness as the ability to tolerate incomplete information. Furthermore, the robustness is determined based on how well it can handle incorrect input. In this case, the information required for the metric is the source code. The only requirement is that the given source code must not have any compile errors. Our metric does not require the whole program so it is robust to some degree. Practicality The metric must be informative. Jones believes a useful metric should be language independent and applicable during the early stages of the development process. However, due to the nature of feature envy which happens after the implementation, it is impossible to come up with a metric that determines feature envy during the design phase. Feature envy can only be revealed after the code has been written. Furthermore, the metric should have the capability of prediction. Our metric also provide the flexibility of adjusting w and x depending on the nature of the applications. It provides a guideline from the semantic viewpoint rather than a mere count of something. 5.4 Summary A new metric for feature envy is discussed in this chapter. The values of our metric can be uniquely interpreted in terms of the severity of the problem. Since the values 86 computed by our metric are in the range of [0, 1], it is easy to compare a particular value with other values. We also explained that an analysis can be combined with the metric to improve the accuracy of the results. It is worthnoting that other research works on feature envy detection [94, 72]. Oliveto et al. introduces the concept of method friendships [72] which analyzes both structural and conceptual relationships between methods. Other works, though not related to feature envy detection, also attempt to retrieve semantic information from the source code. Some researchers use information retrieval technique called Latent Semantic Indexing to extract identifiers and comments [60]. Others use natural language processing techniques and introduce the LORM metric [28]. A work by Bavota et al. proposes a technique to extract class [7] but we believe that their approach can be modified and applied to detect feature envy, since their approach also includes cohesion metric. 87 Chapter 6 The Framework This chapter discusses the overall framework and the architecture of JCodeCanine. Some parts of the framework have already been developed by the Fluid1 group. We start the chapter by discussing the existing components, the architecture of the system, subcomponents of the system and the details of each module. 6.1 Existing Components There are two main components in this work: Fluid and Eclipse. The Fluid infrastructure is used mainly for program analysis and code transformations. The Eclipse framework is used as a front-end that interacts with the user. 1 Fluid is a project in collaboration of Carnegie Mellon University and University of WisconsinMilwaukee. It provides a tool to assure that the program follows the design intent. 88 6.1.1 Fluid Fluid provides a tool to assure that the program follows the programmer’s design intent. The developers can run different analyses on their programs. There are a number of analyses that this work uses which include Effects Analysis and Uniqueness Analysis. The analysis framework is set up in a way that a new analysis can be added easily and without too much hassle. Program analysis is usually done on an intermediate form that represents the program’s structure. The representations that are commonly used are graph and tree. Fluid provides tree-based analyses. The internal representation (IR) which used in Fluid consists of nodes and slots. A node can be used to represent many kinds of objects but this work refers to a node in an abstract syntax tree (AST). A slot can store a value or a reference to a node. A slot can be attached to a node by using an “attribute” or can be collected into a ”container”. For instance, a method declaration node has an attribute “name” that holds the name of the method and is associated with a container that holds references to other nodes (its children) in the AST e.g., a list of parameters, a return type and a method body. Every analysis in Fluid will be perform on the IR. However, since Fluid IR is an internal representation that the developers do not usually understand, we need a way to obtain the source code back from the IR. This process is called unparsing. After the transformations are performed on the IR i.e., refactorings, the unparser is used to obtain the source code which is then displayed to the user. 89 6.1.2 Eclipse Our requirement is to develop a tool that works inside an IDE. We choose Eclipse because it is one of the most popular IDEs for Java. It is also easy to extend via a plug-in which eliminates the need to develop the whole user interface from scratch. 6.1.3 Incompatibilities between Fluid and Eclipse There are a number of incompatibilities between Fluid and Eclipse which make the implementation difficult. One issue involves different representations of the Fluid Abstract Syntax Tree (FAST) and Eclipse Abstract Syntax Tree (EAST). The FAST is more fine-grained than the EAST. On the Fluid’s side, the Java source adapter has been implemented to address such a conflict. The Java source adapter, as its name implies, adapts the EAST into the FAST. It basically converts the abstract syntax tree obtained from Eclipse into a different abstract syntax tree with Fluid IR. The adapter allows us to perform different analyses on the Fluid side, since all analyses expect the FAST. The other and more serious issue is concerned with versioning. While Fluid is versioned, Eclipse is not. In other words, Fluid keeps track of changes made in different versions but there is no versioning system in Eclipse. To make them work together we need a mapping mechanism from non-version to version space and vice versa. Eclipse has no knowledge of which system’s version (under the Fluid’s context) it is working on. Hence, we need a bridge to administer the communication between Eclipse and Fluid. The bridge handles everything that involves Fluid versioning system. Its main duty is to keep Fluid and Eclipse synchronized on resource changes. 90 Fluid Versioned Fluid AST Eclipse Unversioned Eclipse AST Table 6.1: Fluid and Eclipse Incompatibilities 6.2 Implementation Section 6.1 describes several attributes of Fluid and Eclipse that pre-exist and serve as foundations of the current work. This section provides description of newly developed components and how they fit in the existing framework. We developed a tool called JCodeCanine which is an Eclipse plug-in. The main features of JCodeCanine is detecting code smells on Java programs, suggesting different refactorings to the developers and allowing them to apply the suggested refactorings that will remove such smells. Though the implemented tool is for Java programs, the ideas behind this work can be adapted for other object-oriented programming languages. 6.2.1 The Architecture JCodeCanine’s components can be divided into three groups depending on where the activities are taken place. The Eclipse group is the front-end which provides the user interface, interacts with the users and handles all user actions. The Fluid group contains back-end components that involve with IR nodes, versioning and analysis. The last group, the in-betweener, consists of those that provide the interconnections between Eclipse and Fluid. 91 • Eclipse’s side: Editor, User Action Handler • Fluid’s side: Code Smells Detector, Refactoring Manager, Annotation Suggester • In-betweener: Java Source Adapter, Bridge, Promise Parser, Unparser Figure 6.1 shows the architecture of JCodeCanine. The process starts when the developer invokes code smells detection through Eclipse. Eclipse parses the Java source file into the EAST. The EAST then gets adapted into the FAST by the Java source adapter. After the FAST is obtained, a new version is created. The code smell detector performs analysis on the FAST, marks the region of problematic code and returns the information in the form of Eclipse’s warning markers. Each marker is linked with the resolution for the problem i.e., refactorings. If the developer chooses to fix the code smell, the responsibility is shifted to the refactoring manager which performs the semantic check and takes care of the code transformations. Since the analyses and the transformations are done on the Fluid IR, we need the unparser to translate the IR nodes back to Java source code. Currently, the code smells detector is executed whenever a resource is changed which could be a double-edged sword. On one hand, it is automatic and convenient since it shows immediate results to the programmers. On the other hand, this approach may not be applicable for large software systems considering our resource consuming implementation. If the developer doesn’t change anything since the last time the detector is run, there is no need to re-compute analysis information. 92 Figure 6.1: Architecture of JCodeCanine 93 Figure 6.2: Process Cycle in JCodeCanine 94 6.2.2 Refactoring Manager In JCodeCanine, the programmer can refactor his code either through a quick fix after code smells are detected, or through the menu directly without checking for code smells. After a refactoring request is invoked, the source code is adapted into Fluid IR by the Java source adapter. The semantic preconditions for that particular refactoring are checked. If one of such conditions is not satisfied, the code will not be refactored. One distinction between this work and Eclipse’s refactoring is that semantics check are taken into consideration before attempting any refactoring. Using refactoring support in our tool will not introduce compile errors. Hence, we consider our approach to be true behavior preservation. Eclipse refactors the code first and lets the compiler catch any errors that may occur. In a way, Eclipse’s refactoring support is not behavior preserving. Each refactoring has an inverse operation e.g., Extract Method and Inline Method. In general, refactoring can be undone by applying its inverse refactoring. In contrast to other approaches, our refactoring tool utilizes versioning in Fluid for undoing/redoing refactoring. When a refactoring is applied, the program snapshot is captured as a version in Fluid. Hence, undoing/redoing is as easy as switching back and forth between versions or traversing up and down the version tree. The bridge then informs Eclipse of the current version that it is working on. One benefit gained from using a versioning model for undo/redo is that the semantic condition check that needs to be performed before any refactoring can be skipped. Unfortunately, the Fluid framework is not small. Capturing a version whenever the code is refactored 95 can be space intensive. The trade-offs between time and space will have to be weighed out by the developer. 6.2.3 Code Smells Detector Code smells detector acts as a sniffing dog. It checks and determines which part of the code is stinky using analyses discussed in Chapter 4. In this implementation, one detector is developed for each code smell. Basically, the detectors are invoked in a sequence. Each detector analyzes the IR nodes and when it detects a code smell, the problematic nodes are marked. The detector keeps track of those marked nodes. When all code smells are detected, the IR is unparsed into Java source code. The region of source code that is from the marked IR nodes is then highlighted with a “marker”. 6.2.4 Code Smells Resolution As discussed in Chapter 4, each code smell is resolvable using one refactoring or a series of refactorings. The implementation follows that same idea i.e., each code smell is linked with a resolution. Basically, the code smells resolution is the process that attempts to remove the detected code smells by means of refactorings. The refactoring process is then taken care of by the refactoring manager as usual. 96 6.2.5 Annotation Suggester The Fluid analysis framework requires the program to be annotated and such a process can be overwhelming to any developer. The annotation suggester relieves some burden off the programmer by giving an advice of what kind of annotations is required. Our annotation suggester is preliminary. It only considers a portion of code in question and suggests annotations that allow a specific analysis to be executed. For instance, if uniqueness analysis is being executed, it will only suggest those that are related to uniqueness analysis. If effects analysis is later executed on the same method, the programmer is required to add effect annotations. This on-demand approach could be unpleasant since the programmer may spend most of their time annotating the code or worse, they could end up annotating the whole program. Specifically, if a message chain exists, it is necessary that all methods in the chain are checked which means that they must be annotated. To avoid this nuisance, we only check the immediate message and assume that other methods in the chain do not violate the conditions. We are aware that it would be beneficial to adopt “annotation inference” where annotations are automatically derived from the code. However, annotation inference is beyond the scope of this research. 6.3 JCodeCanine’s Key Features This section demonstrates various scenarios when using JCodeCanine. First, we show various code smells detected by JCodeCanine and how to automatically remove code 97 smells with suggested refactorings. Then, we exhibit the situation when refactoring is invoked by the developer. 6.3.1 Code Smells Detection and Resolution Code smell detectors are executed when a resource changes. Each code smell detected is shown as a warning message. The tool provides the following information for each smell: • type of code smell, • file name, and • location of code smell in the source (line number). Figure 6.3 demonstrates how the tool displays the detected code smells to the user. Each marker links to the source location. The problematic code is highlighted with a yellow line which acts as a warning sign to the developer. In addition to the general information about the code smell, each smell marker is linked to an automatic resolution. The user has an option to remove the detected code smell through a quick fix (see Figure 6.5). By invoking a quick fix, the suggested refactorings are automatically applied to the code. 6.3.2 Stand Alone Refactoring Apart from an automated code smells resolution, developers also have an option to initiate and perform refactoring through the menu. 98 Figure 6.3: A Snapshot of Feature Envy Detected Figure 6.4: A Snapshot of Data Class Detected 99 Figure 6.5: Apply Refactoring through Quick Fix After a refactoring request is initiated, the tool checks the semantic conditions behind the scene. Figure 6.6 shows the situation when the chosen refactoring cannot be applied because it violates our behavior preserving conditions. Particularly, the programmer wants to apply rename the bought() method in the IPhone class to updateStock(). However, apply such a refactoring will create an overloading method and change the behavior of the updateStock() method from increasing the quantity by one to decreasing it by one. JCodeCanine catches that this serious side effect and report the issue to the programmer. 100 public class Product { void updateStock() { ++quantity; } private int quantity; } public class IPhone extends Product { void bought() { --quantity; } } Figure 6.6: Semantic Conditions Violated if bought() is Renamed to updateStock() 6.3.3 Annotation Suggestions While the Fluid analysis framework requires programs to be annotated, JCodeCanine will try its best in performing the analysis with available information. If it does not have enough information, it will ask the programmer to annotate their code. JCodeCanine does have the ability to suggest annotations but it may not comply with the design intent. The developer is responsible for providing correct annotations in order to obtain the intended results. Figure 6.7 shows the portion of code that annotation suggester will be activated when the developer wants to move a field warrantyExpirationLeftFrontTire to a component class Tire. Since this particular refactoring requires uniqueness analysis and no annotation has been declared, JCodeCanine advices the developer to add an annotation @unshared to the field which indicates that such a field is unique and cannot be shared among objects. After the expected annotation is added, the semantic condition check is resumed by performing a uniqueness analysis. If it passes 101 public class Automobile { Engine autoEngine; int numOfPassenger; Tire leftFrontTire; Tire rightFrontTire; Tire leftRearTire; Tire rightRearTire; WarrantyInfo warrantyExpirationLeftFrontTire; WarrantyInfo warrantyExpirationRightFrontTire; WarrantyInfo warrantyExpirationLeftRearTire; WarrantyInfo warrantyExpirationRightRearTire; ... } public class Tire { ... } Figure 6.7: Scenario where Annotation Suggester is Invoked the uniqueness analysis i.e., the field warrantyExpirationLeftFrontTire is unique, then the field will be moved to the destination class which is the T ire class. 6.4 Summary The main framework of JCodeCanine is discussed in this chapter. JCodeCanine is implemented based on existing components which are Fluid and Eclipse. The Fluid’s infrastructure is used as the main component for back-end process and Eclipse is used mainly for the user interface. Though Fluid and Eclipse are incompatible in various aspects, the in-betweeners like the bridge and Java source adapter provide the harness between the front and the back end. The key modules i.e., Code Smells Detector, Refactoring Manager and related analysis are developed on the Fluid’s side to show that our work is sound. The nature of our Fluid analysis framework is that it requires 102 the developers to annotate their design intents. JCodeCanine does have the ability to suggest annotations but it currently is considered experimental. It is possible to write a more sophisticated annotation suggester or to implement a mechanism for annotation inference which could reduce the burden on the developers but such a system is beyond the scope of this work. 103 Chapter 7 Empirical Results Murphy-Hill and Black present seven habits that an effective smell detector should possess [67]. Such seven habits include: • Availability: A smell detector should not require much effort from the programmer. Smell information should be made available to the programmer as soon as possible. • Unobtrusiveness: A smell detection tool should perform its job without interfering the programmer while he is coding. • Context-Sensitivity: A smell detector should only point out smells that are related to the current programming context. • Scalability: A tool should not overwhlem the programmer with smell information if a significant number of smells is identified. 104 • Relationality: A smell detector should show relationships between code fragments that contribute to code smells. • Expressiveness: A smell detection tool should provide explanation on why the smell exists. Since our approach is to develop and integrate code smell detection tool with Eclipse IDE, JCodeCanine is context-sensitive, relational, and expressive. The smells found by JCodeCanine are reported with details in the problem view. Each message in the problem view is also linked to the code where the programmer can double click and see the source of the problem in the editor. All code fragments that cause code smells are also underlined in the editor view. According to Murphy-Hill and Black’s definitions, JCodeCanine is not available and obtrusive because the analysis will be performed only when the programmer chooses to start the smell detector. When the smell detector is running, the programmer is not able to do any coding. Code editing is blocked during this process. In our opinion, JCodeCanine is not so scalable because it shows all smells that it detects. We believe that allowing the programmer to apply filter to the smells list will help on the scalability issue. However, this issue is yet to be explored. After we have developed code smells detection with refactoring support tool for Java program, we evaluate our approach by determining the soundness of the code smells detection. We also look at the number of suggested refactorings and determine if they are sound both syntactically and semantically. We perform tests on various sizes of code. Our test cases include: 105 • UWM CS552 homeworks written by students and the instructor which include homework 4, 5, 6 and 7, • java.util package (JDK 1.4.2), • java.lang package, and • fluid.util package. The size of each CS552 homework is about 500 to 3K lines of code. Each homework instance is tested separately. However, we aggregate the results by homework for preciseness. The java.util package consists of 123 classes and 13K lines of code. The java.lang package consists of 100 classes with 10K lines of code. The fluid.util package contains 106 classes with 50K lines of code. 7.1 Code Smells In order to determine how well the code smells detection component performs, we look at the number of code smells detected. The number of false positives is also recorded to further reflect the accuracy of our approach. False positives, in this context, are the legitimate code that are detected as code smells. The comparison of these two numbers give us an idea of how accurate our algorithms are. As shown in Table 7.1 that the average accuracy of detection all four code smells is at 54%. The results from Table 7.1 show that our duplicated code detection cannot find any code clones. We have investigated on why its performance is rather poor and found 106 Code Smell Duplicated Code Feature Envy Data Class Switch Statement Detected 13 24 5 37 False Positives 4 2 0 30 Accuracy (%) 69% 92% 100% 19% Table 7.1: Numbers of Code Smells Detected, False Positives and Accuracy out that the homework code instances do not contain any duplicated code. Regarding java.lang and java.util packages, we cannot go through the code thoroughly due to their sizes. However, based on our rough observation, both java packages are quite well written and do not contain duplication structurally. We also performed tests on our duplicated code detection by creating new packages with known duplicates and our detector can correctly identify those clones. The limitation of our detection algorithm is similar to any AST-based clone detections in such a way that it cannot detect clones which are from statement reordering. Regarding false positives for feature envy, data class, and switch statements, the tool reports 46% overall false positives: 8% false positives for feature envy, no false positives for data class and 81% false positives for switch statements. We further determine the reason why the tool returns such high percentages of false positives. This process has to be done manually. After looking at each and every instance, we found that: • Our work can successfully detect feature envy. • Falsely detected data class can be categorized into: 1) a subclass of Exception and Error classes. 2) a real data class by the programmer’s intents e.g. 107 java.util.CurrencyData. • Poor use of switch statements is very hard to detect in general. We need to find a new heuristic algorithm that can correctly determine the poor use of switch statements without introducing a lot of false positives. The results, though not as satisfying as expected, are very informative. Currently, the algorithms for data class and switch statements are syntax-based. We speculate that program’s semantic analysis may be able to reduce the number of false positives. However, such an idea has yet to be investigated in the future. Another aspect for evaluating the accuracy is testing for false negatives. False negatives in this context are real code smells that are left undetected by the system. In order to test for false negatives, test programs with known code smells are created. Then, we run our detectors on those test programs. The detectors performed really well as it can locate all instances of code smells. No false negatives are found. 7.2 Refactorings In this section, we are particularly interested in seeing the quality of refactorings suggested by our system. Here, the number of suggested refactorings with respect to syntactic correctness and semantic preservation is measured. By looking at these numbers, we are able to determine whether behavior preservation can be realistically achieved in the automated refactoring tool and what the difficulties are. The three categories of refactorings we have measured are listed below. 108 Project hw4 hw5 hw6 hw7 JCodeCanine fluid-eclipse fluid Type 1 4 5 9 13 15 20 58 Type 2 4 3 9 11 15 16 51 Type 3 2 2 6 9 10 9 34 Table 7.2: Number of Suggested Refactorings 1. total number of refactorings suggested by our code smells detector (Type 1) 2. refactorings from 1) that do not break compilation (Type 2) 3. refactorings from 2) that are semantics-preserving. (Type 3) Type 2 refactorings are syntactically sound. They are refactorings that will not cause compile errors in general. However, behavior preservation is not guaranteed if applying type 2 refactorings as is. On the other hand, type 3 refactorings are both syntactically and semantically sound. This type of refactoring are safe to be applied and will neither break the code nor change the program’s behavior. Table 7.2 shows the breakdown number of each type of refactorings that are suggested by JCodeCanine. Out of total refactorings suggested by JCodeCanine, 92% are safe syntactically and 28% are safe syntactically and semantically. Ideally, we would want the number of type 1, type 2 and type 3 refactorings suggested by our system to be equal. However, such figures are reasonable since not all code smell detection algorithms include semantic analysis. After examining those suggested refactorings, we also notice that one of the obstacles is the existing structure of the program especially 109 variable and method naming. The program may need to be restructured or refactored first in order for the suggested refactoring to be applied correctly. Furthermore, a number of suggested refactorings that are unsound or unsafe are attributed by the existence of false positives in code smells detection. 7.3 Code Qualities In this section, we analyzes the performance of our tool by comparing the quality of the code before and after running our tools on each test package. In order to make sure that the program semantics are well-preserved, we choose to apply only type 3 refactorings (syntactically and semantically sound refactorings as discussed in Section 7.2. The metrics used to measure code qualities in this research are mostly from Chidamber and Kemerer’s work [14] as they are pioneers in software quality metrics. They have proposed a set of static metrics that are designed to evaluate the quality of an object-oriented design. Their metrics are widely known and used in software development process. This work uses some of their metrics for measurements which are: 1. Weighted Method per Class (WMC) 2. Depth of Inheritance Tree (DIT) 3. Number of Children (NOC) 4. Afferent Coupling (Ca) 110 5. Efferent Coupling (Ce) 6. Lack of Cohesion between Methods (LCOM*) 7. McCabe’s Cyclomatic Complexity (CCN) A short description of each metric is provided in Appendix A. Table 7.3 shows the objectives for each software metric according to Rosenberg [77]. For some metrics, the lower number the better; however, some metrics are considered trade-offs between readability and complexity. We measured the software quality before and after applying suggested refactorings using Chidamber and Kermerer object-oriented metrics and the comparisons are shown in Table 7.4, Table 7.5 and Table 7.6. Values presented in these tables are the average. The metric calculations are from several opensource Eclipse plug-ins i.e., metrics.sourceforge.net [83], Google’s CodePro Analytix [36] and Analyst4j [16] which provide an extensive numbers of software quality metrics. Note that this work only considers and analyzes metrics that are related to the code smells discussed in Chapter 4; hence, we do not discuss all metrics here. We also summarize the impacts of refactorings on the value of each metric where + means positive impact, - means negative impact and = means no improvement after refactorings have been applied in Table 7.7. Our results show that, in most cases, applying refactorings make positive impacts with respect to object-oriented metrics. Refactorings that make negative impacts include Extract Method as it increases the number of methods in a class; therefore, WMC is increased after refactoring. Other metrics that receive negative impacts include DIT 111 Category Complexity Size Size Coupling Coupling Cohesion Complexity Metric WMC DIT NOC Ca Ce LCOM* CCN Granularity Class Class Class Class Class Class Method Objective Low Trade-Off Trade-Off Low Low Low Low Table 7.3: Objectives for Different Metrics Project hw4 hw5 hw6 hw7 JCodeCanine fluid-eclipse fluid LOC 558 1,167 1,870 2,632 9,729 17,431 223,041 WMC Before After 14.500 14.500 12.941 12.801 9.914 9.080 10.022 9.984 10.830 10.992 37.763 37.940 18.578 17.659 Ca Before After 1.333 1.000 2.000 1.667 4.800 4.600 5.333 5.000 8.000 7.677 8.615 8.512 122.012 118.236 Ce Before After 0.500 1.000 1.667 1.333 4.400 4.200 4.000 3.750 6.500 6.000 5.077 4.922 28.549 25.489 Table 7.4: Comparison of Software Metric Measurements: Low (1) and NOC. As we have discussed earlier that the values of these two metrics are tradeoffs between readability and complexity. The higher values do not necessarily mean poor quality. It depends on what the developers actually focus on. 7.4 Discussion Our results concur with Du Bois et al. [10] that code smells with respect to improve coupling and cohesion are sparse and difficult to find. In addition, we find that most code smells are difficult to find in general. The fact that our metric can detect a 112 Project LOC hw4 hw5 hw6 hw7 JCodeCanine fluid-eclipse fluid 558 1,167 1,870 2,632 9,729 17,431 223,041 LCOM* Before After 0.214 0.210 0.271 0.271 0.204 0.204 0.224 0.224 0.161 0.153 0.215 0.207 0.115 0.108 CCN Before After 2.000 2.000 1.583 1.583 1.684 1.684 1.733 1.667 1.880 1.760 2.721 2.679 1.834 1.710 Table 7.5: Comparison of Software Metric Measurements: Low (2) Project LOC hw4 hw5 hw6 hw7 JCodeCanine fluid-eclipse fluid 558 1,167 1,870 2,632 9,729 17,431 223,041 DIT Before After 3.250 3.250 2.941 2.941 3.029 3.029 3.022 3.022 2.415 2.502 1.753 1.753 3.904 3.972 NOC Before After 0 0 0.118 0.118 0.371 0.371 0.5 0.5 0.279 0.289 0.280 0.280 0.780 0.792 Table 7.6: Comparison of Software Metric Measurements: Trade-off Project hw4 hw5 hw6 hw7 JCodeCanine fluid-eclipse fluid WMC = +1.082% +8.412% +0.379% -1.496% -0.469% +4.947% DIT = = = = -3.602% = -1.742% NOC = = = = -3.584% = -1.538% Ca +24.981% +16.650% +4.167% +6.244% +4.038% +1.196% +3.095% Ce +50.000% +20.036% +4.545% +6.250% +7.692% +3.053% +10.718% LCOM* +1.869% = = = +4.969% +3.721% +6.087% CCN = = = +3.808% +6.383% +1.544% +6.761% Table 7.7: Impacts on Software Quality (+ Positive, - Negative, = No impact) 113 number of real feature envy instances is very satisfactory. While measurements are done in different aspects i.e., size, complexity, coupling and cohesion, we focus on values of coupling and cohesion measures as they are the key of object-oriented programming. Results show that coupling measurements decrease while cohesion measurements increase after applying suggested refactorings. Even though the improvements are insignificant, it proves that our approach works and is not trivial. Furthermore, our work improves the design to some extents. Such a claim is made based on tests and an in-depth investigation on our home-brewed application. The reason that we take this approach is because reasoning a program is difficult and time consuming. With an in-house project, the code’s intent is clear to us; hence, it is easier to analyze the results. Nonetheless, we found that, in some cases, the approximation for the analysis does not perform well as our analysis tends to be restricted and too conservative. Any changes that could change or modify the behavior of the program will be disregarded. Further study needs to be done in order to provide more flexibility while preserving the program’s behavior which is the main objective of this work. Balancing between these two extremes is a challenge. The following subsections discuss how each code smell affects the software metrics according to our findings. 114 7.4.1 Duplicated Code Intuitively, removing code clones will reduce the number of lines of source code. As discussed in Chapter 4 that in object-oriented programming, clones at different locations must be handled differently. Clones in the same class hierarchy can appear in the same method or in sibling classes. Removing such clones will reduce LOC. However, if the clones are found in two or more unrelated classes where an abstract superclass is needed, LOC and NOC measurements will be increased. Removing duplicated code affects the size-related measures. At this point, it is still obscure whether duplicated code removal has effects on coupling and cohesion measures. 7.4.2 Feature Envy It is known that feature envy is not desirable in object-oriented programs. Feature envy removal is an attempt to reduce bad coupling. Theoretically, the Ca and Ce metrics should be lower once feature envy instances are removed as they measure the coupling. Our findings are indifferent as the measurements from both metrics are decreased. Furthermore, we also found that remove a case of Feature Envy indirectly increases the class cohesion since the value of LCOM* is lower. 7.4.3 Data Class Since the number of data classes detected by our tool is not significant, we ran tests on an ongoing software project with 57K lines of code and 375 classes. Our tool found a number of data classes in this project. The result is then shared with the software 115 developers. They confirmed that some classes were incomplete and some classes were no longer necessary. After applying refactorings on unwanted data classes, the number of classes (NOC) decreased and in the case when the data class inherits from other class, the depth of inheritance tree (DIT) was also reduced. We also noticed that there was a slight improvement in terms of coupling and cohesion when removing a data class. 7.4.4 Switch Statement Removing switch statement does not have much improvements on the static software quality metrics. On the contrary, the quality appears to be worse when using existing measurements. Since a wrong use of switch statement is usually resolved by creating an abstract superclass, removing a switch statement this way increases the number of classes (NOC) as well as the depth of inheritance tree (DIT). There is no static metric that provides measurements of polymorphism, since polymorphism by its nature can only be observed and measured at run-time. Approaches to dynamically measure polymorphism include 1) calculating the polymorphic behavior index which analyzes internal and external reuse [15], or 2) calculating the number of potentially polymorphic instructions, the number of receiver types (receiver polymorphism) and the number of different target methods (target polymorphism) [23]. 116 7.5 Summary The evaluation is carried out with respect to the detection accuracy, behavior preservation and the code qualities. In regards to the accuracy of code smell detection algorithms, our system reports a number of false positives. We believe that it is because our analyses are somewhat conservative. However, since this work concerns about behavior preservation, a warning message does not hurt the code because the developer will have a chance to inspect the detected code smells. To further evaluate the accuracy of the system, programs with known instances of code smells are also tested and there are no false negatives i.e., all known code smells are detected by JCodeCanine. Our results show how semantic preservation should be taken into consideration whenever changes are applied to the existing software system. Though the change does not introduce any compile errors, the developers cannot take any changes for granted. Unsafe refactoring is harmful because tracing for semantic errors or behavioral changes is very difficult. Furthermore, the results also show that our system helps improve internal code qualities to some extent. It could detect code smells and suggest proper refactorings. Even though the improvements are not significant, they are promising. We also found that some metrics are too vague and could not provide clear quality measure for object-oriented programs. In fact, applying some refactorings could make the quality worse according to some software metrics. 117 Chapter 8 Conclusion Previous work has introduced many approaches to find refactorings [49, 91, 19]. Some work mentioned the thoughts of combining code smells detection with refactoring tool but none has actually implemented it. Many researchers rely on the metrics alone to detect code smells or find refactoring opportunities. Our work has shown that an analysis can be used in addition to metrics which results in a more precise result. Not only does this dissertation introduce a metric for feature envy, it also proposes a novel approach by demonstrating how an analysis can be integrated into a metric which allows us to obtain a measurement from the semantic viewpoint. This work proposes and develops a framework that combines the processes of identifying and applying refactorings. Such a framework allows information from analysis to be reused and avoid recomputing any information we already know. Therefore, it helps improve the efficiency of the overall system. 118 This work shows that integrating the two processes give us more satisfiable results. It forces us to look at the problem in a big picture. Program analysis plays a crucial part in this work. Even though JCanine only supports Java code, the idea of integrating refactorings with code smells detection can be applied to other object-oriented programming languages. Last but not least, this research has contributed: • analyses required to check semantic preconditions for the chosen refactorings. • analyses required to detect each code smell discussed in this thesis. • relationships between code smells and refactorings (in terms of analysis used) Our current implementation can be improved in many ways: • Persistence: This work does not use persistence. However, Fluid has a mechanism where versioned information can be stored and re-loaded. Regenerating versioning information is space intensive. The system would be more efficient with persistence. • Incremental Binding: With incremental binding, the binding information does not need to be recomputed and can be derived between versions. • Incremental IR Updates: Daniel Graves [38] implemented an incremental updater which allows us to reuse some existing nodes. Unlike the traditional method where the whole FAST is regenerated everytime the source is adapted into IR nodes, an existing FAST is incrementally updated from an EAST. New 119 IR nodes are generated only when necessary. When comparing general updater’s and incremental updater’s memory usage, differences of 72% - 78% are observed. Incremental updater is significantly more efficient than general updater. • Unparser : The unparser does not pretty print the code. It could be improved so that the user’s code formatting is preserved. Preserving the original code formatting after unparsing the internal representation is quite difficult in general. Other issues that are still open for future research include: 1. Adaptive Thresholds: Since different types of applications may require different thresholds for the metric, it is plausible to adapt machine learning techniques that could assist us in choosing a more suitable threshold values [53, 57]. 2. Dynamic Measures for Semantic Information: Since our main concern is to obtain quick analysis on the source code, analyses in this work are structuralbased. Using only structural analysis is found to be insufficient if accuracy is the goal. We believe that incorporating our approach with dynamic semantic metrics will yield better results. However, the main challenge is how to balance the accuracy with the overhead introduced by dynamic metrics or measurements. 3. Annotation Inference: Currently, the programmers are required to annotate their programs which can be burdensome. It would be more user-friendly, if annotations are automatically inferred from the source code. 120 4. Performance Evaluation for Refactored Programs: It would be interesting to see how refactorings affect the program performance and whether better designs comes with the sake of the performance. 121 Appendix A Software Metrics Depth of Inheritance Tree (DIT) is the maximum length from the node to the root of the tree. Deeper trees constitute greater design complexity, since more methods and classes are involved. Number of Children (NOC) is number of immediate subclasses subordinated to a class in the class hierarchy. The greater the number of children, the greater the reuse. However, if a class has a large number of children, it may be a case of misuse of subclassing. Weighted Methods per Class (WMC) calculates the sum of cyclomatic complexity of methods for a class. A low WMC indicates high polymorphism while a high WMC signifies a complex class. Afferent Coupling (Ca) computes the number of classes from other packages that depend on classes in the analyzed package (incoming dependencies). 122 Efferent Coupling (Ce) counts the number of types of the analyzed package depending types from other packages (outgoing dependencies). Lack of Cohesion in Methods (LCOM*) measures the relative disparateness of methods in the class. Cohesiveness of methods within a class is desirable because it promotes encapsulation. The measurement of disparateness of methods helps identify design flaws. Furthermore, lack of cohesion implies classes should probably be split into two or more subclasses. Many researchers define variants of the original LCOM to overcome the problem with the original metric from Chidamber and Kermerer. The measurements in this section is one variant of LCOM by Henderson-sellers [41], also known as LCOM*. McCabe’s Cyclomatic Complexity measures a number of linearly-independent paths through a program module [62]. McCabe suggested that the cyclomatic complexity should not be greater than 10. 123 Appendix B Case Studies The code used in this section is written by students and instructor in CS 552 class for their homeworks. The tool is run on students’ code and following is our findings in the early stage of testing. B.1 Definitely a Feature Envy This is the case when we can determine that the suspicious code is indeed feature envy without looking at other parts of the code. Figure B.1 illustrates an instance of feature envy where the programmer should consider moving index to the class that the put method is defined in. 124 public Object put(Object key, Object value) { if (!value instanceof Item) { throw new IllegalArgumentException(‘‘...’’); } Object old super.put(key, value); if (old != null) index.remove(old); index.add(value); return old; } Figure B.1: Feature Envy Instance B.2 Calls on Objects of the Interface Type Calls on object of the interface type are complicated and difficult to handle. Though an interface is a type just like a class is, they are different. Unlike a class, an interface only defines and never implements methods. Classes that implement the interface are responsible for implementing the methods defined by the interface. With this nature of the interface, we cannot move the implementation into the interface. The approach used in the previous section cannot be used here. One way to handle this case is to convert the interface to an abstract superclass. Since it has become a class, the implementation can be moved into it. However, these steps cannot always be done because there could be more than one classes that implement this interface. If every class that implements the interface is on the same hierarchy, we are home free. We could find the least common superclass, then make the interface an abstract class below it. Nonetheless, if the interface is implemented by classes on different inheritance hierarchies. We can find the least common superclass then create an abstract superclass below it. However, if the least common superclass is Object, it may be a good idea to leave this feature envy alone. Trying to remove 125 this feature envy could make the design even worse. B.3 Calls on Objects of Library Types Generally, in order to make a feature envy disappear, a region of code should be moved to a new home that is the class of the receiver. In other words, Move refactoring has to be applied. If the results from the metric implicate that the feature is more coupled to the library class, theoretically, such a feature needs to be moved. If most method calls are on an object with type defined library, there is nothing that we can do. We cannot change the library. According to feature envy metric discussed in section 5.3, the code smells detector obtains F eatureEnvy(add, warehouse) = 0.851851 which is considered a feature envy. In order to fix this feature envy, the add method has to be moved to the HashMap class. However, we cannot move this method to HashMap class, since HashMap is in java.util package which is not editable. Figure B.2 illustrates the discussed situation. One way to work around this obstacle is to avoid direct use of library classes. This can be done by creating a class that extends the library class and use the newly defined class instead. This approach could help reduce the number of feature envy because we have control over such a class. Unlike dealing with library classes, there is no restriction with the user-defined class. Doing so allows us to move features between classes freely. The workaround is shown in Figure B.3. 126 public class Warehouse { HashMap warehouse = new HashMap(); void add(Item objItem, Integer objQuantity) { assert (objItem != null); if ( warehouse.containsKey(objItem)) warehouse.put(objItem, new Integer(objQuantity.intValue() + ((Integer) warehouse.get(objItem)).intValue())); else warehouse.put(objItem, objQuantity); } } Figure B.2: Feature Envy on Objects of Library Type public class WarehouseStorage extends HashMap { public void addItem(Item objItem, Integer objQuantity) { if (containsKey(objItem)) put(objItem, new Integer(objQuantity.intValue() + ((Integer)get(objItem)).intValue())); else put(objItem, objQuantity); } } public class Warehouse { WarehouseStorage warehouse = new WarehouseStorage(); void add(Item objItem, Integer objQuantity) { assert (objItem != null); warehouse.addItem(objItem, objQuantity); } } Figure B.3: Indirect Usage of Library Class B.4 May-be a Feature Envy This case happens when we cannot really say if it is a feature envy without looking at other parts of the code. This is when analysis comes into play. Consider the code in Figure B.4. Line numbers 5, 7, and 9 are flagged for feature envy candidate because itemPanel is called on three times (out of 5 total method calls). It seems to be a feature envy. However, with variable inStock defined on line 8, we cannot safely extract and move this portion of code to the ItemPanel class unless we are certain that there 127 1: private void updateItemPanel() { 2: Item item = getItem(); 3: int q = getQuantity(); 4: if (item == null) { 5: itemPanel.clear(); 6: } else { 7: itemPanel.setItem(item); 8: int inStock = Warehouse.getInstance().getQuantity(item); itemPanel.setInstock(q <= inStock && 0 < inStock); 9: 10: } 11: } Figure B.4: May-be a Feature Envy are no effects on itemPanel from Warehouse.getInstance().getQuantity(item). Therefore, the effects analysis needs to be performed. If there are effects on itemPanel, the code cannot be moved since itemPanel at line 7 and itemPanel at line 9 are in fact, different objects. On the other hand, if there is no write effect, it is safe to extract line 4-10 and move a newly extracted method to ItemPanel. B.5 Exception Class Many data classes that we found are exception classes. In fact, our earlier version of Data Class Detector reported 82 data classes in the java.util package, all of which are subclasses of the Exception class. It is reasonable that exceptions do not perform many operations. Based on this reason, the tool ignores data classes that extend Exception. An example of Exception subclass detected as a data class is shown in Figure B.5. 128 public class ProjectException() extends Exception { public ProjectException(String msg) { super(msg); } } Figure B.5: Exception Subclass Detected as a Data Class B.6 Switch Statement As discussed in Section 4.4, finding a switch statement in the code is not difficult. The real challenge how to determine which switch statement is bad. Some switch statements are legitimate and are not used in place of polymorphism. BIBLIOGRAPHY Bibliography [1] Giuliano Antonioil, Umberto Villano, Ettore Merlo, and Massimiliano Di Penta. Analyzing cloning evolution in the linux kernel. Information & Software Technology, 44(13):755–765, 2002. [2] Aqris. RefactorIt. http://www.refactorit.com, 2001. [3] Cyrille Artho and Armin Biere. Applying static analysis to large-scale multithreaded Java programs. In Proceedings of the 13th Australian Software Engineering Conference (ASWEC 2001), pages 68–75. 2001. [4] B. S. Baker. On finding duplication and near-duplication in large software systems. In WCRE ’95: Proceedings of the Second Working Conference on Reverse Engineering, page 86. IEEE Computer Society, Washington, DC, USA, 1995. [5] Henry G. Baker. ‘Use-once’ variables and linear objects—storage management, reflection and multi-threading. ACM SIGPLAN Notices, 30(1):45–52, January 1995. [6] Ittai Balaban, Frank Tip, and Robert Fuhrer. Refactoring support for class library migration. In OOPSLA’05 Conference Proceedings—Object-Oriented Programming Systems, Languages and Applications, San Diego, California, USA, October 16–20, ACM SIGPLAN Notices, 40(11):265–279, October 2005. [7] Gabriele Bavota, Andrea De Lucia, Andrian Marcus, and Rocco Oliveto. A twostep technique for extract class refactoring. In Proceedings of the IEEE/ACM international conference on Automated software engineering, ASE ’10, pages 151– 154. ACM, New York, NY, USA, 2010. [8] Ira D. Baxter, Andrew Yahin, Leonado Moura, Marcelo Sant’Anna, and Lorraine Bier. Clone detection using abstract syntax trees. In Proceedings of the International Conference on Software Maintenance (ICSM ’98), Bethesda, Maryland, USA, November 16–20, pages 368–377. IEEE Computer Society, Los Alamitos, California, November 1998. 129 BIBLIOGRAPHY [9] James M. Bieman and Byung-Kyoo Kang. Measuring design-level cohesion. Software Engineering, 24(2):111–124, 1998. [10] Bart Du Bois, Serge Demeyer, and Jan Verelst. Refactoring improving coupling and cohesion of existing code. In Eleventh Working Conference on Reverse Engineering, Delft, Netherlands, November 8–November12, pages 144–151. IEEE Computer Society, November 2004. [11] John Boyland. Alias burying: Unique variables without destructive reads. Software Practice and Experience, 31(6):533–553, May 2001. [12] Lionel C. Briand, John W. Daly, and Jürgen K. Wüst. A unified framework for cohesion measurement in object-oriented systems. Empirical Software Engineering, 3(1):65–117, 1998. [13] Lionel C. Briand, S. Morasca, and V. R. Basili. Defining and validating measures for object-based high-level design. IEEE Transactions on Software Engineering, 25(5):722–743, 1999. [14] Shyam R. Chidamber and Chris F. Kemerer. A metrics suite for object oriented design. IEEE Transactions on Software Engineering, 20(6):476–493, 1994. [15] Kelvin H. T. Choi and Ewan Tempero. Dynamic measurement of polymorphism. In Proceedings of the thirtieth Australasian conference on Computer science Volume 62, ACSC ’07, pages 211–220. Australian Computer Society, Inc., Darlinghurst, Australia, Australia, 2007. [16] CodeSWAT. Analyst4j. http://www.codeswat.com/cswat/index.php? option=com_content&task=view&id=43&Itemid=63, 2012. [17] Jeffrey Dean, David Grove, and Craig Chambers. Optimization of object-oriented programs using static class hierarchy analysis. In ECOOP ’95: Proceedings of the 9th European Conference on Object-Oriented Programming, pages 77–101. Springer-Verlag, London, UK, 1995. [18] Serge Demeyer. Refactor conditionals into polymorphism: What is the performance cost of introducing virtual calls? In Proceedings of the International Conference on Software Maintenance (ICSM ’05), pages 627–630. IEEE Press, 2005. 130 BIBLIOGRAPHY [19] Serge Demeyer, Stéphane Ducasse, and Oscar Nierstrasz. Finding refactorings via change metrics. In OOPSLA’00 Conference Proceedings—Object-Oriented Programming Systems, Languages and Applications, Minneapolis, Minnesota, USA, October 15–19, ACM SIGPLAN Notices, 35(10):166–178, October 2000. [20] David Detlefs and Ole Agesen. Inlining of virtual methods. In ECOOP ’99: Proceedings of the 13th European Conference on Object-Oriented Programming, pages 258–278. Springer-Verlag, London, UK, 1999. [21] Danny Dig, Can Comertoglu, Darko Marinov, and Ralph Johnson. Automated detection of refactorings in evolving components. In Proceedings of the 20th European conference on Object-Oriented Programming, ECOOP’06, pages 404– 428. Springer-Verlag, Berlin, Heidelberg, 2006. [22] Stéphane Ducasse, Matthias Rieger, and Serge Demeyer. A language independent approach for detecting duplicated code. In Proceedings of the International Conference on Software Maintenance (ICSM ’99), Oxford, UK, August 30– September3, pages 109–118. IEEE Computer Society, Los Alamitos, California, August 1999. [23] Bruno Dufour, Karel Driesen, Laurie Hendren, and Clark Verbrugge. Dynamic metrics for Java. SIGPLAN Not., 38(11):149–168, October 2003. [24] eclipse.org. Eclipse. http://www.eclipse.org, 2003. [25] Eva Van Emden and Leon Moonen. Java quality assurance by detecting code smells. In Ninth Working Conference on Reverse Engineering, Richmond, Virginia, USA, October 28–November5, pages 97–107. IEEE Computer Society, October 2002. [26] Twan van Enckevort. Refactoring UML models: using openarchitectureware to measure UML model quality and perform pattern matching on UML models with OCL queries. In Proceeding of the 24th ACM SIGPLAN conference companion on Object oriented programming systems languages and applications, OOPSLA ’09, pages 635–646. ACM, New York, NY, USA, 2009. [27] Michael D. Ernst, Jake Cockrell, William G. Griswold, and David Notkin. Dynamically discovering likely program invariants to support program evolution. In Proceedings of the 21st International Conference on Software Engineering, pages 213–224. IEEE Computer Society Press, 1999. 131 BIBLIOGRAPHY [28] L. Etzkorn and H. Delugach. Towards a semantic metrics suite for object-oriented design. In Technology of Object-Oriented Languages and Systems, 2000. TOOLS 34. Proceedings. 34th International Conference on, pages 71 –80. 2000. [29] Cormac Flanagan and K. Rustan M. Leino. Houdini, an annotation assistant for ESC/Java. Lecture Notes in Computer Science, 2021:500–517, 2001. [30] Gern Florijn. RevJava – Design critiques and architectural conformance checking for Java software, 2002. [31] Marios Fokaefs, Nikolaos Tsantalis, Eleni Stroulia, and Alexander Chatzigeorgiou. Jdeodorant: identification and application of extract class refactorings. In Proceedings of the 33rd International Conference on Software Engineering, ICSE ’11, pages 1037–1039. ACM, New York, NY, USA, 2011. [32] Brian Foote and William F. Opdyke. Lifecycle and refactoring patterns that support evolution and reuse. In PLoP’94: Proceedings of the 1st Conference on Pattern Languages of Programs, pages 239–257. Addison-Wesley, 1995. [33] Martin Fowler, Kent Beck, John Brant, William Opdyke, and Don Roberts. Refactoring: Improving the Design of Existing Code. Addison Wesley Longman, Reading, Massachussetts, USA, 1999. [34] Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, Reading, Massachussetts, USA, 1995. [35] Neal Glew and Jens Palsberg. Type-safe method inlining. Science of Computer Programming, 52(1-3):281–306, 2004. [36] Google. Codepro analytix. http://code.google.com/javadevtools/codepro/ doc/index.html, 2010. [37] Pieter Van Gorp, Hans Stenten, Tom Mens, and Serge Demeyer. Towards automating source-consistent UML refactorings. In Proceedings of the Sixth International Conference on the Unified Modeling Language. 2003. [38] Daniel Graves. Incremental updating for the Fluid IR. Technical report, Department of Electrical Engineering and Computer Science, University of WisconsinMilwaukee, 2004. 132 133 BIBLIOGRAPHY [39] Aaron Greenhouse and John Boyland. An object-oriented effects system. In ECOOP’99: Proceedings of the 13th European Conference on Object-Oriented Programming, Lisbon, Portugal, June 14–18, volume 1628 of Lecture Notes in Computer Science, pages 205–229. Springer, Berlin, Heidelberg, New York, 1999. [40] William G. Griswold and Robert W. Bowdidge. Program restructuring via design-level manipulation. In Proceedings of the IEEE International Conference on Software Engineering (ICSE ’93), Baltimore, Maryland, USA, pages 127–139. ACM Press, New York, May 1993. [41] Brian Henderson-Sellers. Prentice Hall, 1996. Object-Oriented Metrics: Measures of Complexity. [42] Yoshiki Higo, Shinji Kusumoto, and Katsuro Inoue. A metric-based approach to identifying refactoring opportunities for merging code clones in a Java software system. J. Softw. Maint. Evol., 20:435–461, November 2008. [43] Instantiations. jFactor. http://www.instantiations.com/jfactor, 2002. [44] IntelliJ. IDEA. http://www.intellij.com/idea, 2002. [45] Kazuaki Ishizaki, Motohiro Kawahito, Toshiaki Yasue, Hideaki Komatsu, and Toshio Nakatani. A study of devirtualization techniques for a Java Just-InTime compiler. In OOPSLA ’00: Proceedings of the 15th ACM SIGPLAN conference on Object-oriented programming, systems, languages, and applications, pages 294–310. ACM Press, New York, NY, USA, 2000. [46] Ralph E. Johnson and William F. Opdyke. Refactoring and aggregation. In Object Technologies for Advanced Software, First JSSST International Symposium, volume 742, pages 264–278. Springer-Verlag, 1993. [47] Stephen C. Johnson. Lint: a C program checker. In Unix Programming’s Manual, pages 292–303. 1978. [48] Toshihiro Kamiya, Shinji Kusumoto, and Katsuro Inoue. CCFinder: a multilinguistic token-based code clone detection system for large scale source code. IEEE Transaction Software Engineering, 28(7):654–670, 2002. [49] Yoshio Kataoka, Michael D. Ernst, William G. Griswold, and David Notkin. Automated support for program refactoring using invariants. In Proceedings of the International Conference on Software Maintenance (ICSM ’01), Florence, BIBLIOGRAPHY Italy, November 6–10, pages 736–743. IEEE Computer Society, Los Alamitos, California, November 2001. [50] Hyoseob Kim and Cornelia Boldyreff. Developing software metrics applicable to UML models. Lecture Notes in Computer Science, 2374:–, 2002. [51] R. Kollmann and M. Gogolla. Metric-based selective representation of UML diagrams, 2002. [52] Raghavan Komondoor and Susan Horwitz. Tool demonstration: Finding duplicated code using program dependences. Lecture Notes in Computer Science, 2028:383–386, 2001. [53] Jochen Kreimer. Adaptive detection of design flaws. In Fifth Workshop on Language Descriptions, Tools and Applications (LDTA ’05), pages 117–136. 2005. [54] Jens Krinke. Identifying similar code with program dependence graphs. In Eighth Working Conference on Reverse Engineering, Stuttgart, Germany, October 2–5, pages 301–309. IEEE Computer Society, October 2001. [55] Bruno Lague, Daniel Proulx, Jean Mayrand, Ettore M. Merlo, and John Hudepohl. Assessing the benefits of incorporating function clone detection in a development process. In Proceedings of the International Conference on Software Maintenance (ICSM ’97), page 314. IEEE Computer Society, Washington, DC, USA, 1997. [56] Huiqing Li, Claus Reinke, and Simon Thompson. Tool support for refactoring functional programs. In Proceedings of the ACM SIGPLAN Workshop on Haskell, pages 27–38. ACM Press, 2003. [57] N. Maneerat and P. Muenchaisri. Bad-smell prediction from software design model using machine learning techniques. In Computer Science and Software Engineering (JCSSE), 2011 Eighth International Joint Conference on, pages 331 –336. may 2011. [58] Mika Mantyla, Jari Vanhanen, and Casper Lassenius. A taxonomy and an initial empirical study of bad smells in code. In Proceedings of the IEEE International Conference on Software Engineering(ICSE ’03), pages 381–384. 2003. 134 BIBLIOGRAPHY [59] Mika V. Mäntylä and Casper Lassenius. Subjective evaluation of software evolvability using code smells: An empirical study. Empirical Softw. Engg., 11:395– 431, September 2006. [60] Andrian Marcus and Denys Poshyvanyk. The conceptual cohesion of classes. In Proceedings of the 21st IEEE International Conference on Software Maintenance, ICSM ’05, pages 133–142. IEEE Computer Society, Washington, DC, USA, 2005. [61] Katsuhisa Maruyama and Takayuki Omori. A security-aware refactoring tool for Java programs. In Proceedings of the 4th Workshop on Refactoring Tools, WRT ’11, pages 22–28. ACM, New York, NY, USA, 2011. [62] T.J. McCabe. A complexity measure. IEEE Transaction on Software Engineering, 2(4):308–320, 1976. [63] P. Meananeatra, S. Rongviriyapanish, and T. Apiwattanapong. Using software metrics to select refactoring for long method bad smell. In Electrical Engineering/Electronics, Computer, Telecommunications and Information Technology (ECTI-CON), 2011 8th International Conference on, pages 492 –495. may 2011. [64] Hayden Melton and Ewan Tempero. Identifying refactoring opportunities by identifying dependency cycles. In Proceedings of the 29th Austrasian Computer Science Conference - Volume 48, ACSC ’06. 2006. [65] Naouel Moha. Detection and correction of design defects in object-oriented designs. In Companion to the 22nd ACM SIGPLAN conference on Object-Oriented Programming Systems and Applications Companion, OOPSLA ’07, pages 949– 950. ACM, New York, NY, USA, 2007. [66] Naouel Moha, Yann-Gael Gueheneuc, Laurence Duchien, and Anne-Francoise Lemeur. Decor: A method for the specification and detection of code and design smells. volume 36 of IEEE Transactions on Software Engineering, pages 20–36. 2010. [67] Emerson Murphy-Hill and Andrew P. Black. Seven habits of a highly effective smell detector. In Proceedings of the 2008 international workshop on Recommendation systems for software engineering, RSSE ’08, pages 36–40. ACM, New York, NY, USA, 2008. 135 BIBLIOGRAPHY [68] Glenford J. Myers. Composite Structure Design. John Wiley & Sons, Inc., New York, NY, USA, 1978. [69] Flemming Nielson, Hanne Riis Nielson, and Chris Hankin. Principles of Program Analysis. Springer-Verlag Berlin Heidelberg, New York, NY, USA, 1999. [70] Jeremy W. Nimmer and Michael D. Ernst. Invariant inference for static checking: An empirical evaluation. In Proceedings of the Tenth ACM SIGSOFT Symposium on Foundations of Software Engineering, pages 11–20. ACM Press, 2002. [71] A. Jefferson Offutt, Mary Jean Harrold, and Priyadarshan Kolte. A software metric system for module coupling. The Journal of Systems and Software, 20(3):295– 308, March 1993. [72] Rocco Oliveto, Malcom Gethers, Gabriele Bavota, Denys Poshyvanyk, and Andrea De Lucia. Identifying method friendships to remove the feature envy bad smell (nier track). In Proceedings of the 33rd International Conference on Software Engineering, ICSE ’11, pages 820–823. ACM, New York, NY, USA, 2011. [73] William Opdyke. Refactoring Object-Oriented Frameworks. PhD thesis, University of Illinois, 1992. [74] William F. Opdyke and Ralph E. Johnson. Creating abstract superclasses by refactoring. In Proceedings of the 1993 ACM conference on Computer science, pages 66–73. ACM Press, 1993. [75] Meilir Page-Jones. The Practical Guide to Structured Systems Design: 2nd edition. Yourdon Press, Upper Saddle River, NJ, USA, 1988. [76] Don Roberts, John Brant, and Ralph Johnson. A refactoring tool for smalltalk. TAPOS ’97, Journal of Theory and Practice of Object Systems, 3(4):253–263, 1997. [77] Linda H. Rosenberg. Applying and interpreting object oriented metrics. Object Oriented Systems, 1998. [78] Emmad Saadeh, Derrick Kourie, and Andrew Boake. Fine-grain transformations to refactor UML models. In Proceedings of the Warm Up Workshop for ACM/IEEE ICSE 2010, WUP ’09, pages 45–51. ACM, New York, NY, USA, 2009. 136 BIBLIOGRAPHY [79] Emmad Saadeh and Derrick G. Kourie. Composite refactoring using fine-grained transformations. In Proceedings of the 2009 Annual Research Conference of the South African Institute of Computer Scientists and Information Technologists (SAICSIT ’09), pages 22–29. ACM, New York, NY, USA, 2009. [80] Jean-Guy Schneider, Rajesh Vasa, and Leonard Hoon. Do metrics help to identify refactoring? In Proceedings of the Joint ERCIM Workshop on Software Evolution (EVOL) and International Workshop on Principles of Software Evolution (IWPSE), IWPSE-EVOL ’10, pages 3–7. ACM, New York, NY, USA, 2010. [81] Frank Simon, Frank Steinbrückner, and Claus Lewerentz. Metrics based refactoring. In Proceedings of the 5th European Conference on Software Maintenance and Reengineering (CSMR ’01), Lisbon, Portugal, March 14–16, pages 30–38. IEEE Computer Society, Los Alamitos, California, March 2001. [82] Satwinder Singh and K. S. Kahlon. Effectiveness of encapsulation and objectoriented metrics to refactor code and identify error prone classes using bad smells. SIGSOFT Softw. Eng. Notes, 36(5):1–10, September 2011. [83] sourceforge.net. Metrics. http://metrics.sourceforge.net, 1998. [84] sourceforge.net. CppRefactory. http://cpptool.sourceforge.net, 2001. [85] sourceforge.net. Transmogrify. http://transmogrify.sourceforge.net, 2001. [86] sourceforge.net. JRefactory. http://jrefactory.sourceforge.net, 2003. [87] Cara Stein, Letha Etzkorn, and Dawn Utley. Computing software metrics from design documents. In ACM-SE 42: Proceedings of the 42nd annual Southeast regional conference, pages 146–151. ACM Press, New York, NY, USA, 2004. [88] Eli Tilevich and Yannis Smaragdakis. Refactoring: Improving code behind the scenes. In Proceedings of the IEEE International Conference on Software Engineering (ICSE ’05), St. Louis, Missouri, May 15–21, pages 264–273. ACM Press, New York, May 2005. [89] Frank Tip, Robert M. Fuhrer, Adam Kieżun, Michael D. Ernst, Ittai Balaban, and Bjorn De Sutter. Refactoring using type constraints. ACM Trans. Program. Lang. Syst., 33(3):9:1–9:47, May 2011. 137 BIBLIOGRAPHY [90] Frank Tip, Adam Kiezun, and Dirk Bäumer. Refactoring for generalization using type constraints. In OOPSLA’03 Conference Proceedings—Object-Oriented Programming Systems, Languages and Applications, Anaheim, California, USA, October 26–30, pages 13–26. ACM Press, New York, 2003. [91] Tom Tourwé and Tom Mens. Identifying refactoring opportunities using logic meta programming. In Proceedings of the 7th European Conference on Software Maintenance and Reengineering(CSMR ’03), Benevento, Italy, March 26– 28, pages 91–100. IEEE Computer Society, Los Alamitos, California, March 2003. [92] W. T. Tsai, M. A. Lopex, V. Rodriguez, and D. Volovik. An approach measuring data structure complexity. In Proceedings of the International Computer Software and Applications Conference (COMPSAC ’86), pages 240–246. IEEE Computer Society, 1986. [93] N. Tsantalis, T. Chaikalis, and A. Chatzigeorgiou. Jdeodorant: Identification and removal of type-checking bad smells. In Software Maintenance and Reengineering, 2008. CSMR 2008. 12th European Conference on, pages 329 –331. april 2008. [94] N. Tsantalis and A. Chatzigeorgiou. Identification of move method refactoring opportunities. Software Engineering, IEEE Transactions on, 35(3):347 –367, may-june 2009. [95] XRef-Tech. XRefactory. http://www.xref-tech.com/speller, 1998. [96] Limei Yang, Hui Liu, and Zhendong Niu. Identifying fragments to be extracted from long methods. In Proceedings of the 2009 16th Asia-Pacific Software Engineering Conference, APSEC ’09, pages 43–49. IEEE Computer Society, Washington, DC, USA, 2009. [97] Yuming Zhou, Jiangtao Lu, and Hongmin Lu Baowen Xu. A comparative study of graph theory-based class cohesion measure. Software Engineering Notes, 29(2):13–18, 2004. [98] Yuming Zhou, Lijie Wen, Jianmin Wang, and Yujian Chen. DRC: A dependence relationships based cohesion measure for classes. In Tenth Asia-Pacific Software Engineering Conference, page 215. 2003. 138 139 CURRICULUM VITAE Kwankamol Nongpong Place of Birth: Nakhonnayok, THAILAND Education B.S., Assumption University of Thailand, October 1996 Major: Computer Science M.S., University of Wisconsin-Milwaukee, May 2000 Major: Computer Science Dissertation Title: Integrating “Code Smells” Detection with Refactoring Tool Support Awards and Scholarships: 2000-2004: Research Assistant, University of Wisconsin-Milwaukee. 2004-2005: Scholarship, Assumption University, Thailand. 2005-2006: Research Assistant, University of Wisconsin-Milwaukee.