Anna Derezińska
email: a.derezinska@ii.pw.edu.pl

Anna Szustek

Object-Oriented Testing Capabilities and Performance Evaluation of the C# Mutation System ⇤

Keywords: mutation testing, object-oriented mutation operators, C#, system evolution

The main purpose of mutation testing approach is to check a test suite quality in terms of the adequacy to killing programs with inserted programming faults. We present advances in the C# mutation testing system that supports object-oriented mutation operators. The system enhancements related to functional requirements (mutation operators, avoiding generation of invalid and partially of equivalent mutants) and non-functional ones (speed-up using a new parser and reflection, space reduction storing mutant updates). Mutation testing of six widely used open source programs is discussed. The quality of the tests supplied with these programs was experimentally determined. Performance measures were evaluated to assess system enhancements (2-4 faster mutants creation, 10-100 times disk space reduction, tradeoff of time overhead for storing mutants of different size in a local or remote repository).

Introduction

Software testing is a critical part of software development. Mutation is the technique of generating faulty variants of a program [START_REF] Voas | Software Fault Injection, Inoculating Programs Against Errors[END_REF]. It can be applied for assessing the fault detection ability of a test suite or comparing testing strategies. According to the experimental research [START_REF] Andrews | Using Mutation Analysis for Assessing and Comparing Testing Coverage Criteria[END_REF], generated mutants can be treated as representatives of real faults. Mutation analysis can be performed "whenever we use well defined rules defined on syntactic descriptions to make systematic changes to the syntax or to objects developed from the syntax" [START_REF] Offut | A Mutation Carol Past, Present and Future[END_REF].

A transformation rule that generates a mutant from the original program is called a mutation operator. So-called standard (or traditional) mutation operators introduce small, basic changes that are possible in typical expressions or assignment statements of any general purpose language [START_REF] Voas | Software Fault Injection, Inoculating Programs Against Errors[END_REF]. Mutation operators dealing with many specific programming features, including object-oriented ones, were also developed. If for any program P the result of running its mutant P 0 is different from the result of running P for any test case of the test suite T, then the mutant P 0 is said to be killed. A mutant that cannot be killed by any possible test suite is counted as an equivalent one. The adequacy level of the test set T can be computed in terms of the number of mutants killed by T.

Advanced mutation operators of C# language were adopted similarly to objectoriented operators of Java language [START_REF] Chevalley | Applying Mutation Analysis for Object-oriented Programs Using a Reflective Approach[END_REF][START_REF] Chevalley | A Mutation Analysis Tool for Java Programs[END_REF][START_REF] Kim | Class Mutation: Mutation Testing for Object-oriented Programs[END_REF][START_REF] Ma | Inter-class Mutation Operators for Java[END_REF] or developed for the specific language features and studied in experiments [START_REF] Derezińska | Advanced Mutation Operators Applicable in C# programs[END_REF][START_REF] Derezińska | Quality Assessment of Mutation Operators Dedicated for C# Programs[END_REF]. Based on the gathered experiences about evaluation and specification of object-oriented mutation operators, the first CREAM (CRE-Ator of Mutants) system supporting few of them for C# programs was implemented. Prior works [START_REF] Derezińska | CREAM -a System for Object-oriented Mutation of C# Programs[END_REF][START_REF] Derezińska | Tool-supported Mutation Approach for Verification of C# programs[END_REF] have presented the general architecture overview of the system and results of the first experiments. Suffered from several limitations, the system evolved to CREAM2, enhancing its mutant generation capabilities and performance issues.

The crucial problem in mutation testing approaches are considerable time and resource constraints. Due to lack of mature mutation tool support, also very little empirical evaluation has been done in the industrial development of C# programs. In this paper, we discuss advances in the CREAM2 system, including code parsing improvements, preventing generation of invalid and partially of equivalent mutants, cooperation with the distributed Tester environment [START_REF] Derezińska | Distributed Environment Integrating Tools for Software Testing[END_REF], storing mutant's updates in the SVN repository [13]. We conducted experiments with several open source programs to evaluate and verify an extended set of mutation operators, to evaluate quality of test suites, and compare the results with the previous mutation system and standard mutations.

The remaining paper is organized as follows. In the next Section related work is discussed. Section 3 contains the outline of the improvements of the CREAM2 system. Section 4 presents an experimental study on object-oriented mutation of C# programs. We finish with assessment of performance results and conclusions.

Related Work

Mutation testing approach was developed primarily for structural languages, like Fortran with the mostly known Mothra tool [START_REF] Voas | Software Fault Injection, Inoculating Programs Against Errors[END_REF]. Further, similar standard mutation operators were applied for other languages, as C language: Proteum [START_REF] Delmaro | Proteum -a Tool for the Assessment of Test Adequacy for C Programs[END_REF][START_REF] Namin | On Sufficiency of Mutants[END_REF], MiLu [START_REF] Jia | MiLu: a Customizable, Runtime-Optimized Higher Order Mutation Testing Tool for the Full C Language[END_REF], and also object-oriented languages Java, C++, C#.

Standard mutations introduced into Java Byte Code are supported in different tools, like Jumble [START_REF] Irvine | Jumble Java Byte Code to Measure the Effectiveness of Unit Tests, Mutation'07 at[END_REF], MuGamma [START_REF] Kim | MuGamma: Mutation Analysis of Deployed Software to Increase Confidence and Assist Evolution[END_REF]. Selected traditional and some object-oriented mutations in Java were also implemented in Judy [START_REF] Madeyski | On the Effects of Pair Programming on Thoroughness and Fault-finding Effectiveness of Unit Tests[END_REF] and Javalanche [START_REF] Grün | The Impact of Equivalent Mutants[END_REF] tools. Test cases considered for Java programs were commonly unit tests suitable for JUnit environment [START_REF] Irvine | Jumble Java Byte Code to Measure the Effectiveness of Unit Tests, Mutation'07 at[END_REF][START_REF] Madeyski | On the Effects of Pair Programming on Thoroughness and Fault-finding Effectiveness of Unit Tests[END_REF][START_REF] Moore | Jester a JUnit Test Tester[END_REF][START_REF] Do | A Controlled Experiment Assessing Test Case Prioritization Techniques via Mutation Faults[END_REF], or similar but specialized like in MuJava [START_REF] Ma | MuJava: an Automated Class Mutation System, Software Testing[END_REF]. Several testing systems for C++ language uses standard mutation testing also in commercial products, as Insure++ from Parasoft. Simple changes to Java source code, without parser involvement, were implemented also in Jester environment [START_REF] Moore | Jester a JUnit Test Tester[END_REF]. The ideas of Jester system were transformed to Python and C# languages. Nester tool [24] supports the standard mutations of C# language. The improved version of Nester makes only one compilation run for all mutants. Afterwards, it is decided during test execution which mutant should run.

