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 migra- tion [6]. All methods in legacy classes (e.g., Hashtable and Vector) have been super- seded by classes (HashMap and ArrayList respectively) which provide similar func- tionalities 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.
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.
Chapter 3
Refactoring Complexities
Refactorings are structural changes made to a program that preserve program se- mantics. 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 refactor- ings 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 Mar- tin Fowler [33]. Two additional simple low-level refactorings were noticed while we were examining an actual case of software evolution: Reverse Conditional and
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 com- plexities 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:
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
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.