Mutation testing of object-oriented program features (mostly inter-class relations) was exhaustively studied for Java programs [START_REF] Chevalley | Applying Mutation Analysis for Object-oriented Programs Using a Reflective Approach[END_REF][START_REF] Chevalley | A Mutation Analysis Tool for Java Programs[END_REF][START_REF] Kim | Class Mutation: Mutation Testing for Object-oriented Programs[END_REF][START_REF] Ma | Inter-class Mutation Operators for Java[END_REF][START_REF] Ma | MuJava: an Automated Class Mutation System, Software Testing[END_REF][START_REF] Lee | Empirical Evaluation of Orthogonality of Class Mutation Operators[END_REF][START_REF] Smith | A Empirical Evaluation of the MuJava Mutation Operators[END_REF]. Standard and objectoriented operators for Java were applied in MuJava [START_REF] Ma | MuJava: an Automated Class Mutation System, Software Testing[END_REF] and MuClipse [START_REF] Smith | A Empirical Evaluation of the MuJava Mutation Operators[END_REF] -an Eclipse plug-in based on MuJava. In MuJava, mutants are generated either as a parameterized program (so-called meta-mutant) that is further recompiled and executed as a mutant according to a given parameter, or a mutation is introduced directly in the Byte Code.

The first tool supporting selected object-oriented mutations for C# was the CREAM system [START_REF] Derezińska | CREAM -a System for Object-oriented Mutation of C# Programs[END_REF][START_REF] Derezińska | Tool-supported Mutation Approach for Verification of C# programs[END_REF]. The Nmutator tool announced in 2002 was supposed to introduce object-oriented mutations into C# programs, but there are no evidences that this intention was fulfilled. Research in [START_REF] Baudry | From Genetic to Bacteriological Algorithms for Mutation-based Testing[END_REF] mentioned object-oriented features of C# but concentrated on other problems and does not develop any tool at this area. Besides the CREAM system the only known system dealing with object-oriented mutation operators for C# programs is the ILMutator prototype [START_REF] Kowalski | Implementing Object Mutations into Intermediate Code for C# programs[END_REF]. It provides object-oriented mutations into the intermediate code derived from compiled C# programs. It implements so far six types of changes of intermediate code corresponding to object-oriented mutation operators on the C# source code level. Further development of the tool is in progress.

There are still many challenges that need to be solved in order to effectively bring mutation testing into industrial practice. Representativeness of mutation faults in comparison to real faults was studied in case of standard mutation operators and C programs [START_REF] Andrews | Using Mutation Analysis for Assessing and Comparing Testing Coverage Criteria[END_REF]. Experiments gave promising results, but analogous facts for mutation faults injected into other languages and using more specialized operators have been not yet sufficiently verified [START_REF] Do | A Controlled Experiment Assessing Test Case Prioritization Techniques via Mutation Faults[END_REF].

Generation and execution of many mutants is generally very expensive. Time and space performance can be limited by selection of the most relevant mutation operators, via selective mutation [START_REF] Offut | An Experimental Evaluation of Selective Mutation[END_REF], sufficient or orthogonal operators [START_REF] Namin | On Sufficiency of Mutants[END_REF][START_REF] Lee | Empirical Evaluation of Orthogonality of Class Mutation Operators[END_REF]. Performance can be also improved by introduction of faults into intermediate language forms like Java Byte Code, or Intermediate Language of .NET, or usage of repositories if many mutants of the same program are stored.

Another crucial problem is detection of equivalent mutants. In an experiment 40% of all mutations turned out to be equivalent [START_REF] Grün | The Impact of Equivalent Mutants[END_REF]. Existence of equivalent mutants is, in general, an undecidable problem. Therefore, applied algorithms cannot properly classify all mutants in all cases. Different techniques to recognize equivalent mutants in the structural programs were proposed, like constraint based testing (CBT), program optimizing rules, genetic algorithms, program slicing. Dealing with equivalent mutants in object-oriented programs is more complicated and is still not enough tool-supported. Therefore, it is important to prevent creating equivalent mutants. Firstly, the mutation operators should be carefully selected, avoiding those that can lead to many equivalent mutants. Secondly, generation of mutants can be restricted, omitting those cases that might produce equivalent mutants, even if under such restrictions certain proper mutants will be not generated.

Advances in the CREAM2 System

Basic principles of the CREAM2 system are similar to those of the previous version [START_REF] Derezińska | CREAM -a System for Object-oriented Mutation of C# Programs[END_REF][START_REF] Derezińska | Tool-supported Mutation Approach for Verification of C# programs[END_REF]. The system is aimed at object-oriented and other specialized programming flaws. O-O mutation operators, in opposite to simple traditional ones, have to deal with the structural program dependencies. The mutant generator uses a parser tree of the analyzed code. According to the language grammar and the rules defined in a mutation operator, the places in the tree are identified where the operator could be applied. If additional correctness conditions are satisfied at any such place, the tree is modified reflecting a fault introduced by the operator (see an example below). The mutant is a program corresponding to the modified tree. Many mutants can be created from the modifications applied in different places according to the rules defined by the same mutation operator.

Based on the experiment results, the next version of the CREAM system has been developed. In the program evolution the following goals were achieved:

extension of functionality (new mutation operators, collecting of timing features, calculation of statistics, better interface), enhancement of program expansibility, improvements of mutant generation and its speed-up by usage of a new parser library and reflection mechanism, decrease of memory requirements on storing mutated programs, correlation of mutation with the code coverage output. About forty object-oriented and other advanced mutation operators were specified for C# programs and their usefulness evaluated in preliminary experiments [START_REF] Derezińska | Advanced Mutation Operators Applicable in C# programs[END_REF][START_REF] Derezińska | Quality Assessment of Mutation Operators Dedicated for C# Programs[END_REF]. Based on this data, in the CREAM2 system 13 object-oriented operators were implemented (Table 1). Mutation operators mimic different faults introduced by developers using various of programming techniques. Correctness conditions are applied in order to assure that a valid mutant is generated, i.e. an injected fault would not be detected by a compiler. They are also partially aimed at preventing creating too many equivalent mutants. In this case, the measured mutation score indicator is closer to the exact mutation score.

For example, the EOA operator simulates a fault that a copy of an object is assigned instead of a desired object. In case of EOA operator, only code extracts of the following syntax were mutated:

<Expression> = <Identifier> <Variable> = <Identifier>
If the right side of the assignment were equal to a different expression than an identifier of a variable, as for example result of a method call or a complex expression, the EOA operator would be not applied to this statement. In this way we prevent generating a code that might be not correctly compiled. Expansibility mechanism of the system was improved. CREAM2 can be extended with new mutation operators implemented in appropriate add-ins without modification of other code. The same technique can be used for substitution of an existing operator with its better version. This feature was verified by users developing traditional mutation operators, applied in further experiments:

-AOR -Arithmetic Operator Replacement (binary: +, -, * ,/, %, unary:+, -, and pre/post: v++, ++v, v-, -v) -COR -Conditional Operator Replacement (binary: &&, ||, &, |, ^, and unary: !) -LOR -Logical Operator Replacement (binary: &, |, ^, and unary: ~) -ROR -Relational Operator Replacement (>, >=, <, <=,==, !=) -ASR -Assignment Operator Replacement (short-cut: =, +=, -=, *=, /=) In the previous system, a class hierarchy was created and used to examine inheritance dependencies between classes, identification of member types, etc. Such information was helpful during identification of possible mutation places in the parser tree. The approach had some limitations, as classes from external libraries could not be taken into account in the considered class hierarchy. Lack of access to embedded types and library classes impeded implementation of more sophisticated object-oriented operators, like IHI or EOA. For example, the IHI operator requires information about members inherited from the base class. If the base class belonged to a library, information was inaccessible. The problem was solved in CREAM2 by usage of reflection mechanism for creation and inspection of the type hierarchy. More advanced mechanisms of type checking minimized the possibility of creation of invalid mutants.

In CREAM2, instead of C# parser kscparse used previously, a new parser from the NRefactory library [30] was applied. Therefore those files that include C# 2.0 constructions could be also mutated. The new parser enables faster movement on a parser tree and its modification. In result, a number of ill-defined mutants decreased and mutants could be created more effectively.

Experiments performed with the previous system were limited by requirements on disk space for storing code of many mutants. A mutated project differs only in few code lines from the original one. Solving this problem, we applied the SVN (Subversion) server [13] managing program versions. An original project is stored as the first version in a repository and each mutant is one of next versions. Therefore, only differences between a mutant and its origin are stored.

The program repository was also used as a buffer for simple integration of the mutant generator with other testing tools. CREAM2 cooperated with Tester -a distributed environment integrating software testing tools [START_REF] Derezińska | Distributed Environment Integrating Tools for Software Testing[END_REF]. It can organize a hybrid testing process consisting of steps performed by differed testing tools and statistic evaluation of various test results. One of integrated tools was the IBM Rational PureCoverage tool. Code coverage results of a mutated program can be read by CREAM2. Taking into account coverage data can be used as a primarily condition for assessing test suites. It bounds a number of not killed mutants that have to be classified of being equivalent or not, because mutations introduced to areas not covered by tests are often not killed by these tests [START_REF] Derezińska | Quality Assessment of Mutation Operators Dedicated for C# Programs[END_REF].

New versions of C# language (2.0 and 3.0) provide new programming constructions, e.g. generic types, nullable types, extensions to static types, properties, indexers, covariance, delegates etc., that were considered for new advanced mutation operators. A set of such operators were proposed and examined in preliminary experiments. The results were not very promising, because the new features concentrate mainly on simplification of code writing and either their misuse was easily detected by a compiler or many equivalent mutants were generated. Therefore these new operators were not included to the current CREAM2 version.

Experiments on Operators Evaluation and Tests Qualification

Capabilities of the CREAM2 system were verified in experiments performed on a set of widely used, open-source programs, which are distributed together with their unit test suits. The basic complexity measures of the programs and their tests are summarized in Table 2.

The experiments were performed according to the following scenario:

1. An original project with its unit test suite was added to the local or remote project repository. 2. The project was run against its tests. Code coverage of the program was evaluated.

Program test results (an oracle) and coverage results were stored in the test results data base of the Tester system [START_REF] Derezińska | Distributed Environment Integrating Tools for Software Testing[END_REF]. 3. CREAM2 generated set of mutants of the project according to selected mutation operators and coverage data. Updates of compiled valid mutants were stored in the project repository. 4. Mutants were tested with their test suites using NUnit [START_REF]NUnit[END_REF]. Generated results were compared against the given oracle, in order to decide whether a mutant was killed or not, and stored in the data base.

5. Mutation results were evaluated using stored data.

Number of mutants created by CREAM2 for different object-oriented operators are shown in Table 3. The first column identifies programs described in Table 2. Results for the IHD operator are omitted, because no mutants were generated. Usage of the repository for storing mutants allow to significantly raise limits on number of performed mutants. Therefore all possible mutants could be created, as for example above seven thousand mutants of Adapdev.NET ( number 1). In Table 4 and 5 mutation results of the programs and the object-oriented mutation operators are presented. Numbers of mutants killed by the unit tests were in many cases not very high. Mutation score was calculated as a ratio of the number of mutants killed over the total number of generated valid mutants. Precisely, it is the lower bound on mutation score, called in [START_REF] Madeyski | On the Effects of Pair Programming on Thoroughness and Fault-finding Effectiveness of Unit Tests[END_REF] as mutation score indicator. The exact mutation score should be calculated over the number of all non-equivalent mutants. In the CREAM2 mutant generator much effort was spent to create only valid mutants and if possible avoid equivalent mutants. However, equivalent mutants can be also created. In simple cases they could be eliminated by hand. But, for the most discussed programs, when the numbers of not killed mutants were higher (like for the PRV operator), it was impossible to identify precisely all equivalent mutants.

Mutation results of all programs except NCover were low (16-37%) and showed a poor quality of the considered test suites. But, tests of NCover (no 5) killed all mutants of six operators and the most of mutants for the rest of them, giving a total mutation score equal to 97.6%. It is characteristic that in general a test suite good at killing mutants is dealing quite well with mutants generated by the given different mutation operators. The ability of tests to kill mutants generated by different mutation operators was compared. It was measured, how many tests of the suite killed a mutant (Table 6). In case of two biggest programs (1 and 4) any mutation was killed usually by one test. Test cases of these programs were diverse and devoted to disjoint subjects. In the remaining programs a test was killed on average by about ten to several dozen of tests. It points out the existence of many redundant test cases as far as the given programming flaws are concerned. On the other hand, there are many mutants not killed by the tests from these test suites. For program number 5, having the best mutation score, the numbers of tests killing mutants are except the JID operator on a moderate level of about few tests. High mutation results for different mutation operators were obtained by a limited number of tests, but effective and focused on different subjects. We can observe that a number of tests killing mutants was in general a feature of a test suite. There was no significant dependency between this measure and the kind of the operator. The averaged percentage of tests that killed a mutant are given in the last column. Mutation results of the object-oriented operators were compared with results of standard operators. Five standard mutation operators (see Sec. 3) were applied for two programs Adapdev.NET and NCover and a subset of all possible mutants were generated. No mutants were created for the ASR operator because no short-cuts (like +=) were used in these programs. Mutation score was calculated for given numbers of mutants (Fig. 7). All mutants were killed by the test suites in almost all cases. Good results of the test suite were expected for NCover as the tests were also good at detecting various flaws injected by the object-oriented operators. However, good results (99%) were obtained also for Adapdev.NET, while mutation score for the object-oriented operators was about 28%. In general, simple faults simulated by standard operators are more eas- The experiment results have to be considered in the context of possible threats to validity. A big number of mutants helped to cope with threats to the statistical conclusions validity. However, for some mutation operators, e.g. IHI, these numbers were quite small, because such operator deals with less frequent programming constructions. Mono-operation bias is a threat as the experiment was conducted on a single development project. Therefore several, different programs were used in the experiments. The selected programs and their test suites are commonly used and neither prepared nor adapted for the experiments. It reduces threats to external validity concerning possibility of results generalization.

Performance Evaluation

In experiments we measured the impact of the applied technology on the system performance. Time and storage constrains were compared for different solutions and to the previous version of the mutation system.

One of improvements of the CREAM2 system was substitution of the parser module. Usage of a new parser speeded up the generation of mutants, as routines manipulating on parser trees were more effective. Average time of creation of mutants' source code was about 2-4 times shorter than in the previous system.

Repository for storing mutants was applied in order to reduce disk storage requirements. However, storing mutants in the repository takes more time than direct storing of whole mutants. Repository can be saved locally on the same computer as the mutants generation system, or on another remote computer. In dependence on the program characteristics, time of generation of mutants for a local repository was longer about 61-121% than the time of direct storing whole mutants in the same computer. While using a remote repository, the corresponding time was longer of about 39-91%. The average times of generating and storing a mutant in different locations for selected programs are given in Table 8. The time of generation to a repository depends on a program size. For bigger programs (like programs no 1, 4 and 5 -Table 2), time of using a remote repository was shorter than using a local one. The repository server had to analyze many files in order to identify and store the update. Concurrent work of the server and the generator was beneficial and exceeded the transmission overhead. For medium-size programs (as 2 and 3), times of using both kinds of repository were comparable. In It should be notated that, although discrepancies of average times are not very high, difference of times required for processing many mutants can be substantial. For example, generation and execution of all mutants of Adapdev.NET took 29 hours more using the local repository than the remote one.

The main goal of application of the SVN repository was decreasing memory requirements on storing mutants. Mutated programs are very similar to each other. In a repository only the updates to the original programs have to be stored. An empty repository needs about 80 kB and this is a small overhead in comparison to program sizes. Amounts of disk memory are compared in Table 9. Numbers in the last column refer to the repository including given numbers of mutated programs. The previous column shows how many disk space would be necessary to store the same mutated projects directly, without using the repository. The results showed that required memory was reduced in size from 10 to 100 times according to different programs and numbers of mutants.

Conclusions and Future Work

This paper reports on experiments on the object-oriented mutation testing of C# programs performed by the CREAM2 tool and its evolution to the current version. Application of advanced mutation operators implies more computational effort than the standard operators. But, according to experiments, test cases adequate to standard mutations are not enough capable to expose additional faults dealing with object-oriented constructions. Therefore, automated usage of advanced mutants for test qualification would be profitable.

Performed experiments showed that the new version of the CREAM2 system can generate object-oriented mutants more precisely than the previous one. The problem of big amounts of disk space was solved by usage of SVN repository for storing mutants. It gave storage reduction of about 10 to 100 times. A bottleneck of experiments remains time requirements. However, using improved organization and automation of the whole testing process, all implemented object-oriented mutants of the given programs, i.e. above ten thousand of mutants, were generated, tested and analyzed. The long execution time could be tolerable when the whole process can be realized without user supervision. Essential reduction of time requirements can be obtained by elimination of mutants compilation. It is straightforward for standard mutation operators, but not for the advanced ones. First experiments with the prototype ILMutator system [START_REF] Kowalski | Implementing Object Mutations into Intermediate Code for C# programs[END_REF] gave promising results. It inserts mutations that simulate objects-oriented mutations of C# language direct into the Intermediate Language of .NET.

The most crucial problem of the mutation testing automation is still exclusion of equivalent mutants. Although the accurate recognition of all equivalent mutants would be in general impossible, but acceptable results could be obtained by limitation of their number to small amounts that can be neglected. There are two complementary approaches: preventing of equivalent mutant generation and detection of equivalent mutant in a set of non killed mutants. We concentrated so far on the first approach, while identification of equivalent mutants for advanced mutants of C# needs future work.

Another important issue concerning testing automation is execution of test runs and concluding on the killing of mutants. In the reported experiments, NUnit was used to run mutants with unit tests and to generate tests results compared to an oracle. In general, different test cases and test environments can be apply together with mutation testing. In the future, capture and replay environments with testing scripts including verification points and other verification mechanisms can be combined with the mutant generation system.

  public class Car : ICloneable{ public object Clone(){...} ...} Car s1 = new Car(); Car s2; Original code: Mutated code: s2 = s1; s2 = s1.Clone() as Car;

Table 1 .

 1 Selected object-oriented mutation operators.

	No	Operators
	1 EHR Exception handler removal
	2 EOA Reference assignment and content assignment replacement
	3 EOC Reference comparison and content comparison replacement
	4 IHD Hiding variable deletion
	5 IHI Hiding variable insertion
	6 IOD Overriding method deletion
	7 IOK Override keyword substitution
	8 IOP Overridden method calling position change
	9 IPC Explicit call of a parent's constructor deletion
	10 ISK Base keyword deletion
	11 JID Member variable initialization deletion
	12 JTD This keyword deletion
	13 PRV Reference assignment with other compatible type

Table 2 .

 2 Complexity measures of mutated programs.

	No Programs	Number of	Source code [LOC] Project
		Classes Tests	pure with tests total [MB]
	1 Adapdev.NET	252	14	68158	68315	23.0
	2 Castle.Core	57 165	6190	8737	1.5
	3 Castle.DynamicProxy	73	78	6933	10594	2.0
	4 CruiseControl.NET	352 1279	31344	62412	32.0
	5 NCover	38	72	4347	7403	18.4
	6 NHibernate.IEsiCollections	8 153	21047	22293	0.4

Table 3 .

 3 Number of mutants in programs per operators.

	No						Operators						Sum
		EHR EOA EOC IHI IOD IOK IOP IPC ISK JID JTD PRV
	1	89 123 1839	2 88 52 50 23 94 738 2365 1963 7425
	2	0	5	74	0 10 10	0 13	2	22	42	51	229
	3	0	9	41	0	9	9	1	4	4	38	5	49	169
	4	7	49 398 12 65 53 10 22 26 443 964 470 2519
	5	0	17	61	0	3	3	1	1	6	55	77	31	255
	6	0	0	28	0	2	2	0	0	0	4	45	2	83
	Sum	96 203 2441 14 177 129 62 63 132 1300 3498 2566 10680

Table 4 .

 4 Number of killed mutants.

	No						Operators							Sum
		EHR EOA EOC IHI IOD IOK IOP IPC ISK JID JTD PRV
	1	29	40 539	2 15 14 18 10 47 291 690 384 2079
	2	-	0	9	-	0	0	-	3	0	7	4	14	37
	3	-	0	9	-	4	4	0	1	0 33	3	34	88
	4	2	47	19	1	4	3	0	2	0 29 212 260 532
	5	-	17	60	-	3	3	1	1	5 54 74	31 249
	6	-	-	24	-	0	0	-	-	-	1	5	1	31

Table 5 .

 5 Mutation score in [%].

	No					Operators			Sum
		EHR EOA EOC IHI IOD IOK IOP IPC ISK JID JTD PRV
	1	32.6 32.8 29.3 100 17.1 26.9 36 43.5 50.0 39.4 29.2 19.6 28.0
	2	-	0 12.1	-	0	0	-23.1	0 31.8 9.5 31.8 16.7
	3	-	0 21.9	-44.4 44.4	0 25.0	0 86.8 60.0 73.9 37.6
	4	25.0 4.2 19.0 7.14 6.1 5.6	0 9.1	0 6.6 21.9 55.7 21.4
	5	-100 98.3	-100 100 100 100 83.3 98.2 96.1 100 97.6
	6	-	-85.7	-	0	0	-	-	-25.0 11.1	50 37.3

Table 6 .

 6 Number of tests killing mutants.

	No	Operators	Avg. Avg.
		EHR EOA EOC IHI IOD IOK IOP IPC ISK JID JTD PRV	[%]

Table 7 .

 7 Mutation results for standard mutation operators.

	No Programs		Number of mutants	Mutation score [%]
		AOR COR LOR ROR Sum AOR COR LOR ROR Sum
	1 Adapdev.NET	60	46	40	40 186 100	98 100 100 99
	5 NCover	40	8	0	80 128 100 100	-100 100
	ily to detect by tests. It is not sufficient to have test suites adequate to standard mutations
	in order to detect faults misusing object-oriented rules.

Table 8 .

 8 Average time of generating and storing a mutant in [s].

	No Programs	On local In local In remote
		disk	repository repository
	1 Adapdev.NET	27.98	61.9	47.71
	2 Castle.Core	3.20	6.24	6.09
	3 Castle.DynamicProxy	3.77	6.22	6.04
	4 CruiseControl.NET	32.98	33.64	25.13
	5 NCover	14.47	26.19	20.08
	6 NHibernate.IEsiCollections	2.80	5.59	9.30
	case of the smallest program (number 6) usage of the local repository took less time,
	because the server of the repository had smaller requirements on processor time and the
	transmission overhead was noticeable.

Table 9 .

 9 Comparison of memory requirements.

	No Programs	Number of mutants Disk storage Repository storage
	1 Adapdev.NET	7425	167 GB	3 GB
	2 Castle.Core	229	204 MB	34 MB
	3 Castle.DynamicProxy	169	481 MB	31 MB
	4 CruiseControl.NET	2519	79 GB	1 GB
	5 NCover	255	5000 MB	90 MB
	6 NHibernate.IEsiCollections	83	71 MB	6 MB
	Sum	10680	252 GB	4 GB

Acknowledgments

The authors would like to thank Krzysztof Sarba, for his help in performing experiments.

⇤

This work was supported by the Polish Ministry of Science and Higher Education under grant 4297/B/T02/2007/33.


Marvin Ferber
email: marvin.ferber@uni-bayreuth.de

Sascha Hunold
email: hunold@uni-bayreuth.de

Björn Krellner

Thomas Rauber
email: rauber@uni-bayreuth.de

Thomas Reichel

Gudula Rünger
email: ruenger@cs.tu-chemnitz.de

Reducing the Class Coupling of Legacy Code by a Metrics-Based Relocation of Class Members

Keywords: legacy software, class decoupling, pattern-based code refactoring, code metrics

des établissements d'enseignement et de recherche français ou étrangers, des laboratoires publics ou privés.

Introduction

Many software systems that undergo evolution for several years can become legacy when the costs and time to maintain the software grows incrementally. Programmers handle this problem known as code decay [START_REF] Eick | Does Code Decay? Assessing the Evidence from Change Management Data[END_REF] with continuous refactoring and system restructuring. Apart from the need for software analysis tools for an extensive system understanding [START_REF] Binkley | Source Code Analysis: A Road Map[END_REF], there is also the need for transformation tools that support the programmer while restructuring the legacy code. An important restructuring task is the modularization (decoupling) of classes, which helps to reduce the systems' complexity [START_REF] Mens | A Survey of Software Refactoring[END_REF] and to adapt legacy systems to distributed environments [START_REF] Serrano | Reengineering Legacy Systems for Distributed Environments[END_REF].

In a coarse-grained modularization in object-oriented languages the basic elements are classes, which can be moved between packages. The operations to move classes between packages and preserve syntactic and semantic correctness are supported by most modern IDEs. To reduce class coupling, and to improve class encapsulation and cohesion between class members, a finer-grained modularization is desirable. This kind of modularization includes the relocation of member methods and variables into more appropriate classes and the splitting of oversized classes in order to separate inner class concerns or services [START_REF] Seng | Search-Based Determination of Refactorings for Improving the Class Structure of Object-Oriented Systems[END_REF]. Moving member methods and variables without preserving correctness and appropriate test cases is an error-prone task [START_REF] Fowler | Refactoring: Improving the Design of Existing Code[END_REF]. Since programmers are often not familiar with the software and the dependencies of relocated methods or variables in detail, they are not aware of possible side effects. Fully automated transformations that preserve the behaviour of the software are desirable but not available in general.

In this article we focus on refactoring and modularizing legacy classes by moving methods between classes. Since the methods that should be moved may depend on other members of the considered class, e.g., variables or other methods, all related members have to be moved to preserve code integrity. We introduce the term MemberGroup as a set of related class members. A MemberGroup represents an encapsulated concern of a class and is therefore independent from other class members. Because Member-Groups do not have dependencies to other members of the containing class, they can be relocated to other classes to reduce inter-class dependencies.

The article is structured as follows. In the next section we introduce the related groups of member variables and methods called MemberGroups. Section 3 outlines two MemberGroup patterns that can be relocated automatically. Preconditions and transformation rules for the relocation task are also given. In Section 4 we discuss metrics to support the relocation task in order to reduce class dependencies. We show that the analysis and relocation of MemberGroups can be integrated into reengineering toolsets like TRANSFORMR [START_REF] Hunold | Transformation of Legacy Software into Client/Server Applications through Pattern-Based Rearchitecturing[END_REF]. We investigate the impact of the relocation of MemberGroups on two open source applications and show the achieved improvements in Section 5. Related work is discussed in Section 6 and Section 7 concludes the article.

MemberGroups in Classes

In this section we introduce the term MemberGroup as set of member variables and methods that represent an encapsulated concern of a class. To define such a group we describe a software system as a graph structure. Table 1 summarizes the nodes and edges that are necessary to describe all dependencies between the members of all classes of a software as a dependency graph.

The function dep(u, v) between two members u and v represents a dependency between u and v in the source code. If there is a dependency u ! v or a dependency v ! u then dep(u, v) returns True. To take transitive dependencies into account, we introduce the function dep ⇤ U (u, v), which indicates a dep-relation between u and v over a subset of members U ✓ M. Therefore dep ⇤ U represents an undirected path in the dependency graph of the software system. With the definitions of Table 1 we define a MemberGroup MG(m) in a class C starting with a method m 2 Meth(C) as follows:

MG(m) = {m} [ {v 2 Meth(C) [ Var(C) | dep ⇤ Meth(C)[Var(C) (m, v) = True} [ {u 2 Con(C) | 9 t 2 MG(m) with dep(u, t) = True} . (1)
A MemberGroup MG(m) consists of the method m and contains all other member variables and methods of class C that depend on m. Additionally, all constructors belong to MG(m) if they have a dependency to a member of the existing group MG(m). As a result a constructor can belong to more than one MemberGroup if the constructor initializes member variables or calls methods that belong to distinct MemberGroups.

The members of a MemberGroup do not have dependencies to other members of the same class and represent a separate concern (or functionality) of the class. The internal state (value of the member variables) of a MemberGroup can only be modified using members of the MemberGroup. To relocate an entire MemberGroup, several conditions have to be met, which are discussed in the next section.

Relocation of MemberGroups

We describe two MemberGroup patterns, which can be applied in order to improve modularization of the source code. The relocation of "misplaced" MemberGroups is based on transformation rules, which are different for each pattern. Since it is not possible to relocate every arbitrary MemberGroup, some preconditions are necessary to restrict a MemberGroup before applying transformation rules. The common pattern (Section 3.1) specifies a MemberGroup that is not restricted in its composition but in its usage inside the software system. In contrast to the common pattern the strong pattern (Section 3.2) characterizes a MemberGroup that is restricted in its structure. To describe the patterns, the target classes for the MemberGroup relocation, and the transformation rules, we define several terms outlined in Table 2, which are used in the following sections.

The object-oriented concept of inheritance can lead to further restrictions because of possible dependencies between members of the ancestor class and members of the specialized class. A MemberGroup can not be relocated if members of this Member-Group are overridden by members of descendant classes or if they contain dependencies to ancestor classes. This is similar to methods in a MemberGroup that realize an interface. Other obstructive dependencies can be found between members of an inner class and a MemberGroup. In this case the restricted visibility of the inner class has to be taken into account when relocating the MemberGroup to another class. Even if relocation strategies in these contexts might be available, we focus on the relocation of members without these constraints in this article. The absence of dependencies caused by inheritance, interfaces, and inner classes concerning a MemberGroup are preconditions for the relocation of both patterns. Advanced programming techniques such as multithreading or reflection are not considered in this article.

To improve the modularization, a MemberGroup is relocated to a target class. After introducing the patterns a possible set of target classes is presented. These classes have to be determined according to the specific restrictions of the MemberGroup pattern. The transformation process is done by a transformation function moveMG, which automatically relocates a MemberGroup from a source class to a target class if all prerequisites for the relocation are satisfied.

Moving Common MemberGroups

To improve the modularization, a target class T has to be found to which a common MemberGroup in class S can be relocated safely, i.e., preserving the behavior of the software system. Therefore, the software system has to be inspected to locate possible target classes. A reference to a target class must exist in all classes that use the common MemberGroup (call methods or use member variables of the MemberGroup) of S. We assume that the source class S and the target class T are available as member variables in all classes that use the common MemberGroup. This excludes possible source and target classes that are used as local variables or as parameters in a method call. All classes that contain a member variable of type S and use a common MemberGroup in S are denoted as set M has (S):

M has (S) = {c 2 | 9 x 2 Var(c) with type(x) = S} . (2)
M has (S) is further inspected to find possible target classes that have to be member variables of all classes of M has (S). The set M ref ( , S) with as a subset of contains classes that are used as types of member variables in all classes of and are not equal to S.

M ref ( , S) = {t 2 | t 6 = S and (8 v 2 9 x 2 Var(v) with type(x) = t)} . ( 3
)
The set M tar (MG(m)) defines the set of possible target classes for the relocation of a common MemberGroup MG(m) located in the source class S. According to Equations ( 2) and (3) we define:

M tar (MG(m)) = M ref (M has (S), S) . (4)
A matching pair of member variables, one of type S and one of type T must be found to relocate the common MemberGroup. There can be multiple occurrences and instantiations of the source and target types within the containing class. Thus, different cases have to be taken into account. For the number of occurrences of the source and target types we can distinguish four cases. V(1:1) specifies the case in which there is one occurrence of a member variable of the source and one of the target type in the containing class. In the V(1:n) case a member variable of the source type appears once and the target type occurs multiple times as member variable in the containing class (e.g., in an array). The cases V(n:1) and V(n:m) are accordingly defined.

In a related way the number of instantiations of member variables of the source and target types have to be taken into account. Multiple instantiations of the source class cause reinitializations of the member variables of the MemberGroup. When relocating a MemberGroup its behaviour has to be preserved as well, e.g., a reinitialization of the source class must cause a reinitialization of the relocated MemberGroup in the target class. In an I(1:1) relation both member variables of type S and T are instantiated once (e.g., in the constructor of the containing class). In an I(1:n) relation T is instantiated at least twice. The cases I(n:1) and I(n:m) also exist.

Each individual case requires different relocation strategies to preserve the behaviour of the software. In the following we consider the case in which the source and the target types occur only once in all classes that use the common MemberGroup (V(1:1)). Additionally, we assume that both member variables are instantiated only once (I(1:1)).

One of the target classes that meet all preconditions is selected. We use code metrics to obtain a suitable candidate, which is described in Section 4. We summarize our preconditions for the relocation of a common MemberGroup from a class S to a class T as follows: a) If a constructor is part of the MemberGroup, it must be possible to move the Mem-berGroup-dependent statements to the constructor of the target class T. Therefore these statements must not depend on other variables not available in the constructor of class T. b) The source and target classes must be member variables in all the MemberGroup referencing classes and are permitted to occur only once (V(1:1)). c) Both variables of type S and T are supposed to be instantiated once (e.g., in the constructor of the referencing class) to meet the I(1:1) requirement.

The algorithm moveCommonMG relocates a common MemberGroup MG from a given source class S to a target class T. The reference to our software graph (project P) is required to modify all references from the source class to the target class in all classes of M has (S). The transformation process consists of the following steps. As a pre-transformation step all name clashes between the target class T and the common MemberGroup MG, which will be relocated to T, are resolved (line 3). Therefore the function detects equal identifiers, uniquely renames them, and updates referencing statements. Although renaming is necessary to avoid name clashes, a huge number of automatic renamings may lead to unreadable code. To avoid this effect, human interaction can be integrated in the renaming task. Afterwards, all constructor statements (line 6) and members (line 8) of the common MemberGroup are moved to the target class by the methods move-ConstructorStatements and moveMember. Finally, all references to members of the common MemberGroup of the original class S are updated to references to the relocated members in the extended class T by the method replaceMemberGroup-References (line 9). Fig. 1 shows the relocation of the common MemberGroup {m(), b(), var} from class Source to class Target.

Moving Strong MemberGroups

The strong MemberGroup pattern presented in [START_REF] Hunold | Pattern-based Refactoring of Legacy Software Systems[END_REF] defines a special type of Member-Groups that consist of exactly one public method m and a set of private members.

MG strong (m) = {m : vis(m) = public} [ {v 2 {MG(m) \ Con(C)} | v 6 = m : vis(v) = private} . (5)
Possible target classes are taken from the parameters of the method m. The motivation for the relocation is a high cohesion between the strong MemberGroup and the class of the parameter as well as the removal of the class dependency between the source class and the class of the parameter. As a precondition for the possible target classes we require the type of the relevant parameter not to be an interface. The target classes M tar (MG strong (m)) for the relocation of the strong pattern are given as:

M tar (MG strong (m)) = {p | p 2 M par (m) and
p is not an interface or a primitive type} .

In the strong MemberGroup pattern there is only one occurrence of variables of the source and target type involved (V(1:1)). To relocate a strong MemberGroup to a target class in M tar the following preconditions have to be fulfilled in order to preserve the behaviour of the software system. a) Like in the common MemberGroup pattern, the dependent constructor statements of strong MemberGroups must be movable as well. b) To keep the state of the strong MemberGroup inside the target class, it must be guaranteed that source and target classes are not reinstantiated (I(1:1)).

If a strong MemberGroup pattern is found and all previous preconditions are fulfilled, we can relocate the MemberGroup MG strong (m) to the target class T 2 M tar using the relocation function moveStrongMG:

Metrics and Tools for Analyzing and Relocating MemberGroups

We leverage code metrics to select an appropriate target class from a set of possible classes for the relocation of a MemberGroup (considered in Section 3). These metrics calculate the system improvement after the transformation by measuring the coupling between classes. A coupling between two classes exists if there is at least one dependency between members of both.

The Coupling Intensity (CINT) [START_REF] Lanza | Object-Oriented Metrics in Practice[END_REF] is used to determine the number of inter-class dependencies, which can be removed by relocating the MemberGroup. From a set of possible target classes the class with the highest value of CINT is used as target class. The CINT metric of the MemberGroup MG(m) and the members of the target class T 2 M tar is defined as follows:

CINT(MG(m), T) = {u ! v | u 2 MG(m) and v 2 M(T)} . ( 7
)
This metric does not measure the overall number of removed class couplings, but it is used to estimate the system improvement in advance. The overall number of resulting class couplings (see Equation ( 8)) depends on the actual dependencies to other classes of the source and target class. A class coupling between the source class and another class might be removed if only the MemberGroup references this class. But the coupling remains if the source class has additional references to this class. In the same manner the number of class couplings of the target class might be influenced.

In order to measure the improvement of decoupling and modularization of the whole software after applying the transformations, we use the Class Coupling metric (CC) (c.p. the Coupling Factor [START_REF] Brito E Abreu | Object-Oriented Software Engineering: Measuring and Controlling the Development Process[END_REF]), which indicates the number of dependent class pairs in relation to the maximum possible number of dependent class pairs. The CC is defined as follows whereas n denote the overall number of classes in the system:

CC = P n 1 i=0 P n 1 j=0,j6 =i c(C i , C j ) n 2 n with c(C i , C j ) =
( 1, dependency between classes C i and C j , 0, otherwise .

We intend to search for "misplaced" MemberGroups using the defined patterns and try to relocate the MemberGroups to other classes that fit better. Considering this, we state that a lower class coupling is an improvement in separation of concerns and modularization of a legacy software system.

To support the programmer in different reengineering and restructuring tasks we developed the toolset TRANSFORMR [START_REF] Hunold | Transformation of Legacy Software into Client/Server Applications through Pattern-Based Rearchitecturing[END_REF]. It extracts the software graph from the source code, based on abstract syntax trees, and offers transformation operations. As part of this work, the TRANSFORMR toolkit was extended to support the analysis and transformation of MemberGroups. This new module of TRANSFORMR is called the Member-Group browser, see Fig. 4. The browser lists all MemberGroups in a selected software element and restricts the resulting MemberGroup list with user-defined constraints. With these constraints MemberGroups of a software system that match the strong or the common pattern are determined and it is detected whether a MemberGroup is movable or not. In the next section the presented patterns and metrics are applied to two open source projects to test their usability and benefit in a real world evaluation.

Case Study

In a case study we applied our approach for MemberGroup detection and transformation on two open source projects written in Java: JMeter (ver. 2.3.2, 80 KLOC, 794 classes), a desktop application designed to test and measure performance of various network services, and jEdit (ver. 3.4, 95 KLOC, 940 classes), a cross platform text editor.

To measure the effect of applying the presented MemberGroup patterns on the two projects, a software graph was constructed using TRANSFORMR. Based on the graphs we calculated the original coupling metrics and tested the preconditions to move all MemberGroups found with the MemberGroup browser. A small number of movable MemberGroups was found on which we applied the transformations from Section 3 for each pattern. In order to gain information about the code improvement, we recomputed the CC metric and recorded the removed and newly added class dependencies for each pattern.

Table 3 lists the number of movable and not movable MemberGroups with the specific pattern for JMeter and jEdit. As we can see the number of movable MemberGroups is very low in relation to the number of the not movable groups. The main difficulty of the strong pattern is the absence of an appropriate parameter used as target class in the publicly used method. In about 90 % of the not movable MemberGroups the method had no parameters, the parameters were interfaces or primitive types, or were identified as external library classes. Additional 8 % of the not movable MemberGroups do not have any dependencies to the target class, so no improvement of the decoupling between source class and target class could be expected. A few not movable Member-Groups have members, which could not be moved because of inheritance restrictions. In case of the common pattern, the most frequent cause that a MemberGroup cannot be relocated was due to inheritance of members. Another reason for not movable Member-Groups was the assumption that the source and target classes are only used as member variables in all classes that use the MemberGroup. The source class of many not movable MemberGroups was additionally used as parameter or local variable, or the source class was never used as a member variable in all classes that use members of the Mem-berGroup. These causes sum up to about 95 % of all identified common MemberGroups in both investigated projects. Since the toolset TRANSFORMR cannot yet detect whether a method realizes an interface or uses methods of an ancestor class of a library, some MemberGroups are incorrectly marked as movable (false positives in Table 3). This problem could be solved either by additionally inspecting all libraries with TRANSFORMR (possible for most open source projects) or by using sophisticated compiler tools (like Eclipse JDT) to detect the inheritance properties of methods.

Despite the previously mentioned problems, we found that we can reduce the code coupling by relocating MemberGroups with the outlined patterns. Table 4 shows the results with the number of pair couplings in both projects and the improvement of the CC metric after applying both patterns. There is only a small improvement of the coupling in both projects because we could only relocate a small number of MemberGroups (see Table 3). In summary all relocated MemberGroups lead to an improved modularization as dependencies between classes could be removed. Interestingly enough, the relocation leads to a higher coupling regarding the strong pattern for jEdit. This is due to couplings of the relocated MemberGroup that had to be added to the couplings of the target class. Unfortunately, in the source class these couplings could not be removed since the source class contains further dependencies to these classes. But even in this case we could decouple source and target classes. The results show that an automated relocation of MemberGroups can help to decouple software.

Related Work

Restructuring and refactoring to improve the quality of software in the context of software evolution has been intensively studied. Fowler et al. introduce many refactorings and design patterns for object-oriented languages as solutions for common mistakes in code style in order to make the software easier to understand and cheaper to modify [START_REF] Fowler | Refactoring: Improving the Design of Existing Code[END_REF]. Tests support the proposed manual changes to verify the correctness afterwards. A case study in a close-to industrial environment [START_REF] Moser | A Case Study on the Impact of Refactoring on Quality and Productivity in an Agile Team[END_REF] also indicates that "refactoring not only increases aspects of software quality, but also improves productivity". Mens and Tourwé describe the need for automatic support but also state the limits and drawbacks of automated restructuring (e.g., untrustworthy comments, meaningless identifier names) [START_REF] Mens | A Survey of Software Refactoring[END_REF]. They present transformation operations and discuss the behaviour preservation including the consistency of unit test results. In [START_REF] Seng | Search-Based Determination of Refactorings for Improving the Class Structure of Object-Oriented Systems[END_REF] an approach with evolutionary algorithm usage is described. By simulating refactorings with its pre-and postconditions classifications are done. The variation, which improves the class structure best, is chosen. With an open source case study the potential of the method is shown. The so-called searchbased determination of refactorings is introduced in detail in [START_REF] O'keeffe | Search-based Refactoring for Software Maintenance[END_REF]. A framework, which uses object-oriented metrics for detecting error-prone code for particular transformations, is proposed in [START_REF] Tahvildari | Improving Design Quality Using Meta-pattern Transformations: a Metric-based Approach[END_REF]. After analyzing the impact that various transformations have on functional and non-functional software metrics, and by modeling the dependencies between design and code features, the potential to enhance the target qualities and requirements (soft-goals) for the new system can be shown. Automatic refactorings may also benefit from considering of antipatterns [START_REF] Brown | Anti-Patterns: Refactoring Software, Architectures, and Projects in Crisis[END_REF]. Brown et al. describe (in contrast to the various how-to-publications) common mistakes in every step of software engineering procedures and show better alternatives. These problem information can be used for metrics as well as for the manual choice of refactoring tasks. The points-to analysis [START_REF] Rayside | Points-To Analysis[END_REF] was initially developed for structured programming languages. It still has its eligibility for object-oriented languages, as references-to analysis [START_REF] Ryder | Dimensions of Precision in Reference Analysis of Object-oriented Programming Languages[END_REF]. The (dynamic) knowledge of referencing objects can support complex refactorings. Some of our current restrictions may be eased by using reference analysis.

Conclusions

This article presents a pattern-based approach to relocate sets of member variables and methods to automatically improve the modularization of legacy software systems. The key element of the described modularization of software is the so-called MemberGroup, which represents a separate concern or a service in a class. The MemberGroup is independent of the rest of a class and can therefore be relocated entirely to a target class. This relocation however has several constraints on the structure of a MemberGroup as well as on the referencing classes. We show which constraints have to be fulfilled in order to automatically apply the relocation operations on the legacy software without user interaction. We also demonstrate how the relocation of MemberGroups can be integrated into reengineering toolsets, such as TRANSFORMR. In a case study the approach was applied to two open source systems and the results have shown that the automatic relocation of MemberGroups can lead to less coupled legacy code (less code dependencies).

In future work we intend to enhance the common MemberGroup pattern with additional relocation strategies to rise the number of possible MemberGroup relocations. The integration of inheritance and language specific constructs like inner classes in the MemberGroup definition is also a promising goal for additional options to modularize.

Fig. 1 .) 2 begin 3 resolveNameClashes ( MG , T ) ; 4 for each member M in MG : 5 if M in constructors ( S ) : 6 moveConstructorStatements ( S , M , T ) 7 else : 8 moveMember ( S , M , T ) 9 replaceMemberGroupReferences

 123456789 Fig. 1. TRANSFORMR call diagrams. Ellipses denote member methods and rhombi denote member variables. Left: Relocatable common MemberGroup {m(Target), b(), var} in class Source. Right: Relocated MemberGroup into Target.

Fig. 2 .

 2 Fig. 2. TRANSFORMR call diagrams. Left: Movable strong MemberGroup {m(Target), b(), var} with exactly one publicly used method and the target class as parameter. Right: Relocated MemberGroup with decoupled classes Source and Target.

Fig. 2

 2 Fig. 2 shows an example of a strong MemberGroup, emphasized in class Source. A strong MemberGroup can be built upon the (common) MemberGroup definition of Equation (1) defined as follows:

Fig. 4 .

 4 Fig. 4. Left: MemberGroup browser (in the tree view of the software), showing the list of all Mem-berGroups in the selected software graph node and two constraints limiting the MemberGroup list, arranged below. Right: Move MemberGroup dialog indicating the move possibilities.

Table 1 .

 1 Elements of the graph structure used to model the software system.

	Description	Definition
	Set of all classes Set of all constructors of a class C	= {C0, . . . , Cn} Con(C)
	Set of all methods of a class C	Meth(C)
	Set of all member variables of a class C	Var(C)
	Set of all members of a class C Set of all members of a project	M (C) = Meth(C) [ Var(C) [ Con(C) M = S C2 M(C)
	Read/write/call reference from u to v (between arbitrary members u and v)	u ! v
	Dependency of class members Transitive dependency of class members over a subset of all members U ✓ M	dep(u, v) = (u ! v) _ (v ! u) dep ⇤ U (u, v)

Table 2 .

 2 Functions and sets of classes to define MemberGroup transformation rules. Set of classes in \ {S} which each reference S and are referenced by all classes of the set Mref ( , S)

	Description	Definition
	Type (class) of a variable v	type(v)
	Visibility of a member u	vis(u) (public or private)
	Set of all types of parameters of a method m	M par (m)
	Set of classes which have a member variable of type S	M has(S)
	Set of possible target classes for MemberGroup MG(m)	Mtar(MG(m))

Table 3 .

 3 Number of movable and not movable MemberGroups in analyzed projects. The parenthesized values denote the identified but even so not movable MemberGroups (due to constraints not yet detectable by our toolset).

	project, type of pattern movable (false positives) not movable
	jEdit, strong	2 (15)	851
	jEdit, common	3 (27)	1600
	JMeter, strong	4 (8)	897
	JMeter, common	6 (32)	1678

Table 4 .

 4 Improvements in the number of pair couplings (p.c.). A lower number denotes coupling improvement and a higher number denotes a decline in the coupling,

	project original p.c.	strong p.c.	common p.c.	P
	jEdit	4453	4455 (+0.04 %) 4449 ( 0.08 %) 4451 ( 0.04 %)
	JMeter	3698	3694 ( 0.1 %) 3697 ( 0.03 %) 3693 ( 0.14 %)

Acknowledgment

The transformation approach described in this article as well as the associated toolkit are part of the results of the joint research project called TransBS funded by the German Federal Ministry of Education and Research.

Similar to the relocation algorithm for the common MemberGroup the function moveStrongMG resolves overlapping identifier names and moves the members and dependent constructor statements (lines 3-8). After the relocation of the strong Mem-berGroup the formerly used variable of type T, passed to the method m as parameter, is unnecessary. The occurrences of the parameter inside the strong MemberGroup are redirected to the target type (line 9). The relocation has to be propagated to the referencing classes of the public method m. All references to classes that used the strong MemberGroup inside the source class S are replaced by references to the target class (line 10). Finally, the formerly used parameter in the method m can be removed in all occurrences of the method m (line 11).

Fig. 3 displays the source code of class Another (illustrated in Fig. 2). The class is shown before (left) and after (right) the relocation of the strong MemberGroup. Line 8 contains the result of the relocation function: Method m and all other members of the MemberGroup are relocated to parameter class Target followed by the removal of the parameter of m. An additional improvement can be achieved if the reference to the source class becomes unused in a class that originally invoked the publicly used method m of the relocated strong MemberGroup (line 7). In this case an additional class dependency between the classes Another and Source can be removed, which leads to another modularization improvement.