maintenance of assertions consistency across code refactorings

76
Maintenance of Assertions Consistency Across Code Refactorings A Dissertation Submitted In Partial Fulfilment Of The Requirements for the Degree Of MASTER OF SCIENCE In Software Engineering In the FACULTY OF SCIENCE THE UNIVERSITY OF READING by Josep Coves Barreiro February, 2006 University Supervisors: Daniel Rodríguez Vassil Alexandrov

Upload: josep-coves-barreiro

Post on 14-Aug-2015

50 views

Category:

Documents


1 download

TRANSCRIPT

Page 1: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

A Dissertation

Submitted In Partial Fulfilment Of The Requirements for the Degree Of

MASTER OF SCIENCE

In

Software Engineering

In the

FACULTY OF SCIENCE

THE UNIVERSITY OF READING

by

Josep Coves Barreiro February, 2006

University Supervisors:

Daniel Rodríguez Vassil Alexandrov

Page 2: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

2

Josep Coves

Abstract Assertions are formal constraints which are inserted as annotations in a source program text. Assertions are widely used in the software industry today, primarily to detect, diagnose and classify programming errors during testing. If a source program has been annotated with assertions, and the code was subjected to refactoring, then it is natural that the original assertions would no longer be consistent with the refactored code. The main focus of this dissertation is to specify how to maintain the assertion consistency through the refactoring process. In order to do this task properly, we also considered design-by-contract as a powerful tool to decide whether assertions were maintained or not. We created a novel refactoring classification depending on their assertion maintenance. We found there are some refactorings which are automatically maintained (see 3.3.1. Trivial assertion maintenance), some which require changes (see 3.3.2. Refactorings which require changes) and finally some that need the intervention of the programmer because they can not be automated (see 3.3.3. Refactorings which require assertion redefinition). We also described a schema to be able to decide if a given refactoring can be maintained and how to maintain it (see 3.4). In particular, we detailed and demonstrated the steps required to maintain the assertions through Pull Up Field, Pull Up Method and Self Encapsulate Field refactorings which we called Augmented Refactoring Steps. To maintain Self Encapsulate Field refactoring we created and demonstrated a procedure for extracting the precondition of a setting method from the class invariant (see chapter 5). This procedure can be helpful to maintain future refactorings, but it also can be helpful in many other areas such as Artificial Intelligence where data extraction from predicates is very common. Finally we implemented a small package in order to demonstrate the precondition extraction procedure was possible to implement. This package can be used in time to integrate the Augmented Refactoring Steps in any current refactoring tool, such as Eclipse or NetBeans. Josep Coves Barreiro University Of Reading

Page 3: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

3

Josep Coves

Acknowledgments It is impossible to mention all those who, in a direct or indirect way, have helped me through all the work for the degree. Thanks to all of them. First of all I want to give thanks to my family who have supported me during all my life, and made possible to finish my degree here in England. I am really thankful to Mercè, my mother, that has helped me, has loved me, has suffered for me, and has encouraged me during all my life and also particularly during my staying out of home. I also thank Dolors, my grandmother that has also taken care of me, just like my uncle Josep and my aunt Gemma. I owe thanks to several friends that has accompanied me through all the degree in Barcelona, first of all to Roger who helped me to start the university, also specially to Dani who has helped me to live my last years of university in a more real and deep way, helping me to be in first person and not simply letting the things pass by. I thank Alberto who is now starting the degree. I thank Joan for being always there, since the beginning until the end, like Carol and Albert. I want to thank in a particular way the teachers who made my love to Computer Engineering grow, thanks to J. Sistac, F. Tiñena, J.M. Llaberia, D. López, J. Petit and S. Roura. I also want to thank all the friends who have helped me during my dissertation in Reading. I am specially thanked to David with who I spent most of the time, thanks for his support, his comprehension and his patience, but most of all for his friendship. I am also thanked to Antonio, Patrick, Rudi, Francesco, Irene, Lucía, Paula, Elisa and Marieke. I also particularly thank Amos, Gianluca and Tristan for helping me to improve my English. I am especially thankful to my girlfriend Alba for her support in everything, including my studies. As nobody else, she picked me up the slack and encouraged me in an extraordinary way. She deserves an award for loving me more for who I am rather for what I do. For the same reason I thank my friends Víctor, Joan, Dani, Alberto, Miquel Carreras, Ignacio, David, Joan, Clara, Àlex, Marta. For the ones who know me better than I do, helping me to live according to what I really desire; thanks to Lluís for the opportunity he gave me, to Miquel for his patience with me, to Jorge, to Garci, to Dima, to Mn. Josep, and specially to Amos, Rudi and Gianluca who I met here, I want to thank them particularly for his support during all my stay in this country. I can not forget Daniel, my supervisor who helped me not only technically; he made me feel like I was at home. Thanks for his patience, his guidance and for comforting me to finish my degree even when the things were not clear. He invested in me his most valuable resource: his time. I would like to thank in a very special way Don Giussani’s help, he is like a father for me and he watches over me every day. Finally, I am thankful to God for having granted me the skills and opportunities that made me possible to finish this humble dissertation and for giving me everything I am thanked for.

Page 4: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

4

Josep Coves

Table of Contents Maintenance of Assertions Consistency Across Code Refactorings . 1 Abstract.......................................................................................... 2 Acknowledgments .......................................................................... 3 1 Introduction............................................................................... 6 1.1. Introduction .................................................................................. 6 1.2. Research Objectives ....................................................................... 6

2 Review of literature ................................................................... 7 2.1. Assertions..................................................................................... 7 2.1.1. What assertions are................................................................. 7 2.1.2. Why should we use assertions? ................................................. 7 2.1.3. General classification of assertions............................................. 8 2.1.4. OO Assertion Classification ....................................................... 8 2.1.5. Most useful assertions: A first criteria for writing assertions......... 11

2.2. Design by Contract....................................................................... 13 2.2.1. What design-by-contract is.................................................. 13 2.2.2. Searching for an ultimate link .............................................. 13 2.2.3. The pretension of design-by-contract .................................... 13 2.2.4. One small example............................................................. 14

2.3. Refactorings................................................................................ 17 2.3.1. What refactoring is ................................................................ 17 2.3.2. When to apply refactoring....................................................... 17 2.3.3. Refactorings classification....................................................... 19

3 Maintaining Assertions through Refactoring Process ............... 22 3.1. Introduction ................................................................................ 22 3.2. Some examples to understand the problem ..................................... 22 3.2.1. The Inline Method Refactoring................................................. 22 3.2.2. The Move Method Refactoring ................................................. 24 3.2.3. The Encapsulate Field Refactoring............................................ 25 3.2.4. The Remove/Add Parameter Refactorings ................................. 26 3.2.5. The Replace Conditional with Polymorphism Refactoring.............. 27

3.3. A first assertion maintenance refactoring classification ...................... 29 3.3.1. Trivial Assertion Maintenance .................................................. 29 3.3.2. Refactorings which require changes ......................................... 29 3.3.3. Refactorings which require assertion redefinition........................ 30

3.4. A small schema to maintain Refactorings......................................... 30 3.4.1. Introduction ......................................................................... 30 3.4.2. How to use this schema ......................................................... 30 3.4.3. A small schema..................................................................... 31

4 Augmented Refactoring Steps.................................................. 34 4.1. Introduction ................................................................................ 34 4.2. Pull Up Field ................................................................................ 34 4.2.1. Classification of Pull Up Field Refactoring .................................. 34 4.2.2. Demonstration ...................................................................... 34

4.3. Pull Up Method ............................................................................ 37 4.3.1. Assertion Maintenance Steps................................................... 37 4.3.2. Demonstration ...................................................................... 38 a. Demonstration of step 3.a.i........................................................ 38 b. Demonstration of step 3.c.......................................................... 39

4.3.3. Example............................................................................... 40 4.3.4. Classification of Pull Up Method Refactoring............................... 41

4.4. Self Encapsulate Field................................................................... 43 4.4.1. Some important facts before facing the refactoring .................... 43 4.4.2. Assertion Maintenance Steps................................................... 44

Page 5: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

5

Josep Coves

4.4.3. Demonstration ...................................................................... 45 a. Adding a precondition to the setting method................................. 45 b. Adding a postcondition to the setting method ............................... 46 c. Adding a precondition and a postcondition to the getting method .... 46 d. Checking the class invariant in both methods................................ 47

4.4.4. Classification of Encapsulate Field ............................................ 47 5 Extraction of the setting method precondition from the CI ...... 48 5.1. Introduction ................................................................................ 48 5.2. Extraction Procedure Steps............................................................ 48 I. Forging a Binary Tree ................................................................... 48 II. Replacing the Variable name ...................................................... 48 III. Applying ASR and OSR substitution rules ..................................... 48 IV. Logical Simplification of the result tree ........................................ 49 V. Reconstructing the Precondition from the tree.................................. 49

5.3. Demonstration of the Extraction Steps ............................................ 49 5.3.1. Some facts we must remember ............................................... 49 5.3.2. Forging a Binary Tree Demonstration ....................................... 49 5.3.3. Replacing the Variable name Demonstration.............................. 49 5.3.4. ASR and OSR rules demonstration ........................................... 50 5.3.5. Logical Simplification of the result tree Demonstration ................ 53 5.3.6. Reconstructing the Precondition from the tree ........................... 53

5.4. Proposed precondition extraction pseudocode algorithm: ................... 53 5.5. Some examples of CI derivation..................................................... 54 5.5.1. Encapsulating the year field of a Movie ..................................... 54 5.5.2. Encapsulating the empty field of a bounded Stack...................... 56

5.6. A cheating case: correlated updates ............................................... 59 5.7. Possible future optimizations ......................................................... 60 5.7.1. The CNF optimization ............................................................. 60 a. CNF Transformation problems........................................................ 60 i. Preconditions too long ............................................................... 60 b. Cheating predicates .................................................................. 61

5.7.2. Simplify by applying logical rules ............................................. 61 6 Implementation ....................................................................... 63 6.1. Dealing with implementation ......................................................... 63 6.2. Implemented Application .............................................................. 64 6.2.1. Base package ....................................................................... 64 6.2.2. Visual application interface ..................................................... 65 6.2.3. Application restrictions and future extension ............................. 66

6.3. Testing....................................................................................... 67 6.3.1. Common Examples ................................................................ 67 a. Encapsulating the year field of a Movie ........................................ 67 b. Encapsulating the empty field of a bounded Stack ......................... 69

c. CNF Examples ............................................................................. 70 i. Good CNF Optimization.............................................................. 70 ii. Too long precondition................................................................ 71

7 Conclusion and future work ..................................................... 72 8 References............................................................................... 73 9 Appendix – Bertrand Meyer’s rules ......................................... 75

Page 6: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

6

Josep Coves

1 Introduction

1.1. Introduction The maintenance of a program is very expensive work, both in time and money. In our current world, where maintenance and updating of software tools is becoming more important everyday, programming languages have evolved in order to make the programmers’ job clearer and easier. In consequence, many tools have appeared to improve this task inside the same programming language and also external tools. One of the tools created inside many programming languages are assertions. Assertions are formal constraints which are inserted as annotations in a source program text. Assertions are widely used in the software industry today, primarily to detect, diagnose and classify programming errors during testing. Assertions are a powerful tool to avoid future errors and bugs which, otherwise, would require a lot of debugging time to find out. Another powerful tool, the implementation of which is still in development process, is refactoring. Refactoring is an external tool that helps the programmer to rebuild the structure of a source code in order to improve or clean it, to make its maintenance easier and quicker. If a source program has been annotated with assertions, and the code was subjected to refactoring, then it is natural that the original assertions would no longer be consistent with the refactored code. The main focus of this dissertation is to specify how to maintain the assertion consistency through the refactoring process. In order to do this task properly, we also considered design-by-contract as a powerful tool to decide whether the assertions are maintained or not.

1.2. Research Objectives The aim of the dissertation is to define a set of steps and rules in order to adapt the assertions during the refactoring process. Since there is not a complete definition of all kind of refactorings (and this is impossible, because new kind refactorings are being continuously developed to solve new problems) the goal of this project is not to define a set of rules for each refactoring, but to define some of the most important refactorings and make an abstract classification in order to make it easier to understand what we can do to adapt the assertions in the new refactorings that can appear in time. To do all the work above, a deep study of assertions, refactorings, Object Oriented properties and Design-by-contract scheme is needed (the last two will be necessary for the creation of the steps and rules). Finally, one of the proposed tasks is an implementation of a small package which provides some of the required methods to maintain the assertions after the refactoring process. This small package can be in time used for the extension of current refactorings. A small proof of concept application will be also needed in order to demonstrate the package works properly.

Page 7: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

7

Josep Coves

2 Review of literature In the following sections we are going to describe the main concepts in which our dissertation is based. Those are Assertions, Design-by-contract and Refactorings.

2.1. Assertions

2.1.1. What assertions are Assertions were created by Hoare as an Axiom System to show the correctness on Algol programs[1]. Rosenblum defines assertions as: Assertions are formal constraints on software system behaviour that are

commonly written as annotations of the source text. The primary goal in writing assertions is to specify what a system is supposed to do rather than how it is to do

it. [2] As we can see from the above definition, assertions are a tool which helps us to develop software according to its specification (what is it expected to do) instead of its implementation (how it is expected to do it). This definition matches perfectly with the Object Oriented paradigm. Assertions are Boolean expressions which helps us to verify the state of the program in a certain point of the execution. In other words, they are a way to express our state assumptions. If the assertion is evaluated to True it means the state satisfies the desired constraints, while if it is evaluated to False, it means that the program has entered into an inconsistent state, and so, we cannot rely on the result even if the output was correct. Another important property of assertions is that they are not expected to add any side effect to the program state.

2.1.2. Why should we use assertions? Since we can express assumptions of the run-time program state, we can use assertions as a way of documentation (instead of writing comments) with the advantage of being notified in case our state suppositions were not true. Following this idea, we can define the specifications of an object oriented program, even before having the code implemented, by expressing its method preconditions and postconditions. This idea is used in languages as Eiffel, describing this use of assertions as Design by Contract: the notion of a contract in object oriented programming describes the relationship between a supplier class and a client class

and can be expressed as pre- and post-conditions on methods, and invariants on classes.[3] Furthermore, since assertions are a way of verifying suppositions in any point of the code, we can detect implementation errors as well. We can save a lot of the debugging time that we would expend searching for an error, since an assertion fail gives us detailed information about the error (which we won’t have searching with the debugger). Despite the fact that Rosenblum observed there was no clear correlation between the location of an error and the place where the assertion which revealed the error was [2], M. Satpathy, N. T. Siebel and D. Rodríguez during the development of a software project (ADVISOR) found out that the proper placing of assertions was often helpful in determining

Page 8: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

8

Josep Coves

the location of an error [4]. As a consequence, programming with assertions, we can have our programs running in a quicker way and with a higher confidence in the program’s correctness.

2.1.3. General classification of assertions Rosenblum described a general classification of assertions in the context of software maintenance and testing [2]. He found two main categories: (a) Specification of Function Interfaces (b) Specification of Function Bodies

The first category of assertions describes the behaviour of the function, independently of its implementation (black-box view), while the second one aims to ensure its correct implementation (white-box view). We will not explain in further detail the sub-categories of Rosenblum’s classification because we found a better classification for our scope which includes this one.

2.1.4. OO Assertion Classification In the context of our scope, we need to know how we can classify assertions in an Object Oriented system, because once we have the system properly “asserted”, we want to know if those assertions are still consistent through the refactoring process. In [5], M. Satpathy, N. T. Siebel and D. Rodríguez describe an Object Oriented assertion classification, which we will use as a reference. While they were developing a software project (annotated with assertions) they found out that Rosenblum’s classification was not enough, since they realised that out of the 12 categories of assertions found by Rosenblum only 4 categories, and additionally

one new category of assertions (assertions to detect the impact of known errors)

was used in their project [5]. According the key properties of object oriented programs it is possible to classify the assertions in two main categories: (a) intra-class properties

(b) inter-class properties The first category classifies the properties which are expected to hold within a class, while the second are the ones expected to hold when two classes interact. There is another first category between run-time assertions and compile time assertions, the main categories above are from the second type. (i) Compile time assertions

It is possible to define one kind of assertions to be checked at compilation time. If the assertion fails, the compilation ends with an error. Macros perform this kind of condition checking, they are usually used to verify expressions that use the sizeof operator. Another example of this kind of assertions could be the keyword const in C++, it does not change the object state and it gives a compilation error if the source code is not consistent with that constraint. (ii) Intra-class properties

a. Class Invariants An object is an implementation of an abstract data type (ADT) [6]. The characteristics of an ADT are: name of the ADT, a set of functions to interact with the ADT, a set of axioms to define the proper behaviour of the functions, and functions preconditions. In the implementation process, the set of functions

Page 9: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

9

Josep Coves

become methods, function preconditions become method preconditions, and the axioms become class invariants. We can define a class invariant as a predicate, which must be true before and after each method invocation. It is easy to see that we can implement this constraint using assertions. For instance, in the JAVA Sun documentation (http://java.sun.com), they propose to implement class invariants as a verification function (boolean), which may be called after the execution of each public method and constructor by an assertion [7]. For example, if we are implementing a balanced tree we want to assure that the tree is always balanced. Following the JAVA documentation, we may write a function like this:

And we should call this function after each public method and constructor call in this way:

b. Method preconditions An assertion can be written to validate the precondition of a method. The precondition checks the validity or the dependency between input arguments. In the JAVA documentation it is told not to put assertions as preconditions of public methods, because those specifications should be correct whether the assertions are enabled or not, and also because if there is an assertion failure, the program will not throw the appropriate exception [7]. This kind of assertions can show:

• Consistency between function arguments Useful to check a constraint between the arguments of the function. For example, we define a stack sum, which only can sum stacks of the same size. It is possible to define an assertion like this to check this precondition:

• Appropriate bounds on input values. Those are constraints to check range restrictions of any kind (subrange restriction, not null pointers, and proper string termination character)

• Validity of the context in which the function is called. To check

context assumptions. For instance, we want to check that a function is only called when a global constraint is true.

c. Postconditions

Assertions at the end of a method can show:

• Dependency of return values on function arguments. It is possible to define a constraint between arguments which has to be true at the end of the method.

private void sum(Stack A, Stack B){ assert size(A) == size(B); …

}

… assert balanced();

}

private boolean balanced(){ … }

Page 10: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

10

Josep Coves

• Effect of the call on the class state. For instance, we may want to verify that a delete function really deletes what it was expected to erase.

• The call not producing any undesirable side-effect on class state. • Validity of return values. As in the appropriate bounds in value

inputs, we can check the range at the end of the method is in the correct bounds.

d. Assertions within function bodies

This is the Specification of Function Bodies of Rosenblum’s classification. All the assertions which check implementation assumptions are placed here. The most important properties which can be expressed with assertions are:

• Strengthening of condition in the else-part (default case) of an if-statement (switch statement). We can put an assertion to verify that the execution flow has entered in the correct case by putting an assertion checking the bounds, for instance.

• Consistency between related data. To check all kind of

relationships that should be true between two data items. • Loop invariants. Constraints which should be true before and after

each loop iteration. This kind of assertions is not easy to express most of the times, or maybe they are impossible to express with the power of the programming language.

• Loop variants. Are expressed with non-negative variables which

decrease at each iteration, used to show loop ending. It is possible to express them with an assertion.

• Array index invariants. Since the array out of bounds is a really

frequent error, this is a very useful kind of assertion to check the correctness of the index variable.

• Assertions for reliability. When a program has a key functionality,

sometimes it is useful to implement two algorithms to do the same task. This kind of assertions can check if there is a mismatch between the results of both algorithms.

• Assertions to detect the impact of known errors.

This is the type of assertions which were not in Rosenblum’s classification [2] and where found out during the AVITRACK project [5]. They described this kind of assertions in this way: When code contains known bugs or incomplete implementations of functionality these are sometimes not corrected because of lack of resources. In order to detect the case where a problem may arise from

these conditions appropriate assertions can be added to the code [5]. • Other assertions. Assertions can be used to check common errors

like division by zero, arithmetic overflow, null objects… (iii) Inter-class properties

Page 11: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

11

Josep Coves

This kind of assertions helps us to ensure that some properties are preserved when two classes interact through inheritance, aggregation or method calls.

o CI of a Derived Class

A derived class strengthens the invariant of the original class. Let us suppose the base class has InvB and the derived class InvD. We can put an assertion as a postcondition of the derived class constructor in order to ensure that InvB Λ InvD is satisfied (this should be the new CI of the derived class, which strengthens the one of the base class), and we can put an assertion after the base class constructor to show that InvB holds.

a. Indirect Invariant Effect Even when the CI is hold, when we are programming with pointers, sometimes we need extra assertions in order to know if certain key properties are still preserved. The example suggested in [5] is a double linked list, which each node object has a forward and backward pointer. To hold a property like this: (this -> forward != NULL) implies (this -> forward -> backward == this)

The cooperation of both objects is necessary. If the forward pointer of A links with object B, and B does not set its backward pointer, then the assertion is violated. To check this property is not violated an assertion can be used.

b. Assertions to satisfy Liskov Substitution Principle (LSP)[8] The LSP says: functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it [5]. It means, if A object uses B object, and we change B to C (a derived class of B), A must maintain its consistency. For instance, if we have a function f in B with type (A -> B), then its redeclaration in C, must have the type (A -> C) provided C is a derived class of B. This means that the type of the input parameters of any redeclared function must be the same, and the returning type must be a subtype of the virtual function in the base class. Although following the above constraints, LSP may be violated even if the base class has the virtual function noted with assertions defining its pre- and post-conditions. To check the principle, we can use Bertrand Meyer’s Assertion Redeclaration Rule which says: “A routine redeclaration may only replace the original precondition by one equal or weaker, and the original postcondition by one

equal or stronger”[6]. Let us assume the original pre- and post-conditions and the body are Pre, Post and Body, and the new ones are Pre’, Post’ and Body’. To be sure the new implementation (Body’) is still consistent with the system, and in consequence holding the LSP, we can add assertions to verify (following Bertrand Meyer’s rule): Pre → Pre’ Λ Post’ → Post.

c. Design by Contract In next section we are going to study deeper the relationship between design-by-contract and assertions.

2.1.5. Most useful assertions: A first criteria for writing assertions

Page 12: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

12

Josep Coves

During the development of the ADVISOR project in [4], 52% of the total code size was checked with assertions. After their wide experience putting assertions into the code they pointed out the most useful assertions. (i) Context in Which Function is called

These kinds of assertions are the validity of the context in which the function is called, explained in the section Method Preconditions. (ii) Subrange Membership of Data

In this category of assertions we consider the Array index invariants and the Appropriate bounds on input values (iii) Non-null Pointers

This section matches with Validity of return values (iv) Consistency Between Related Data

Here we consider the relationships between data in the input and output parameters and the variables within a class. So we consider: consistency between function arguments, dependency of return values on function arguments and consistency between related data. (v) Assertions to Detect the Impact of Known Errors

We explained this kind of assertions in the above section.

Page 13: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

13

Josep Coves

2.2. Design by Contract We considered design-by-contract an important scheme that helps us to use assertions in an optimal way, because it rules how and when to write assertions, that is, deciding when an assertion is needed and when it is correct or not. In the last section assertions were considered useful only because of their impact in helping to detect errors. We see the design-by-contract as a criterion which measures the utility of assertions in a more objective way, that is, in front of the application goal. In next sections we will see it with more detail.

2.2.1. What design-by-contract is The Design by Contract principle[6] represents the relationships between a class and its clients as a formal agreement. Thus, we can express the contract as: "If you, the client, guarantee certain preconditions, then I, the supplier, will establish certain other results when I return to you. If you violate the preconditions, then I

promise nothing.” Following this concept, the assertions are a perfect tool for expressing each party’s rights and obligations, because, for instance, with them we can express the client obligations with preconditions, and the supplier obligations with postconditions. And unconsciously we are determining whether a precondition is correct or not.

2.2.2. Searching for an ultimate link Until now we were talking about assertions as an entity itself. We could write an assertion as a method precondition or as a CI, without taking care of the rest of the code. Although we can write assertions in separate pieces of code and with different rules, we always have in mind one question: what links everything? Since we know we are programming software to reach one concrete goal, the code and the assertions inside that code should point the same objective. That is, in other words, which is the criterion in order to know if an assertion expresses the correct precondition or the correct CI? What links the concrete assertion with our ultimate goal?

2.2.3. The pretension of design-by-contract Meyer defined design-by-contract[6] with the pretension of being the best methodology to build reliable software. Meyer defines reliability as correctness and robustness. He aims that before design-by-contract the only way to ensure this property was trusting that their authors will have applied all the required care, including extensive testing and other validation techniques[6] (page 68). Since design-by-contract we have a formal definition of class correctness, and a way to ensure whether a class is correct: A class like any other software element, is correct or incorrect not by itself but with respect to a specification. By introducing preconditions, postconditions and

invariants we have given ourselves a way to include some of the specification in the class text itself. This provides a basis which assess correctness: the class is

correct if and only if its implementation, as given by the routine bodies, is consistent with the preconditions, postconditions and invariant.[6] (page 369).

If a class is equipped with assertions, it is possible to define formally what it

means for the class to be correct. [6](page 407)

Page 14: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

14

Josep Coves

Now we have the criterion which gathers everything. As we can see Meyer focuses the design-by-contract mainly in the preconditions, postconditions and class invariant assertions. For that reason, he defined a set of rules in order to know the correctness of those assertions in many different situations. We have written down the most important rules for our dissertation in the appendix. If we guarantee those rules, we are guaranteeing design-by-contract, that is, the correctness of the assertions. Therefore, design-by-contract is the criterion used to know whether an assertion is useful or not.

2.2.4. One small example Let us suppose we have the following stack class code:

We could ask two questions that emerge from looking at this code: How could we know if this code works? The first and obvious answer is compiling it and testing as many cases as possible. But we can not always test all possible cases before sending the code to our client, so we need a more reliable criterion in order to know if the piece of code works properly or not. The second question is: how do we know if the assertion assert(!this.isEmpty()); is useful? Again we need a criterion in order to answer this question. Let us attempt to build this software following design-by-contract. First of all, we need to define the specifications of our class in order to know what each class is expected to do. Let us suppose the specifications are (in an informal way):

a. An unbounded stack can be always created b. After creating an stack this must be empty c. Pushing an element in the stack it is always possible d. After the push method, the stack is the same as before but with the new

element in the top

Class stack{ Element top; public stack(){ top = null;

} public void push(int i){ if (top == null){

top = new Element(i); top.previous = top.next = null; }

else { top.next = new Element(i) top.next.previous = top; top = top.next; } } public int pop(){ assert(!this.isEmpty()); int result = top.getIntElement(); top = top.previous; top.next = null; return result; } public boolean isEmpty(){ return top == null; }

}

Page 15: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

15

Josep Coves

e. It is only allowed to pop a non-empty stack f. After the pop method, the stack has one element less which was the top

and the new top was its previous element g. Checking whether a stack is empty must be always allowed

All those specifications are helpful in order to know what the methods are expected to do and in which situations they work. Therefore, we know the preconditions and postconditions of these methods. But is this all? Does having the preconditions and postconditions written assure the class is correct? Not actually. If we follow Class Correctness property(CC)viwe realise we need to define a class invariant also to ensure the class is correct. And we know how to write and when to check a Class Invariant following the Invariant Ruleiv. Now, we have a criterion to know when the class and the assertions are correct with respect its specification. Following those properties we can rewrite the code:

Class stack{ Element top; public stack(){ assert(true); // precondition, following a specification top = null;

// postcondition, following b and class correctness (CC) assert(isEmpty() && invariant())

} public void push(int i){

assert(true); //c element oldtop = top;

if (top == null){ top = new Element(i); top.previous = top.next = null; }

else { top.next = new Element(i) top.next.previous = top; top = top.next; } assert(oldtop == top.previous && oldtop.next == to p &&

top.getIntElement==I && invariant()) //d && CC } public int pop(){ assert(!this.isEmpty()); //Correct assertions as pre element oldtop = top; int result = top.getIntElement(); top = top.previous; top.next = null; //following f and CC

assert (result == oldtop.getIntElement && top == oldtop.previous && top.next == null && invariant()) ;

return result; } public boolean isEmpty(){ assert(true); //following g return top == null; assert(invariant()); //CC } private boolean invariant(){ //We assume an internal CI which ensures the value of the //top is correct

return ((isEmpty() && top==null) || (!isEmpty()) && top != null );

} }

Page 16: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

16

Josep Coves

In conclusion, after applying the design-by-contract following its rules, we can assure the code is correct within its specifications and the assertions are useful because they are required for the design-by-contract.

Page 17: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

17

Josep Coves

2.3. Refactorings

2.3.1. What refactoring is Fowler defines refactoring as: Refactoring is the process of changing a software

system in such a way that it does not alter the external behavior of the code yet improves its internal structure. It is a disciplined way to clean up code that

minimizes the chances of introducing bugs. In essence when we refactor we are improving the design of the code after it has been written [9].

If we pay attention in the definition we realise that refactoring is a powerful tool to improve the maintenance of a software project. Refactoring helps us to restructure a program in order to make it clearer to understand and in consequence, to modify it. In [9], Fowler gives us the following reasons to use refactoring:

• Improves the design of Software • Makes software easier to understand • Helps us to find bugs • Helps us program faster

The main property of refactoring is the behaviour preserving, so we should have a clear definition of what behaviour preserving means. Opdyke defined and demonstrated in his thesis the behaviour-preserving through the refactoring process. Following Opdyke’s definition, the behaviour is preserved, when after the refactoring: a) There’s an unique superclass b) The classes have distinct names c) The members and functions within a class must have different names d) In function redefinition the signatures have to be compatible (in order to preserve the LSP [8]) e) The assignments have to be type-safe f) The references and Operations must be semantically equivalent1[10]. Despite the fact that refactoring is a very powerful tool, it is not a “free” use tool, in the sense that we cannot apply refactoring in all circumstances. W. Opdyke in [10] defined preconditions for each type of refactoring, which have to be true in order to preserve the behaviour of the program through the refactoring process. In his thesis, Opdyke divided the refactorings in two main categories: low-level refactorings, whose behaviour preserving property is easy to show if their preconditions are respected and Composite refactorings which were built from the low-level ones; since the Composite ones are constructed using behaviour preserving refactorings, behaviour was still preserved with them if the preconditions were not violated.

2.3.2. When to apply refactoring The first question we may ask is whether refactorings are really useful or not. Fowler and Beck describe many situations in order to know when a refactoring could be useful. They use the terminology Bad Smells on Code to decide when a refactoring could be used. Summarizing their idea, a code Smells Bad when[9]:

1 Opdyke defined the semantic equivalence in this way: let the external interface to the program be via the function main. If the function main is called twice (once before and once after a refactoring) with the same set of inputs, the resulting set of output values must be the same 10. Opdyke, W.F., Refactoring Object Oriented Frameworks, in Department of Computer Science. 1992, University of Illionis Urbana-Champaign..

Page 18: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

18

Josep Coves

• We have duplicated code; we can apply a refactoring to reuse the code. • It is possible to split a long method in small parts, creating methods,

specializing functions… • If we have a long class, we may think about extracting a subclass from it,

or distributing its methods in other classes or subclasses. • We have a long list of parameters in one function and we want to reduce its

size without changing its functionality. • When we realise that all the changes needed to update a code always

affects the same different classes, we may want to regroup them in one only class.

• The opposite situation, when we see that all the changes are in the same different methods of the same class, we may want to extract a class and change the functions in the new class.

• We see one method which uses more the features of another class instead of the class in which it is declared. We may want to move the method to the referenced class.

• Often we see the same three or four data items together in many places, same fields in many different classes, same parameters in different methods, we can group them in a new object and use it instead of the individual items.

• Sometimes, we need some type constraints that the basic types of the language are not able to check; it is possible to apply a refactoring to change the primitive type for an object which checks the desired constraints. For instance, we are using a String to save a telephone number, and we want to check the user only writes numbers, we can change the String to a Telephone type.

• The problem with switch statements is the duplication of code. We may want to change the switch statement for a polymorphism, or create a specific method which checks the condition, etc.

• When every time we create a subclass of one class, we have to make a subclass of another class. We can solve this problem by moving fields or methods in order to make the referring class disappear.

• When we are maintaining a class whose utility is not worthy to have a whole class for it. We may think about deleting the class and move its functionality to another class.

• We built a class thinking of a future feature which, in the end, is never implemented. We have built all the class with generalizations and special cases which are useless without that feature, and only make the code harder to maintain and understand. We can use refactoring to clean the code.

• We have fields which are barely used, or they are used only in the private implementation of the class. These fields make the code harder to understand, we can refactor to put the variables in the right places.

• When we see a client asks one object for another object, which the client asks for another object, and so on. At the end there will be a long chain of getThis methods, this means the client is bound to this way of Navigation through the classes. We can delete this chain of calls using refactoring.

• When we have a class too encapsulated with delegation, at the end the class can be useless, and we may need to communicate directly to the object which really knows what is going on.

• We have two classes which are too bound together and they know too many things of the other, or we did an inheritance that allows the subclass to know too much things about its superclass and it is dangerous. We can break up these strongest relationships by changing the structure of the code using refactoring.

• Two methods do the same work but they have different signatures. We may join them into a single method.

Page 19: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

19

Josep Coves

• We have a very good library which does whatever we expected, but there is one functionality lacking. We can use some refactoring techniques to add the new function without modifying the library and using it as if it had that functionality.

• We can protect the fields of a data class by either creating or deleting its setting and getting methods.

• We have an inheritance in which the subclass does not really need the methods or fields given by the superclass. We can apply a refactoring to change the location of that methods and making this inheritance down a better place.

2.3.3. Refactorings classification Once we know what refactorings are, and when they are useful, we can give a classification of refactorings to know which kind of things we can do in a more concrete way. Fowler gives the classification bellow [9]: a) Composing methods In this section, Fowler puts all the refactorings which are related with the construction of methods and modifications within the class, independently of its class location. The main Refactorings in this classification are Extract Method, which consists in the creation of a new method from any existing part of the source code. Inline Method does the opposite; it substitutes a method call with its code. Another kind of refactorings to make modifications within the class are refactorings like Replace Temp with Query, which helps us to delete a temporary variable by replacing its use with a function call. b) Moving Features Between Objects Since the task of putting responsibilities is not an easy work, and we may realise in a near future that we missed in its location, we may want to change its location, passing the responsibilities to another class. The refactorings in this section are a powerful tool to make these changes in a safe and comfortable way. We can move a method from one class to another with Move Method, or we can only Move Field. When we see a class has too many responsibilities we can think on splitting this class in two classes in order to distribute the responsibilities, so we can use Extract Class; or we can merge two classes with Inline Class. c) Organizing Data Working with data makes us always take decisions as to who is able or unable to access it, where is better to save the data in, which type of structure is better, etc. These refactorings try to make less annoying the task of changing things to solve these matters. For instance, we may want to encapsulate a field in order to make its access or modification safer, we can use Encapsulate Field. We realise is better to use an object instead a primitive type, it would be helpful using the Replace Data value with Object. During the design of a class we are using a number which would be better to declare as a constant, we can try to use Replace Magic Number with Symbolic Constant. It is possible to change the links between objects by Change Unidirectional Association to Bidirectional or Change Bidirectional Association to Unidirectional. In this classification Fowler considers the problem of GUI designs, for instance the model-view-controller design, and we have a particular refactoring which makes

Page 20: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

20

Josep Coves

easier the task of distributing data by Duplicate Observed Data. Another problem of this type, is when we realise we are using fields that work only as type code (in the sense that its work is to enumerate things in order to difference them) and they do not alter the behaviour of the class we can move them to a new class with Replace Type Code with Class or Replace Code with State/Strategy; maybe we want to do just the opposite thing, so we can use Replace Subclass with Fields. d) Simplifying Conditional Expressions This section considers all kind of modifications within conditional expressions. When a conditional is very large we can think about breaking the conditional into pieces, separating the idea from the logic details by replacing the condition with a method which makes easier to understand what we are doing with such a big conditional. We can use Decompose Conditional to do this task. When we have several Boolean expressions which do the same we can replace the conditions with a method call using Consolidate Conditional Expression or undo this modification using Consolidate Duplicate Conditional Fragments. In the same way, we may want to change the structure of a conditional, but without replacing the condition only changing its structure. We can use Replace Nested Conditional with Guard Clause. Maybe we realise we are separating cases which would be properly done by a polymorphism instead of a conditional expression (as a switch clause), then we can use Replace Conditional with Polymorphism. e) Making Method Calls Simpler As its name says, this section gathers all the refactorings which allow us to simplify the method calls, for example by changing its name (Rename Method), or by changing its signature (Add Parameter, Remove Parameter, Introduce Parameter Object, Parameterize Method…). But it also includes enlarging functionalities which are already defined, for instance, we can Replace Constructor with Factory Method or Replace Error Code with Exception (this last has more sense in the JAVA programming language). f) Dealing with Generalization In this section Fowler puts all the refactorings which are risen from generalization matters. We can move methods or fields through the hierarchy of inheritance using Pull Up Field/Method and Push Down Field/Method. We have particular cases inside of these last refactorings as Replace Constructor with Factory Method (since it is not allowed to move a constructor method). If we have different methods which have a very similar implementation and only vary on details we may want to Form a Template Method. Another kind of refactorings inside this branch, are the refactorings which helps us to change the hierarchy itself by creating new classes. We can Extract Subclass, Extract Superclass, Extract Interface or Collapse Hierarchy. Fowler considers other refactorings as Replace Inheritance with Delegation or Replace Delegation with Inheritance, which we can use when we think is better to use a delegate instead a hierarchy or just the other way round. g) Big Refactorings (Extract Hierarchy, separate Domain from presentation) All the last categories above are small refactorings which can be used separately. When we use one of the refactorings above we are thinking about a bigger change, a change which has meaning from itself. Fowler and Beck, created this category as a joining of all the last small refactorings in order to not lose the whole game. They say the first refactorings are like “individual moves” of a big

Page 21: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

21

Josep Coves

refactoring. Then, in this section they put the Big Refactorings. For example, we are converting a procedural code to an object oriented code, so we should follow Convert Procedural Design to Objects, one of the big refactorings. We realise that we made a big program without a hierarchy and we think it should be a help to change the code; we could follow Extract Hierarchy or just the other way round using Tease Apart Inheritance. The last refactoring they propose is to distinguish between the Domain and the Presentation (the typical layer programming strategy) by using Separate Domain from Presentation.

Page 22: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

22

Josep Coves

3 Maintaining Assertions through Refactoring Proces s

3.1. Introduction Once we have seen what assertions are, what refactorings are, which kind of assertions and refactorings we can use in a code, and a criterion to decide when assertions are useful, we can start wondering what happens with assertions after applying some refactorings. Are really the assertions inconsistent after applying refactoring? Do all refactorings make assertions inconsistent? Which kind of assertions are inconsistent after one refactoring? Are we really able to maintain them?

3.2. Some examples to understand the problem First of all, let me show some examples to see whether the assertions are maintained or not after one refactoring process. Before giving the examples, I would like to clarify the notation of the code I am going to write:

• I will follow the JAVA notation to write the codes below. • The Inv method is the Class Invariant method (of each class in which it

appears), following the JAVA documentation [7]. • The assertion written just at the beginning of a method is considered the

precondition of that method. • The assertion written just before the end of a method is considered the

postcondition of that method.

3.2.1. The Inline Method Refactoring The first refactoring I choose is the Inline Method. This refactoring consists in replacing a method call with its code. It is a useful refactoring when we are thinking of deleting a method which is not really useful. Or maybe we are just thinking of splitting two methods in one only method, so we will need to inline them into one big method and then re-extract both methods [9]. I propose this code noted with assertions to understand what happens after the refactoring:

… public int reversePart(int a, String b){ assert(a > 0 && b.length() >0 && a < b.length()); //Precondition String result; int i; result = “”; for (i=a;i>0;i--){ result = result + b.getCharAt(i); //Loop Invariant assert(i > 0 && i <= a && a > 0 && b.length()>=i) ; }

assert(result.length()==a && //Postcondition reverse(b.substring(i)).compareTo(result) ==0 && I nv()); return(result);

}

Page 23: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

23

Josep Coves

In the Fowler definition of inline method [9], we can see a lot of steps lacking which should be there. For instance, it is obvious that we have to check if the variables declared in the inlined method already exist in the destination method, and if it happens, we should rename the variables in the inlined method before making the substitution. I would extend the Inline Method steps in this way:

• Check that the method is not polymorphic • Find all calls to the method • Check for each calling location if there is access to variables which can be overridden with the variables of the function we are going to inline.

o If there are, we must rename the variables in the function before replacing the call with the method body

• Replace each call with the method body o Replace the parameters with the expression by which the method is called each time.

• Compile and test • Remove the method definition

As Fowler says, this definition still does not cover the recursive functions, the functions with multiple return points, and many other cases, but it is a more accurate definition which matches exactly with how the current implementations of the Inline Method work (for instance with the Eclipse 3.1 implementation [11]). Then, following this definition we reach this code:

Following the definition above, we realise that if we treat assertions as normal expressions, some of them are automatically consistent. For instance, the precondition is still consistent (now perhaps it does not work as a precondition, but it checks the same condition and so it is still consistent). We can also see the Loop Invariant is still consistent. The postcondition should work, but it calls the

… public static void main (String args[]){ … String chain=”reverse me!”; s = x.reversePart(7,chain); … }

public static void main (String args[]){ … String chain=”reverse me!”;

//Precondition assert(7 > 0 && chain.length() >0 && 7 < b.length() );

String result; int i; result = “”; for (i=chain;i>0;i--){ result = result + chain.getCharAt(i); //Loop Invariant assert(i > 0 && i <= 7 && 7 > 0 && chain.length() >=i); } // Postcondition assert(result.length()==chain && reverse(chain.substring(i)).compareTo(result) == 0 && Inv()); s = result; …

}

Page 24: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

24

Josep Coves

Class Invariant, which is not so clear to be correct. Since the function is going to disappear, it can only have access to the fields of the class through other public methods, which, in the same way, have to maintain the CI, then calling the CI is a redundancy. We should delete the calling to the Inv function in the postcondition assertion of the inlined code.

3.2.2. The Move Method Refactoring When a method is using more features of another class than in the class which is currently defined we can think about moving the method to the other class. Another situation in we may use this refactoring is when we realise that some functionality is badly placed and should be located in another class.

In this example, we want to move the method reversePart from the A class to the B class, because we have realised the method uses one field of B while it does not use any field of A. Following Fowler steps, we should get [9]:

class B{ public String value; ... public int reversePart(int a, String b){ assert(a > 0 && b.length() >0 && a < b.length()); //Precondition String result; int i; result = “”; b = value + b; for (i=a;i>0;i--){ result = result + b.getCharAt(i); //Loop Invariant assert(i > 0 && i <= a && a > 0 && b.length()>=i) ; }

assert(result.length()==a && reverse(b.substring(i)).compareTo(result) ==0 && In v()); //Postcondition

return(result); } … }

class A{ public int reversePart(int a, String b){ assert(a > 0 && b.length() >0 && a < b.length()); //Precondition String result; int i; result = “”; b = (new B()).value + b; for (i=a;i>0;i--){ result = result + b.getCharAt(i); //Loop Invariant assert(i > 0 && i <= a && a > 0 && b.length()>=i) ; }

assert(result.length()==a && reverse(b.substring(i)).compareTo(result) ==0 && In v()); //Postcondition

return(result); } … } class B{ public String value; ... }

Page 25: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

25

Josep Coves

In this case the maintenance of the precondition and the loop invariant is an obvious step, because they are the same. If we look at the postcondition now, we realise that the CI called after the refactoring will be the CI of B (instead of the one in A). This is correct, because now we have a public method which following Meyer’s rules [6] should maintain the CI after its execution. The only thing that we should check is B class has an Inv() method declared. The rest of assertions of the code, if they are treated like expressions, should be consistent after the refactoring. For instance, if we have an assertion like this in any part of the code:

After the refactoring will be:

In conclusion, with this first approach we realise all the assertions are maintained after the refactoring process.

3.2.3. The Encapsulate Field Refactoring This is a very simple refactoring, which consists in encapsulate a field, creating its set and get functions. All the expressions which before called the field directly are obliged to modify them through these new methods.

If we apply the refactoring for value we should get:

Class A{ public int value = 32342; public A(){ } public void setValue(int a){ value = a; } public int getValue(){ return value; } } …

Class A{ public int value = 32342; public A(){ } } … Class B{ … a.value += 20; … assert(a.value>10); …

}

assert( classB.reversePart(5,“hello”) == “olleh”)

assert( classA.reversePart(5,“hello”) == “olleh”)

Page 26: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

26

Josep Coves

Since all the assertions are treated as expressions, after the refactoring they are still consistent. Even though the assertions are not inconsistent, we may want to preserve the design-by-contract or, if it was not using that design, at least assuring the new methods will be correct. In order to check the new methods are correct we must guarantee they preserve the design-by-contract rules which are not so obviously maintained. A deeper study of this refactoring is described later.

3.2.4. The Remove/Add Parameter Refactorings When we find out we are not really using one of the parameters of a function we can think of deleting it using the Remove Parameter. Or perhaps we want to expand the parameters of a method using Add Parameter. Let us refactor the following code:

We want to delete the d parameter in equation method, because we don’t use it, and we want to add a parameter to the solve function in order to check both solutions with the same method, so we apply both refactorings:

… Class B{ … a.setValue(a.getValue()+20); … assert(a.getValue()>10); … }

… Private double[] equation(int a, int b, int c){ assert( b*b – 4*a*c >= 0 && d >= 0); ??? double solution[2] sqroot; sqroot = sqrt(b*b – 4*a*c); solution[0] = (-b + sqroot)/(2*a); solution[1] = (-b - sqroot)/(2*a); assert(solve(a,b,c,solution[0]) == 0 && solve(a,b, c,solution[1] == 0)); return(solution); } …

… Private double[] equation(int a, int b, int c, int d){ assert( b*b – 4*a*c >= 0 && d >= 0); double solution sqroot; sqroot = sqrt(b*b – 4*a*c); solution[0] = (-b + sqroot)/(2*a); solution[1] = (-b - sqroot)/(2*a); assert(solve(a,b,c,solution[0]) == 0 && solve(a,b, c,solution[1] == 0)); return(solution); } … private double solve(int a, int b, int c, double x) { double solution; solution = x*x*a + x*b + c; return solution; } …

Page 27: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

27

Josep Coves

As we can see, when we delete a parameter, and this parameter was in the precondition, we cannot maintain that assertion. We should ask the developer to change the precondition (we can not modify the assertion directly because we do not know the meaning of the precondition). Adding a parameter seems an easier task, because all the assertions should be consistent after it, but even though the precondition is still correct, we could ask the programmer if he wants to include this new parameter in the precondition.

3.2.5. The Replace Conditional with Polymorphism Re factoring The problem with this refactoring and the way of maintaining the assertions is propped up in the Satpathy, Siebel and Rodríguez in [5]. This refactoring consists in creating a polymorphism from a conditional statement where we have many differenced cases. We realise that should be better to call a polymorphic method instead of all the branches of the conditional. For instance, taking the example of Fowler’s book [9], we have the following code:

And we want to modify it in order to reach Figure 1 design.

Figure 1

… private double solve(int a, int b, int c, double x, double x2){ double solution; solution = x*x*a + x*b + c; return solution; } …

double getSpeed(){ switch(_type){ case EUROPEAN: return getBaseSpeed(); case AFRICAN: assert(getLoadFactor()>0); return getBaseSpeed() – getLoadFactor() * _numberOfCoconuts; case NORWGIAN_BLUE: return (_isNailed) ? 0 : getBaseSpeed(_voltage); } Throw new RuntimeException(“Should be unreachable” ); }

Bird

getSpeed

European

getSpeed

African

getSpeed

Norwegian Blue

getSpeed

Page 28: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

28

Josep Coves

In order to maintain the assertions, we should:

• Copy the CI of the Base class to each new class. (Following the Parents’ Invariant Rule of Meyer [6])

• Each subclass corresponds to a particular case of the original conditionals. Then, we should modify each CI of the subclasses by adding a condition checking the type remains constant. This step is done in order to maintain the condition of the conditional that now we have deleted.

• The postcondition of the methods in the derived classes remain the same as the one in the base class. (Following the Assertion Redeclaration Rule of Meyer [6])

Following the steps above we should reach a code like this:

Class Bird{ public abstract int getSpeed(); private Inv(){…} } Class European Extends Bird{ public int getSpeed(){ assert Inv(); return getBaseSpeed(); } private Inv(){ … && _type == EUROPEAN); } } Class African Extends Bird{ public int getSpeed(){ assert(Inv() && getLoadFactor()>0); return getBaseSpeed() – getLoadFactor() * _number OfCoconuts; } private Inv(){ … && _type == AFRICAN); } }

Class Bird{ public abstract int getSpeed(); private Inv() … } Class European Extends Bird{ public int getSpeed(){ return getBaseSpeed(); } } Class African Extends Bird{ public int getSpeed(){ assert(getLoadFactor()>0); return getBaseSpeed() – getLoadFactor() * _number OfCoconuts; } } Class Norwgian_blue Extends Bird{ public int getSpeed(){ return (_isNailed) ? 0 : getBaseSpeed(_voltage); } }

Page 29: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

29

Josep Coves

3.3. A first assertion maintenance refactoring classification The above examples helped us to understand deeper the problem we are facing. Looking at the solutions we reached to maintain the assertions, we can make a first classification of refactorings depending on how they affect assertions.

3.3.1. Trivial Assertion Maintenance Since in many languages assertions are expressions like any other, they may be treated as such during the refactoring process. Following this criteria a lot of the Refactorings described in Fowler’s book maintain the assertions consistency without doing anything. For example, in the Rename refactoring, which consists in renaming one variable, the only thing we have to do is replace the old variable name with the new variable name everywhere whether it is inside an assertion or not. After doing this it is obvious the assertion should be consistent:

I give the names of some refactorings (using Fowler’s nomenclature [9]) which we could include in this category:

• Inline Temp • Replace Temp with Query • Introduce Explaining Variable • Remove Assignments to Parameters

3.3.2. Refactorings which require changes In the examples above, we saw refactorings like Encapsulate Field which the assertions are not inconsistent after the refactoring, but we can not guarantee they are correct. We should guarantee the new changes are consistent with design-by-contract in order to demonstrate their correctness. For instance, when we add new public functions it is easy to add the CI checking before the end of the method, or when we create a new subclass inheriting and checking the CI of the Base Class in each public method and in the constructor.

Class Norwgian_blue Extends Bird{ public int getSpeed(){ assert Inv(); return (_isNailed) ? 0 : getBaseSpeed(_voltage); } private Inv(){ … && _type == NORWGIAN_BLUE); }

}

X = 10; assert(x>0);

variableX = 10; assert(variableX > 0);

Page 30: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

30

Josep Coves

3.3.3. Refactorings which require assertion redefin ition At the end we find refactorings which forbid us to change the assertions automatically after applying them. Sometimes because we need to know the meaning of what the developer is trying to check with some assertion in order to modify it properly, and sometimes because the functionality of some assertion has changed, we can not change that automatically and we have to inform the programmer. The idea is to give a kind of warning (like the ones given during the compilation process) to the developer. For example, when we are deleting a parameter from a method and that parameter is inside the precondition we cannot do anything. We could think of deleting the clauses related to that parameter, but this is not always possible, for instance:

If we want to delete any of the three parameters, we should delete the assertion, which is not a very good way to maintain it. Another similar situation is when we are turning a private variable into a class field using the Convert Local Variable to Field refactoring of the Eclipse platform [11]. It is true the assertions will still be consistent, but we should ask the developer if he wants to add any checking in the Class Invariant in order to consider this new field.

3.4. A small schema to maintain Refactorings

3.4.1. Introduction After trying to classify some refactorings we discovered some rules that can help us to determine whether the refactoring can be maintained or not and how to do it. Therefore now we are going to give a small pattern to be able to classify refactorings according to its possible automation for the maintenance of the design-by-contract. This pattern does not attempt to be exhaustive and non-modifiable. Many of the steps which are now non-automatable may be automatable in a future; and surely new refactorings may be created to do different things according to the new programming languages and new programming models that may appear.

3.4.2. How to use this schema According to Opdyke’s refactoring classification[10] we can decompose the refactorings into low-level refactorings and composite refactorings which are built from the low-level ones. We are going to follow that idea of classifying the refactorings according to its individual actions. If all the small steps of the refactoring maintain the design-by-contract we can guarantee the whole refactoring will maintain it too. But we cannot guarantee that a refactoring which does not maintain the design-by-contract in any of their steps is not going to be a refactoring that maintains it, this is the problem which Opdyke discovered also in his thesis. Therefore, we must use this pattern as a warning and we may use our intelligence to determine whether a refactoring can be automated or not. For instance, if we have a refactoring that deletes a class, in one of the intermediate steps we will be deleting a field which may appear in the CI; so we will have a refactoring that cannot be maintained because we will not be able to modify the

Function method(int a, int b, int c) assert(a+b+c != 0); //Precondition

Page 31: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

31

Josep Coves

CI. But this will not be true, because we are going to delete the whole class, and after deleting the class we will delete the problem of maintaining the CI because the CI will disappear too.

3.4.3. A small schema Therefore, consider the small steps of the refactorings we can classify them according to the object which will receive the action of the refactoring and by the action that will be done by the refactoring2:

• Renaming anything If we treat all the assertions as expressions while we are renaming the assertions they will be automatically maintained.

• Modifying fields o Adding a class field This can not modify anything because we did not use that field before the refactoring and the class was working properly. Anyway, we must notice that the programmer may modify the CI in order to include the new field to avoid future inconsistency problems with the field value. Even this step is not compulsory although if we are trying to build a good design-by-contract we can not skip it. o Deleting a class field Since we are considering the preconditions of the refactoring are held, before applying the refactoring, the field can not be used for any method. Therefore, the only part where the field can be considered is inside the assertions. We consider two possible situations:

• All the assertions of the class do not consider the field. Then the refactoring maintains the assertions without doing anything.

• There is any assertion which considers the field. If the assertions

are treated as normal expressions we can save automatically doing this step, otherwise we must delete the clauses that consider the field. We are not able guarantee this step can be automated; we can find ourselves in this situation:

If we are trying to delete field and we delete all the clauses in which field appears we are going to miss all the other properties that indirectly the predicate expressed.

• Modifying method parameters

2 We assume the preconditions of the refactoring are hold before considering the assertion maintenance. If the refactoring preconditions are not hold we can not guarantee the refactoring will work properly, therefore, the assertion holding either.

CI = {x = field + y AND y > field AND y = multiple(field)}

Page 32: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

32

Josep Coves

o Adding a method parameter We are in the same situation as in Adding new class fields. The only difference is that now we should notify the programmer there is a new parameter which could be needed to be checked by the precondition instead of the CI. o Deleting a method parameter We are in the same situation as in Adding new class fields. The only difference between them is that now the only assertions that can consider the parameter are the ones inside the method.

• Modifying methods

o Adding a class method If the method does not have precondition and postcondition we can try to extract it if we have enough information (for instance, the precondition extraction of a setting method) or we can leave this task to the developer, notifying him about his new responsibilities. Assuming the new method has its preconditions and postconditions correctly defined, we must verify whether the new method has checked the CI of the correspondent class before ending. If the new method is redefining any inherited method we must maintain Meyer’s Assertion Redefinition Ruleiii, then let us assume Pre’ and Post’ as the precondition and postcondition of the new method, and Pre and Post the precondition and postcondition of the method inherited from the superclass. We must redefine the new method precondition as: {Pre OR Pre’} and its postcondition as: {Post AND Post’} o Deleting a class method If there is any assertion inside its class that calls the method we are in the same situation as Deleting a class field.

• Type Modification We can save the current assertions if after the refactoring they hold Meyer’s Type Conformance Rulei. Further information can be found in [12] in order to handle any possible situation. • Managing subclasses

o Creating a subclass When we are creating a new subclass we must verify the new subclass CI includes the parent’s CI, according to Meyer’s Parent’s Invariant Rulev. This step can be easily automated by adding with an AND the new CI to the parent’s CI. o Deleting a subclass We assume we can only delete a subclass when it has no more subclasses. To delete a subclass we must firstly delete all the class

Page 33: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

33

Josep Coves

methods and later all the class fields. After deleting everything, we can delete the whole class. Since the refactoring preconditions assure that no one is going to use the subclass we can affirm the refactoring will maintain the assertions even though any of the intermediate steps will not maintain the internal assertions of the class, because after the refactoring the class will not exist.

• Moving anything We know how to handle this situation by following the indications given in Delete Anything + Add Anything. For instance if we are moving a method, we must delete it from its old subclass and after this add it to the new class.

Page 34: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

34

Josep Coves

4 Augmented Refactoring Steps

4.1. Introduction In this chapter we are going to demonstrate how different refactorings can maintain their assertions through the refactoring process. We added some steps in Fowler’s refactoring steps [9] in order to maintain the assertions. We called this steps Augmented Refactoring Steps.

4.2. Pull Up Field Explanation: The goal of this refactoring is to move one field from one or more subclasses (in the second case the field must be the same in each subclass) to its superclass. Figure 2 is a schematic example of this refactoring.

Figure 2

4.2.1. Classification of Pull Up Field Refactoring This refactoring automatically maintains the design-by-contract, and so we can classify it inside Trivial Assertion Maintenance.

4.2.2. Demonstration First of all, we must think about all the possible assertions that can be inconsistent after the refactoring, and demonstrate that in every case their consistency is held:

• Intra-class assertions The only intra-class assertions which could have included the field are the ones of the subclasses because, before the refactoring, the superclass did not have the field declared. In the subclasses we will be able to access the field as we did before since, after the refactoring, we will inherit the field from the superclass3. Therefore the intra-class assertions of the subclasses are not affected for the refactoring.

3 We assume this fact true according to the 4th step of the Mechanics of Pull Up Field refactoring in Fowler’s Book9. Fowler, M., et al., Refactoring: Improving the Design of Existing Code., ed. Addison-Wesley. 2000. (p. 321) which aims: If the fields are private, you will need to protect the superclass field so that the subclasses can refer to it.

A

B f

C

f

A f

B

C

Page 35: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

35

Josep Coves

Since all the existing assertions will be maintained, let us think about adding new assertions. We know the subclasses will work as they did before, so we must discuss whether adding assertions in the intra-class properties of the superclass will be correct or not. Then we consider the following intra-class assertions in the superclass:

o Superclass method Preconditions, Postconditions and method bodies { m∀ | ⇒∈ )(AMethodsm )(mBodyf ∉ } (A = superclass) Since the superclass did not have the field declared before the refactoring, the methods of the superclass do not use f inside its body. Therefore they must work properly without taking care if new class fields are added. Then including the field inside the preconditions, the postconditions or in the assertions inside the body is nonsense since we do not care about its value. o Superclass CI The only place where, adding something related with the field can have sense is inside the CI. But what could happen if we add a checking of the field inside the superclass CI?

• We will be expressing restrictions about a field that we do not use. Since the only place where a field can be used is inside the methods of the class, and we demonstrated in the step above that they do not use it, we are not going to use the field, and then it can take any possible value. In consequence, the best solution is leaving the CI without any information about f.

• We would be restricting the use of the field for new future

subclasses.

Figure 3

As we can see in Figure 3, if we add a restriction about f in the superclass we know ACIf ∈ ( ACI contains restrictions about f). Then, following Parent’s Invariant Rule v, we deduce

DDA CIfCICI ∈⇒⊆ (since all the clauses of ACI are also inside

DCI we conclude the new subclass will also contain the

A

f

B

C

State after the refactoring

ACIf ∈

CA CICI ⊆ BA CICI ⊆

A

f

C

Adding a new D subclass

ACIf ∈

CA CICI ⊆ BA CICI ⊆

B

D

D

DA

CIf

CICI

∈⇒

Page 36: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

36

Josep Coves

restrictions about f). Therefore the new subclass is inheriting all the restrictions about f that we had written in the superclass. This is not a very good idea because we are restricting the use of f in the new subclass. Since the superclass did not use f it is nonsense to restrict its use in the new subclass; the subclass should be free to use the field according to its will. In conclusion, it is better to leave the CI without restrictions about f in order to let the new subclasses use the field freely.

• Inter-class assertions In all the inter-class assertions in which the field appeared now they are maintained for the same reason we gave before for the intra-class ones. Since the external relationships were working correctly, the ones that used f (the ones of the subclasses) will inherit the parameter after the refactoring, and so they will be automatically maintained. The relationships which did not consider f must continue without considering it because they will not use the parameter. Perhaps later this situation will change because the superclass can use f, but when the programmer starts using the field it is his task to make the corresponding changes to maintain the assertions; just after the refactoring the design-by-contract is maintained.

Page 37: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

37

Josep Coves

4.3. Pull Up Method Explanation: The goal of this refactoring is to move one method from one or more subclasses (in the second case the function must be the same in each subclass) to its superclass. Figure 4 is a schematic example of Pull Up Method refactoring.

Figure 4

4.3.1. Assertion Maintenance Steps Following Fowler’s steps[9], we add the steps in bold:

1) Inspect the methods to ensure they are identical. a. If the methods look like they do the same thing but are not

identical, use algorithm substitution on one of them to make them identical.

2) If the methods have different signatures, change the signatures to the one you want to use in the superclass.

3) Create a new method in the superclass, copy the body of one of the methods to it, adjust, and compile

a. If you are in a strongly typed language and the method calls another method that is present on both subclasses but not the superclass,

declare an abstract method on the superclass.

i. If we are in a language that allows the definition of preconditions and postconditions of abstract methods

(like Eiffel), we should define the precondition of that method as the AND of the precondition of the original

methods, and the postcondition as the OR of the postcondition of the original methods.

b. If the method uses a subclass field, use Pull Up Field, or Self Encapsulate Field and declare and use an abstract getting method

c. Ensure the CI called is the one of the superclass, otherwise do the respective modifications.

4) Delete one subclass method 5) Compile and test 6) Keep deleting subclass methods and testing until only the superclass

method remains 7) Take a look at the callers of this method to see whether you can change a

required type to the superclass.

A

B

f()

C

f()

A

f()

B

C

Page 38: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

38

Josep Coves

4.3.2. Demonstration We are going to demonstrate each step we added before.

a. Demonstration of step 3.a.i 3.a.i) If we are in a language that allows the definition of preconditions and

postconditions of abstract methods (like Eiffel), we should define the precondition of that method as the AND of the precondition of the original methods, and the

postcondition as the OR of the postcondition of the original methods.

Figure 5

As we see in Figure 5, the original method f() calls an specific function g() which is different in each subclass. So, after the refactoring we have an abstract method g() defined in A. Let us define g(), g’() and g’’() Preconditions and Postconditions as:

{Pre’}g’(){Post’} {Pre’’}g’’(){Post’’} {Pre}g(){Post}

The new method will call the abstract methods without knowing which of the subclass method’s will be called, so we must ask for both preconditions to be true, and guarantee any of the postconditions in the abstract method. For that reason, in the steps above we said we should define the Precondition of the new abstract method as the AND of the precondition of the original methods, and the Postcondition as the OR of the postcondition of the original methods. So we can change {Pre} = {Pre’ AND Pre’’} and {Post} = {Post’ OR Post’’}:

{Pre’}g’(){Post’} {Pre’’}g’’(){Post’’} {Pre’ AND Pre’’}g(){Post’ OR Post’’}

Since now g() is the base method and g’() and g’’() are the redefinitions of the base method, their preconditions and postconditions should maintain the Assertion Redefinition Rule (1)ii which claims: A routine redeclaration may only replace the original precondition by one equal or weaker, and the original postcondition by one equal or stronger. It is easy to demonstrate that {Pre’} and {Pre’’} are weaker preconditions than {Pre’ AND Pre’’} since the new domain is included in the old preconditions. And also {Post’} and {Post’’} are stronger postconditions than

A

B

f() g’()

C

f() g’’()

A

f() g()

B

g’()

C

g’’()

Page 39: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

39

Josep Coves

{Post’ OR Post’’} since the old postconditions domain is included in the new postcondition. We can also demonstrate that we are maintaining the Assertion Redeclaration Rule (2)iii. Having the base method declared in this way:

{Pre’ AND Pre’’}g(){Post’ OR Post’’} And we have the new clauses of the redefinition methods:

{Pre’}g’(){Post’} {Pre’’}g’’(){Post’’}

Following the Assertion Redefinition Rule (2)iii we change them to:

{Pre’ OR (Pre’ AND Pre’’)}g’(){Post’ AND (Post’ OR Post’’)} {Pre’’ OR (Pre’ AND Pre’’)}g’’(){Post’’ AND (Post’ OR Post’’)}

If we develop these clauses applying the distributive property we reach:

{Pre’ OR (Pre’ AND Pre’’)} g’() {Post’ AND (Post’ OR Post’’)} {(Pre’ OR Pre’) AND (Pre’ OR Pre’’)} g’() {(Post’ AND Post’) OR (Post’ AND Post’’)} {Pre’ AND (Pre’ OR Pre’’)} g’() {Post’ OR (Post’ AND Post’’)} {Pre’’ OR (Pre’ AND Pre’’)} g’’() {Post’’ AND (Post’ OR Post’’)} {(Pre’’ OR Pre’) AND (Pre’’ OR Pre’’)} g’’() {(Post’’ AND Post’) OR (Post’’ AND Post’’)} {Pre’’ AND (Pre’ OR Pre’’)} g’’() {Post’’ OR (Post’’ AND Post’)} {Pre’ AND Pre’’} g() {Post’ OR Post’’} {Pre’ AND Pre’’} is clearly stronger or equal than {Pre’ AND (Pre’ OR Pre’’)} and {Pre’’ AND (Pre’ OR Pre’’)} {Post’ OR Post’’} is clearly weaker or equal than {Post’ OR (Post’ AND Post’’)} and {Post’’ OR (Post’’ AND Post’)}

In conclusion, with this definition of the Precondition and Postcondition of the abstract method we are holding both of Meyer’s rules.

b. Demonstration of step 3.c

c. Ensure the CI called is the one of the superclass, otherwise do the respective

modifications.

After the refactoring we will have the method that was in the subclasses in its superclass, and now the class invariant which has to be called is the one of the superclass instead of the one of the subclass following Meyer’s Invariant Ruleiv Specifically, we are interested in the E2 rule. If the method was not public, it would not have the checking of the Class Invariant, and since there is no CI checking we do not have to worry about this step and we can skip it. But when the method is public it should have Class Invariant following the Invariant Rule. Let us assume: CIb = Class Invariant of the superclass CIs = Class Invariant of the subclass

Now we have to be careful, because we have to be sure this CI is CIb instead of CIs. It is true that CIs includes CIb according to Meyer’s Parent’s invariant rulev, but CIs will have specific clauses of the subclass that checking them in CIb is nonsense. Therefore we have only to check CIb.

Page 40: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

40

Josep Coves

If we check the Class Invariant calling a private method defined in the subclass, when we move the method to its superclass it will be automatically correct (if we have an invariant method defined in the superclass, otherwise we have to create a new CI for the superclass that returns true). If the class invariant is defined in another way we must change it manually and we can not guarantee that it is possible to be automated.

4.3.3. Example Let us expand Fowler’s Pull Up Method example[9] (p. 324). We assume we have a Customer design like in Figure 6, which separates a preferred customer from a regular one.

Figure 6

createBill method is the same for each class:

And the chargeFor method is different on each subclass:

In Regular Customer class double chargeFor (date Start, date End){ /* Pre = {Start ≠ null AND End ≠ null AND Start <= End} */ … /* Post = {result = numberOfDays(Start, End)*amoun tPerDay()}*/ }

In Preferred Customer class double chargeFor (date Start, date End){ /* Pre = {Start ≠ null AND End ≠ null AND Start <= End} */ … /* Post = {result = (numberOfDays(Start, End)*amou ntPerDay()) – discountPercentage(Start,End)}*/ }

void createBill (date Date){ /* Pre = {Date ≠ null} */ double chargeAmount = chargeFor (lastBillDate, dat e); addBill (date, chargeAmount); /* Post = {getBill(date) ≠ null AND getBill(date).amount = changeFor(lastBillDate, date) AND CI} */ }

Customer

lastBillDate

addBill(Date,double)

Regular Customer

createBill(Date) chargeFor(Date, Date)

Preferred Customer

createBill(Date) chargeFor(Date, Date)

Page 41: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

41

Josep Coves

We see we cannot move the chargeFor method because it has a different implementation in each subclass. Therefore we must create and abstract method in the superclass following the rules defined above. Then we reach the design of Figure 7.

Figure 7

4.3.4. Classification of Pull Up Method Refactoring As we see in the steps above, this refactoring can be assertion-maintained automatically. To automatise it we have to consider two parts:

• Automatise the part of defining the Pre/Post of each abstract method created in the superclass as a method that is needed for the method we are going to move. Therefore we can classify this step in Refactorings which require changes, but if the language does not allow the definition of Pre/Post in abstract methods (like JAVA) we can classify it in Trivial Assertion Maintenance.

• Check that the Class Invariant checked after moving the method is the one of the superclass instead of the subclass. If we define some rules for the checking of the Class Invariant this step can be classified in Trivial Assertion Maintenance, otherwise we will have to guess the CI, and it can be easy or impossible depending in how we defined the CIs. In consequence of that, this step can be Refactorings which require changes or Refactorings which require assertion redefinition.

In Customer class void createBill (date Date){ /* Pre = {Date ≠ null} */ double chargeAmount = chargeFor (lastBillDate, dat e); addBill (date, chargeAmount); /* Post = {getBill(date) ≠ null AND getBill(date).amount = changeFor(lastBillDate, date) AND CI} */ } abstract double chargeFor (date Start, date End){ /* Pre = {Start ≠ null AND End ≠ null AND Start <= End} */ /* Post = {(result = numberOfDays(Start, End)*amou ntPerDay()) OR (result = (numberOfDays(Start, End)*amountPerDay() ) – discountPercentage(Start,End))*/ }

Customer

lastBillDate

addBill(Date,double) createBill(Date) chargeFor(Date, Date)

Regular Customer

chargeFor(Date, Date)

Preferred Customer

chargeFor(Date, Date)

Page 42: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

42

Josep Coves

In conclusion of all we have said, depending in the language we are and if we have defined a set of rules to check and define the CIs, we can reach to classify this refactoring as Trivial Assertion Maintenance or Refactorings which require changes. If we cannot define a set of rules to the way of checking and defining the CIs, we are in Refactorings which require changes or Refactorings which require assertion redefinition.

Page 43: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

43

Josep Coves

4.4. Self Encapsulate Field This is a very simple refactoring, which consists of encapsulating a field, creating its set and get functions. All the expressions which before called the field directly are obliged to modify it through these new methods. Figure 8 is an schematic example of this refactoring.

Figure 8

4.4.1. Some important facts before facing the refac toring It is true that the assertions would not be violated directly after the refactoring, but this does not mean that we are maintaining the design-by-contract scheme. For instance, we may have a piece of code like this[6]:

And we can refactor the field field to access it by public methods:

As we can see the assertions are not violated after the refactoring, but we have to be aware that we can be violating the idea of the design-by-contract if we leave the code as it is now. There are three important points that can not be taken for granted:

1) We are now creating new public methods, and so, following the definition of class correctnessvi, they must check the Class Invariant

… /* CI: get_field() == null OR get_field() >= 0 */ int value = abs(3*initial_value); assert(value>=0); set_field(value); … assert(get_field>=0); … public void set_field(int value){ this.field = value; } … public int get_field(){ int result = this.field; return result; } …

… /* CI: this.field == null OR this.field >= 0 */ int value = abs(3*initial_value); assert(value>=0); this.field = value; … assert(this.field>=0); …

A

Field: type

A Field: type type get_field() set_field(type)

Page 44: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

44

Josep Coves

before ending. If we do not check it now it is not incorrect, but we cannot guarantee that later the programmer modifies that methods and, unconsciously, leads the program to a state that violates the Class Invariant. He should be notified about it with an assertion. We have to check the CI before the end of both methods.

2) Before the refactoring, the programmer was modifying the field

according to the range of values it is available to get, and he would not assign an invalid value. We are saying that the programmer was checking unconsciously a precondition that, from now, nobody else will check. So, at least we need to define a precondition to the setting method.

3) As the same that happened with the setting method, the programmer

was unconsciously checking two postconditions that we can not take for granted now. One is the postcondition which aims that after the setting method the value is set, and the other one which aims that we are really returning the value of the field we want and not other one. They seem obvious but we have to check them because the programmer can modify the routines and involuntarily change the functionality of the method.

4.4.2. Assertion Maintenance Steps Following Fowler’s steps[9], we add the steps in bold:

1) Create getting and setting methods for the field a. Add a precondition to the setting method which checks

whether the value we are going to assign is inside the range

of allowed values for the field according to the CI. This can be extracted from the CI

b. Add a postcondition to the setting method to check whether the value of the field has been changed for the one passed in

the setting method c. Add a precondition and a postcondition to the getting method

to check whether the variable we are going to return is the

correspondent field d. Add the checking of the Class Invariant before the ending of

each method 2) Find all clients outside the class that reference the field. If the client uses

the value, replace the reference with a call to the getting method. If the client changes the value, replace the reference with a call to the setting

method a. If the field is an object and the client invokes a modifier on the

object, that is a use. Only use the setting method to replace an

assignment 3) Compile and test after each change 4) Once all clients are changed, declare the field as private 5) Compile and test

Page 45: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

45

Josep Coves

4.4.3. Demonstration

a. Adding a precondition to the setting method

1.a) Add a precondition to the setting method which checks whether the value we are going to assign is inside the range of allowed values for the field according to

the CI. This can be extracted from the CI First of all, we may be wondering why a {True} precondition would not be correct (to maintaining the design-by-contract scheme). Let us think in the opposite way, assuming we write {True} as the precondition of the setting method. After the refactoring we will have a tempting public method which allows us to modify the values of the field according to our will. Imagine we are encapsulating a field that is an integer that contains the age of somebody and the Class Invariant has in its specification a clause that forces this field to be greater or equal to zero. After the refactoring we will be able to assign a negative value to this field; after doing this, we are going to violate the Class Invariant after the execution of that method. In consequence of that, we are violating the design-by-contract which aims that after each execution of any public method the CI must hold (class correctness propertyvi). Let us probe further, if we check the CI inside the postcondition of the setting method we will get a violation of the postcondition, and since it will not violate the precondition (it is {True}) the programmer will think that the supplier class has a bug (following Meyer’s Assertion Violation Rulevii), when the real problem is that we are accepting a cheating client because we have a bad precondition. Once we reached this point we should be wondering whether it is possible to extract that precondition, and whether we can automatise it. To understand the procedure of extracting the precondition from the CI, we must be aware of some facts:

1) Our responsibility is to modify only one field The goal of the refactoring is to create the setting and getting methods for one field, so this is obviously true. 2) The CI is fulfilled before executing the setting method For the class correctnessvi property, we see that before (and also after) each execution of any public method the CI must be fulfilled. Since the setting method will be a new exported routine of the class, the CI is fulfilled before its execution. 3) The CI can have information of the allowed range values of the field If the CI is well defined, and more because before the refactoring this field had no setting method it should include a check for the range of acceptable values of the field. Furthermore, Bertrand Meyer aims that a correct class should have Implementation Invariantsviii in the Class Invariant, and then it is more than probable to have clauses in the CI that checks the range of the field. Otherwise we can assume {True} as the precondition of the method, allowing the field to take every possible values. 4) The setting method will be public and the field will be private

Page 46: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

46

Josep Coves

This is true for the functionality of the refactoring. The refactoring guarantees that after its execution the field must be private and we can access it through its new public setting and getting methods.

For all we said above, we deduce that checking the CI is a correct precondition for the method (it must be held before the execution and probably includes the checking of the range correctness of the field), but obviously not the best because it is too long and can involve some clauses which are implementation intrinsic or simply clauses that does not affect our setting method, and the client does not have to be aware of them. Having this clear, we think about deleting the clauses which are not specific of the method, in other words, we want to leave only the ones which are related with the field. Now we reach another point: is it possible to extract correctly the precondition from the CI? Since the answer of this question will require a lot of space, we decide to make an independent chapter to face this issue. Further details to know how to extract a precondition from the CI can be found in Chapter 5.

b. Adding a postcondition to the setting method

1.b) Add a postcondition to the setting method to check whether the value of the field has been changed for the one passed in the setting method

We can think of skipping this step because it seems obvious that this will be correctly done, also because the refactoring is behaviour-preserving and guarantees the work will be done properly. All of this is true, but we can not forget that later the programmer can modify the setting method to his will, and we can not prevent him. So what we must do is ensure the method has done what was expected by checking the correspondent postcondition. Furthermore, leaving a blank postcondition a client will never know what the method does, and this is not a correct design-by-contract. This step can be easily automated by filling a template which will be different for each programming language. For instance in JAVA we can use the following template:

To automatise this step, we only have to replace field by the name of the field we are going to modify and theField by the name of the parameter variable of the setting method.

c. Adding a precondition and a postcondition to the getting method

1.c) Add a precondition and a postcondition to the getting method to check whether the variable we are going to return is the correspondent field

Since there are no input parameters in a getting method any call will be accepted. Therefore we will write TRUE as the precondition of the getting method.

Pre = {TRUE}

Post = {this.field == theField}

Page 47: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

47

Josep Coves

All things said in the last demonstration step are valid for the postcondition, but we may also note that the heart of a getting method is inside the postcondition since the precondition will be TRUE. This step can be easily automated by filling a template which will be different for each programming language. For instance, in JAVA we can use the following template:

To automatise this step, we only have to replace field by the name of the field we are going to retrieve the value, and result by the name of the variable which contains the return value of the getting method.

d. Checking the class invariant in both methods 1.d) Add the checking of the Class Invariant before the ending of each method

Since the new getting and setting methods are both public, following Meyer’s Class Correctness Ruleiv, we must check the class invariant before the ending of both methods. If we have defined rules to write the CIs it is easy to automatise this step.

4.4.4. Classification of Encapsulate Field Since we have demonstrated that all the steps to maintain the design-by-contract can be automated and since we can not skip those steps, we classify this refactoring in Refactorings which require changes if the following facts are granted:

• We have defined a set of rules to declare the CI in order to ease the checking of class invariant in the new getting and setting methods. It should be done preferentially by calling a class private method.

• If we are going to use the CNF optimization4, we must force the programmer to verify that the evaluation result of each individual clause inside the CI predicate does not raise any exception.

Otherwise, the refactoring can be inside Refactorings which require assertion redefinition.

4 Detailed information of this step can be found in next Chapter

Post = {result == this.field}

Page 48: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

48

Josep Coves

5 Extraction of the setting method precondition fro m the CI

5.1. Introduction Although this is one of the steps required in the Self Encapsulate Field, we decided to create an independent chapter in order to face it accurately for mainly two reasons: first of all, this is not an obvious step and we need a whole chapter to explain it in detail. Furthermore, the extraction process can be helpful in any other situations in which we require information extraction from a predicate. In order to have a better understanding of this chapter it is useful to have in mind the facts we described in 4.4.1 and the demonstration in 4.4.3.a.

5.2. Extraction Procedure Steps At the end a CI is a logic predicate which must be true or false. A predicate can be expressed only with ANDs, ORs and sometimes NOTs. Once we have reduced the predicate, we can forge a binary tree such that each node is a logical operator AND or OR. Let us draw some steps and rules in order to extract the precondition from the CI:

I. Forging a Binary Tree Forge a binary tree from the CI such that each node is a logical AND or OR.

II. Replacing the Variable name Substitute the name of the field variable by the name of the parameter variable of the setting method in each tree leaf where appears the field we are going to encapsulate. Mark all the leaves where we made this substitution.

III. Applying ASR and OSR substitution rules Substitute by TRUE everything that holds any of the following rules:

o AND Substitution Rule (ASR) In an AND node we can substitute a branch for TRUE if we hold all the following properties:

1) None of its sub-branches has a marked leaf 2) None of its branch predecessors is an OR node

o OR Substitution Rule (OSR)

In an OR node we can substitute the whole node for TRUE if we hold all the following properties:

1) None of its branches and sub-branches have a marked leaf 2) None of its branch predecessors is an OR node

Page 49: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

49

Josep Coves

IV. Logical Simplification of the result tree Simplify the tree using the following rules:

o Logic Rule 1 (Figure 9)– Logical AND rule:

TRUE AND x ≡ x AND TRUE ≡ x

Figure 9

o Logic Rule 2 (Figure 10)– Logical OR rule

TRUE OR x ≡ x OR TRUE ≡ TRUE

Figure 10

V. Reconstructing the Precondition from the tree Rebuild a predicate from the remaining tree. This predicate will be the precondition of the setting method.

5.3. Demonstration of the Extraction Steps

5.3.1. Some facts we must remember

In order to understand the validity of those rules we must understand the context in which we are working:

⇒ We come from a state which the CI is hold ⇒ After the setting method we want to hold the CI ⇒ The only changes that the setting method can do are the ones related

with the field ⇒ We are extracting a Precondition which verifies the new value we are

going to set does not violate the CI

5.3.2. Forging a Binary Tree Demonstration It is trivial that any predicate can be converted to a Binary Tree.

5.3.3. Replacing the Variable name Demonstration

We want to extract a precondition that checks the new value we are going to set does not violate the CI, so instead the field we want to verify the new value still holds the CI, otherwise we are not allowed to set the new value to the field. This is the reason of this change.

OR

TRUE X

OR

TRUE X TRUE ≡ ≡

AND

TRUE X

AND

TRUE X X ≡ ≡

Page 50: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

50

Josep Coves

5.3.4. ASR and OSR rules demonstration Let us start with a base case, represented in Figure 11. Base case: Root Node

Figure 11

The base of our tree is a logical X operator Root Node (X = AND / OR). We represent as A and B the sub-trees of each branch of the root node. A’ and B’ represents the sub-branches after the substitution of the variable field by the parameter variable of the setting method. Since we know the original CI was fulfilled ({A X B} = {TRUE} the logical evaluation of the original tree was TRUE), and we changed the field for the value of the setting method, now we have the result of the evaluation of the new value, it means the Precondition of the setting method ({Pre} = {A’ X B’}); if it is FALSE it means the new value does not fulfil the CI and so it is not a possible value to assign, otherwise it means the CI is fulfilled and so that is an acceptable value. We can have two kind of root nodes:

1. AND Root Node (Figure 12)

Figure 12

We know that {A AND B} = {TRUE} is a fact. Hence we can deduce two more facts from the logical and: • {A} = {TRUE} • {B} = {TRUE} Otherwise the evaluation of {A AND B} would not be {TRUE}. We want to demonstrate when we can simplify one of the branches. We have two possible situations:

a. { l∀ | ⇒∈∧ )')(( Allleaf lfield ∉ } There is no leaf inside A' sub-tree that includes field. This means that there has been no substitution inside A branch, so {A’} = {A}. Therefore we can say: {Pre} = {A’ AND B’} = {A AND B’} =5 {TRUE AND B’} = {B’}

5 We can do this simplification step remembering the Fact {A} = {TRUE}

X

A B X

A’ B’ Substitution Field / new value

AND

A B AND

A’ B’ Substitution Field / new value

Page 51: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

51

Josep Coves

{Pre} = {B’} That demonstrates that we can simplify the tree by deleting A’ branch. b. { l∃ | ⇒∈∧ )')(( Allleaf lfield ∈ } There is a leaf inside A’ sub-tree that includes field. This means that there has been a substitution inside A branch, so the precondition will be: {Pre} = {A’ AND B’}. In this case it is normal we can not simplify because the precondition needs part of that sub-branch in order to check the new value.

In the same way, it is possible to demonstrate the simplification of B’ sub-tree replacing A’ by B’. This case demonstrates ASR property 1. 2. OR Root Node (Figure 13)

Figure 13

We know that {A OR B} = {TRUE} is a fact. Hence we can deduce that is not possible to be FALSE both predicates, but in contraposition of the last case, we can not know which one(s) is/are TRUE. We want to demonstrate when we can simplify the whole tree. We have two possible situations:

a. { l∀ | ⇒∈∧ )')(( Allleaf lfield ∉ } There is no leaf inside A' sub-tree that includes field. This means that there has been no substitution inside A branch, so {A’} = {A}. Therefore we can say: {Pre} = {A’ OR B’} = {A OR B’} {Pre} = {A OR B’} Then we have two possibilities for B’ sub-tree:

1. { l∀ | ⇒∈∧ )')(( Bllleaf lfield ∉ } In the same way we said above, we deduce {B’} = {B}. Therefore we can say: {Pre} = {A OR B’} = {A OR B} = {TRUE} {Pre} = {TRUE} Since the field does not appear in none of the sub-trees we can deduce the result will be the same as the original CI, and since it was fulfilled we can affirm the result of the evaluation of {A’ OR B’} will be {TRUE}, simplifying the tree. 2. { l∃ | ⇒∈∧ )')(( Bllleaf lfield ∈ } This means there is a leaf that contains the field, so we can not replace B’ by B. {Pre} = {A OR B’} In conclusion in this case we can not simplify the expression.

b. { l∃ | ⇒∈∧ )')(( Allleaf lfield ∈ }

OR

A B OR

A’ B’ Substitution Field / new value

Page 52: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

52

Josep Coves

There is a leaf inside A’ sub-tree that includes field. There is no need to consider the rest of the cases, because we are in the same situation of previous case, since there is a branch that contains field we can not simplify the whole tree. This demonstrates OSR property 1.

Recursive Case: Any other Node (Figure 14)

Figure 14

We represent Y as the root node of the tree, and X as any other node of the tree. We consider A, B, C and D as sub-trees. In this case it is not important whether the substitution has been made or not. We take consideration of two possible situations:

a. { n∀ | ⇒∈∧ ))()(( Xrspredecessonnnode )(nisANDNode } All the predecessors of X (included Y, the root node) are AND nodes. We have: {C AND D’ AND (A X B)}, where D’ is the path from Y to X, and so we know:

D’ = {AND iniD ..1= } ={D1 AND D2 AND … AND Dn}.

So we have this clause: {C AND D1 AND D2 AND … AND Dn AND (A X B)} = {TRUE} Therefore, since they are ANDs, we can affirm that all the nodes must be evaluated to TRUE to fulfil the CI. Hence we can deduce all the clauses must be evaluated to TRUE. Then we can affirm: {A X B} = {TRUE} Now we want to know if we can reduce part of the expression {A X B} = {TRUE}. We realise we are in the base case situation, so we proceed with the steps of the base-case in order to know whether we can simplify it or not.

b. { n∃ | ⇒∈∧ ))()(( Xrspredecessonnnode )(nisORNode } There exists a node in the path from Y to X that is an OR node. Since in the logical OR only one clause must be held to fulfil the Predicate, we can not affirm whether our node must be TRUE or FALSE. If we are in this situation we must evaluate the whole expression: {A X B} = {?}.

This case demonstrates ASR property 2 and OSR property 2.

X

A B

Y Root node

Any other node

C D

Page 53: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

53

Josep Coves

5.3.5. Logical Simplification of the result tree De monstration This step is demonstrated by the logical AND and OR rules cited above.

5.3.6. Reconstructing the Precondition from the tre e

Since we can forge a tree from any predicate we can rebuild the predicate traversing the tree in in-order6 way.

5.4. Proposed precondition extraction pseudocode algorithm:

6 An in-order traversal consists in visiting the left node/sub-tree, then the node, then the right node/sub-tree.

action ASR(n: node, newf: field) if ¬OR_predecessors(n) → if ¬ (contains(n.leftBranch, newf)) → n.leftBranch = TRUE endif if ¬ (contains(n.rightBranch, newf)) → n.rightBranch = TRUE endif endif end action

action ASR_OSR_rules(n: node, newf: field) if isANDNode(n) → ASR(n,newf) if n.leftBranch ≠ TRUE → ASR_OSR_rules(n.leftBranch, newf) endif if n.rightBranch ≠ TRUE → ASR_OSR_rules(n.rightBranch, newf) endif endif if isORNode(n) → OSR(n) if n ≠ TRUE → ASR_OSR_rules(n.leftBranch, newf) ASR_OSR_rules (n.rightBranch, newf) endif endif end action

function precondition_extraction(ci: CI, oldf: field, newf: field) : precondition variables n: node endvars n = forgeBinaryTree(ci) substituteField(n,oldf,newf) ASR_OSR_rules(n, newf) simplifyTree(n) return rebuild_ci(n) end function

Page 54: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

54

Josep Coves

The cost of the algorithm will be variable depending on the implementation. A good implementation may use node attributes to determine whether a node has OR predecessors or contains newfield in any of their branches. Supposing such an implementation thus: OSR and ASR = )1(Ο

ASR_OSR_rules = )(cΟ

ForgeBinaryTree = )(cΟ

substituteField = )(cΟ

simplifyTree = )(cΟ

rebuild_ci = )(cΟ

precondition_extraction = )(cΟ

5.5. Some examples of CI derivation

5.5.1. Encapsulating the year field of a Movie

We are going to derive an easy example to understand the above process. Suppose we have a Movie Object designed as in Figure 15.

Step 1

We convert the pseudocode of the CI to a Predicate which its only operators are AND, OR and NOT, and we transform it into a binary tree as in Figure 16.

action OSR(n: node, newf: field) if ¬OR_predecessors(n) → if ¬contains(n.leftBranch, newf) AND ¬contains(n.rightBranch, newf) → n = TRUE endif endif end action

CI = (Title ≠ null) AND((NOT Director ≠ null) OR directorsDatabase.exists(Director)) AND (hasIntValu e(Year) AND intValue(Year)>0 AND intValue(Year) <= getCurrentYe ar())

CI = Title ≠ null AND

(Director ≠ null → directorsDatabase.exists(Director)) AND (hasIntValue(Year) AND intValue(Year)>0 AND intValue(Year) <= getCurrentYear())

Figure 15

Movie Title: String Director: String Year: char[4] getTitle(): String getDirector(): String

c = number of clauses

Page 55: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

55

Josep Coves

Figure 16

Step 2

Let us suppose we are going to encapsulate the year field, and the new setting method has the following signature:

In Figure 17 we represent the marked leaves in bold font.

Figure 17

Step 3

In Figure 18 we mark all the sub-trees which can be simplified applying ASR/OSR rules, and in Figure 19 we show the state of the tree after applying these rules.

Figure 18

void set_year(char[4] theYear)

AND

AND

AND hasIntValue(theYear)

intValue(theYear)>0

intValue(theYear)<=getCurrentYear()

AND

NOT Title ≠ null

NOT Director ≠ null

directorsDatabase.exists(Director)

OR

ASR

OSR

AND

AND

AND hasIntValue(theYear)

intValue(theYear)>0

intValue(theYear)<=getCurrentYear()

AND

NOT Title ≠ null

NOT Director ≠ null

directorsDatabase.exists(Director)

OR

AND

AND

AND

NOT Title ≠ null

NOT Director ≠ null

directorsDatabase.exists(Director)

OR

AND hasIntValue(Year)

intValue(Year)>0

intValue(Year)<=getCurrentYear()

Page 56: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

56

Josep Coves

Figure 19

Step 4

In Figure 20 we show the final tree after simplifying it with logical rules.

Figure 20

Step 5

At the end we reach the new precondition: {Pre} = {hasIntValue(theYear) AND intValue(theYear)>0 AND intValue(theYear) <= getCurrentYear()} This precondition allows us to change the field year if the new value is:

• A representation of an integer (hasIntValue) • Contains a number between 1 and the current year

With this example we can see clearly how deriving the precondition helps us to maintain the design-by-contract. If we had created the new setting method without this condition a client could introduce any string of four characters, easily violating the CI.

5.5.2. Encapsulating the empty field of a bounded S tack For instance, let us assume we are going to implement a bounded stack class from the design of Figure 21.

CI = 0 <= count count <= capacity capacity = array.length empty ↔ count = 0

count > 0 → array[count] ≠ null

Figure 21

AND

AND hasIntValue(theYear)

intValue(theYear)>0

intValue(theYear)<=getCurrentYear()

AND

AND

AND hasIntValue(theYear)

intValue(theYear)>0

intValue(theYear)<=getCurrentYear()

AND

TRUE

TRUE

Bounded Stack Capacity: int Count: int Empty: boolean Array: array(object) Top(): object Pop() Push(object)

Page 57: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

57

Josep Coves

Applying logic rules we can modify the CI to reach a predicate which only contains ANDs, ORs and NOTs:

Step 1

In Figure 22 we build a binary tree of ANDs and ORs, assuming left associativity, from that predicate.

Figure 22

Once we implement the class, we realise that is better to encapsulate the empty field; it is very dangerous to let the client modify it directly because he will surely violate the CI. So we want to derive from the CI the precondition of the following method:

Step 2

Knowing the new setting method has the above signature, in Figure 23 the marked leaves are in bold.

void set_empty(boolean is_empty)

CI = 0 <= count AND count <= capacity AND capacity = array.length AND ((NOT empty OR count = 0) AND (empty OR NOT count = 0)) AND

AND (NOT count > 0 OR array[count] ≠ null) →

AND

AND

AND

AND

OR AND

OR OR

0 <= count

count <= capacity

capacity = array.length

NOT count > 0 array[count] ≠ null

NOT count = 0 empty count = 0 NOT empty

Page 58: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

58

Josep Coves

Figure 23

Step 3 In Figure 24 we show the resulting tree after applying ASR/OSR rules.

Figure 24

Step 4

After simplifying the tree with logical rules we reach the one in Figure 25.

Figure 25

Step 5

At the end we reach the new precondition: {Pre} = {(NOT is_empty OR count = 0) AND (is_empty OR NOT count = 0)}

AND

OR OR

NOT count = 0 is_empty count = 0 NOT is_empty

AND

AND

AND

AND

AND

OR OR

TRUE

TRUE

TRUE

TRUE

NOT count = 0 is_empty count = 0 NOT is_empty

AND

AND

AND

AND

OR AND

OR OR

0 <= count

count <= capacity

capacity = array.length

NOT count > 0 array[count] ≠ null

NOT count = 0 is_empty count = 0 NOT is_empty

Page 59: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

59

Josep Coves

This precondition allows us to change the value empty:

• To true only when there are no elements in the stack • To false only when there is one or more elements in the stack

It is a reasonable precondition for a setting method. The obligation of a setting method is to change the value of one variable. Maybe later the programmer wants to create a method which deletes all the elements of the stack when we set the empty field to true, but as we can see the programmer did not want a setting method only. As we can see this is a strange situation which makes us wonder what had happened. We will call these situations “cheating cases”:

5.6. A cheating case: correlated updates

Let us suppose we have a simple design of a bank account like Figure 26:

In this situation, the developer could think about encapsulating inputs or outputs fields. For instance let us suppose we want to encapsulate the inputs field. The extracted precondition for the setting method will be: Pre = {inputs >= 0 AND balance = inputs - outputs}. What is this precondition saying? The predicate is saying we can only change the value for the current value, and then we can not change the value at all. We may be wondering what is going wrong, is it a contra example of our precondition extraction? Or is it an error of the developer? To face this problem we must understand the nature of the setting method. A setting method consists in setting one (and only one) field to another value. In this case the developer does not want a setting method, he wants a method which sets a new value to inputs (this is true), but he also wants a method which updates the value of balance. Therefore he wants an updating method not a setting method. He can use the encapsulate field to start creating the new method but he has to be aware of what he is doing to modify the precondition and the body of the setting method properly in order to convert it into an updating method. In conclusion, the setting method is working properly because it ensures that, modifying only one field fulfilling the precondition we hold the CI after the execution of the method. This is the functionality of a setting method, if the programmer wants to modify more than one field inside the method he can not reach it by creating a setting method.

CI = inputs >= 0 outputs >= 0 balance = inputs - outputs

Figure 26

Bank Account inputs outputs balance

Page 60: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

60

Josep Coves

5.7. Possible future optimizations

5.7.1. The CNF optimization Knowing the 2n property of both simplification rules (ASR and OSR) we deduce that having the predicate in Conjunctive Normal Form (CNF) will help us to make a better simplification of the tree. For instance, if we have an OR Node as the root of the tree, we are not able to simplify anything because we will be violating the 2n property of both rules. Having the predicate in CNF we assure the AND nodes are so near the root as possible and the OR nodes are so far the root as possible, which is the best situation to apply the ASR and OSR rules, assuring maximum simplification. The standard CNF conversion has an exponential increase in the size of the

formula )2( cΟ (where c=number of clauses), because it is obtained by the Boolean algebra distributive properties of AND and OR. But we can find better CNF conversions, for instance Boy de la Tour[13] which reaches quadratic algorithm

complexity )( 2cΟ or the Compact Conversion Algorithm proposed by D.

Sheridan[14] which only has linear algorithm complexity )(cΟ . Since is possible to convert our CI predicate into a CNF linearly and it helps us to optimize better, it is a very reasonable step before starting the Predicate extraction from the CI.

a. CNF Transformation problems

i. Preconditions too long The CNF conversion can expand our tree excessively because in every CNF conversion step we are growing our tree in an exponential way7. Therefore we have to decide when it is useful to make the transformation or not. First of all, we must check if the CNF has deleted any of the original clauses, which means we will be checking fewer conditions. If with the CNF transformation we finish checking the same clauses that we checked by extracting the precondition from the original CI tree, we can decide the CNF conversion was not useful because we will be checking the same precondition but with a greater predicate, which will require more computation time. Even though at the end we are checking fewer clauses, we have to decide whether the resulting tree is reasonable or not. Hence, we should do both transformations and build a criterion in order to decide which the best is. We could solve this problem by only converting to CNF some parts of the tree, or making the inverse transformation in some branches after the precondition extraction. Further research in this field is required to get an optimal precondition extraction.

7 Remember the basic algebraic transformation in which we are replicating the original A branch in each conversion: A OR (B AND C) = (A OR B) AND (A OR C) (B AND C) OR A = (B OR A) AND (C OR A)

Page 61: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

61

Josep Coves

b. Cheating predicates We may be wondering whether the CNF transformations will create any undesired collateral effects to our Predicate. At first sight we can correctly think that it would not have any effect, because with the CNF transformation we reach a logically equivalent predicate[2] and so we might get the same results. This is true for all correct predicates, but we cannot take for granted the way how the original predicate was written becasue it can be a cheating predicate. For instance, in JAVA we can define a perfectly correct logical predicate like this:

This predicate is correct in JAVA because we assume that the second clause is going to be evaluated only if the first clause was true. So in this case it is impossible to get an exception, because we will never evaluate v[i]>=0 when i is lower than zero. But if we change the order of the clauses we easily get a predicate which at any time can raise an exception, and so it will be impossible to be evaluated. Before the CNF transformation, the evaluation order of the clauses was always the same, but after the CNF transformation we can not guarantee that a certain clause is evaluated before any other, and this is dangerous in cheating predicates. If the predicate was written with this kind of assumptions we can not guarantee that our resulting predicate will not raise an exception when evaluated. For example, if we have the following correct CI (which will never raise an exception):

Transforming it into CNF:

We get a predicate which can raise an exception in the penultimate OR conjunction and will raise an exception in the last OR conjunction. Therefore, being aware of that issue, we can avoid it by forcing the programmer to write clauses that can be individually evaluated without raising an exception.

5.7.2. Simplify by applying logical rules One possible optimization is modifying the resulting tree using logical rules in order to simplify the tree. For instance, we can apply identity logic rule (A AND A = AND), inverse distributive logical rules ((A AND B) OR (A AND C) = A AND (B OR C)), etc. This can be really helpful to simplify our predicate. For instance if we try to extract A clause from the following predicate:

It is clear that our result will be A if we follow our steps. But if we apply the distributive rule before applying the refactoring:

A AND (B OR C)

(i>=0 OR i<0) AND (i>=0 OR v[-i]<0) AND (v[i]>=0 OR i<0) AND (v[i]>=0 OR v[-i]<0)

(i>=0 AND v[i]>=0) OR (i<0 AND v[-i]<0)

Assert (i>=0 && v[i]>=0);

Page 62: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

62

Josep Coves

Applying CNF Transformation we will get the following predicate:

And applying the ASR and OSR rules we reach the following predicate:

Therefore, we reached this precondition: (A OR A) AND (B OR A) AND (A OR C) instead the optimal one which was only A. We should continue applying logical rules and ASR/OSR rules until we reach the optimal precondition. Deciding whether to apply one logical rule and to decide if we made the right choice is a complex problem; some rules or heuristics could be found in order to reach, not the best, but a good solution. The theory of circumscription of a predicate8 can be a helpful area to reach this goal [15]. Further research in this area is needed.

8 The basic definition of circumscription of a predicate P would be a sentence which states that there is no predicate smaller from P such that has the same behaviour.15. Doherty, P., W. Lukaszewick, and A. Szalas, Computing Circumscription Revisited: A Reduction Algorithm. Journal of Automated Reasoning, 1997. 18: p. 297-336.

(A OR A) AND (B OR A) AND (A OR C)

(A OR A) AND (B OR A) AND (A OR C) AND (B OR C)

(A AND B) OR (A AND C)

Page 63: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

63

Josep Coves

6 Implementation

6.1. Dealing with implementation One of the initial ideas of the dissertation was to implement a small refactoring extension which should maintain the assertions which are in the code. The very problem I had after looking at the current refactoring implementations is they are still in development stage, and some of the refactorings are automatically consistent because they treat assertions as any other expression. After looking in the platforms which implement refactorings (for instance the Rename refactoring), I discarded looking deeper into Net Beans 5.0 (Beta 2) [16] because it had a lot of bugs in the current implementation and also because the Eclipse 3.1 [11] platform implements more refactorings and they include the ones implemented in Net Beans. The refactorings implemented in Eclipse are (I will compare all of them with the refactoring definition in Fowler’s book [9]):

• Rename – Includes Rename Method of Fowler’s notation and also allows renaming any other field.

• Move* – Moves One Class from one file to another file (It is not included in Fowler’s list)

• Change Method Signature – This refactoring includes Add Parameter, Remove Parameter and also changing the Types of the parameters which is not included in Fowler’s list.

• Convert Anonymous Class to Nested* – This is one specific JAVA refactoring which allow us to give a name to an anonymous class9 by converting it into a Nested class10. A good example to understand this refactoring can be found in [19].

• Move Member Type to new File* – Creates a new file to move a class there. This refactoring is not considered in Fowler’s list.

• Push Down – This includes Push Down Method and Push Down field. • Push Up – This includes Push Up Method and Push Up Field. • Extract Interface+ • Generalize Type* - This refactoring lets us change any parameter,

variable, and return type to another type more General. • Use Supertype when possible* - allows replacing the references to a

type by its supertype, if it is possible. • Infer Generic Type Arguments* - Eclipse can attempt to infer type

parameters for all generic type references in a class, package, or project.

This is especially useful when migrating from Java 1.4 code to Java 5.0 code, allowing we to automatically make use of the generic classes in

Java's collections API. Eclipse can attempt to infer type parameters for all

generic type references in a class, package, or project. This is especially

9 An anonymous class is essentially a local class without a name. Instead of defining a local class and then instantiating it, you can often use an anonymous class to combine these two steps... an anonymous class is defined by a Java expression, not a Java statement. This means that an anonymous class definition can be included within a larger Java expression... 17. Flanagan, D., Java in a Nutshell: A Desktop Quick Reference, ed. O'Reilly. 2005 (5th Edition). 10 You can define a class as a member of another class. Such a class is called a nested class 18. SunMicrosystems, The JAVA tutorial, http://java.sun.com/docs/books/tutorial/. * Not Considered in Fowler’s Book + The name matches with the one given in Fowler’s Book. Further Detail in 9. Fowler, M., et al., Refactoring: Improving the Design of Existing Code., ed. Addison-Wesley. 2000.

Page 64: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

64

Josep Coves

useful when migrating from Java 1.4 code to Java 5.0 code, allowing we to automatically make use of the generic classes in Java's collections API[11].

• Inline - The Inline refactoring takes a reference to a method, static final field, or a local variable and replaces it with its code or value[20]. This includes Inline Method and Inline Temp.

• Extract Method+ • Extract Local Variable – Matches with Introduce Explaining Variable • Extract Constant – Matches with Replace Magic Number with Symbolic

Constant

• Introduce Parameter – Matches with Add Parameter • Introduce Factory – Matches with Replace Constructor with Factory

Method Convert Local Variable to Field* - Moves a declaration of a variable from inside a method to the top of the class where it is then visible to the entire class[20].

• Encapsulate Field+ After going deeper in the dissertation, we realised this job required a lot of extra time studying the extensibility of the Eclipse platform, and finally we decided to do a little proof of concept application that later could be added in the Eclipse platform.

6.2. Implemented Application We decided to make a small proof of concept application that covers all Chapter 5, to demonstrate the proposed precondition extraction is viable. The goal of our implementation is to provide a package which can be used later to extend current refactorings. In next sections we divide our implemented application in two main parts:

• A package which can be used to implement a future extension for the assertion-maintenance of our current refactorings.

• A visual interface that allows us to test this package in a comfortable way.

6.2.1. Base package

Figure 27 shows the class diagram of our implemented base package. As we can see the main class is preconditionExtraction which allows us to extract a CI from a predicate given in String form, the name of the field to extract and with the possibly of applying CNF Optimization. LogicBinaryTree contains the CI tree representation of the String we passed through preconditionExtraction, inside this class we implemented all the precondition extraction steps as class methods. TreeNode is the node structure from which the LogicBinaryTree is built. It contains all the implementation specific methods and fields required in order to implement optimal algorithms for the precondition extraction steps.

Page 65: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

65

Josep Coves

Figure 27

6.2.2. Visual application interface We also implemented a visual interface, shown in Figure 29, which allows us to:

• Try the precondition extraction base package through a comfortable window interface.

• Show a visual tree representation of the CI predicate and the resulting precondition tree. We can also choose to minimize the resulting tree view.

Figure 28 shows the class diagram of our implemented visual application interface base package. The main class is AppView, which is the responsible of the communication through the visual window interface and the base package. TreeWindow is the responsible of printing the graph represented in VisualTree class. VisualTree contains a visual information representation in order to print the information contained in a LogicBinaryTree class. GraphEdge and GraphNode are the classes from which a VisualTree is built.

Page 66: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

66

Josep Coves

Figure 28

6.2.3. Application restrictions and future extensio n Since we are building only a proof of concept application, we applied some restrictions in the use of the package in order to make the implementation easier. If in the future we want to use this package to extend any of the current refactoring implementations, we must be aware of those restrictions to use or modify the package properly. The package restrictions are:

• We must introduce all the predicates in pre-form11. This simplification has been made due to the fact that introducing a predicate in pre-form avoids us all troubles of associativities and parsing the input.

11 A pre-form operation is such that the operator is written before the operands. For instance, if we want to express the formula a + b in pre-form we must write it as + a b

Page 67: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

67

Josep Coves

If we wanted to forge a tree from a predicate written in the usual form we must have programmed a compiler, and we considered this unnecessary because surely later we will link this application with any current SDK in order to modify the their refactorings, and the SDK will provide its own expression compiler. If we want to delete this restriction we must modify forgeTree method inside LogicBinaryTree class. • We can not insert spaces inside any predicate clause. This restriction is due to the same issue explained in the last point. If we want to modify it we must also modify forgeTree method inside LogicBinaryTree class.

• No variable expressed in the predicate can be a substring of any other

clause. This restriction is due to the same issue explained in the first point. If we want to modify it we must also modify forgeTree and UpdateNodes methods inside LogicBinaryTree class.

6.3. Testing In the following sections we are going to try the application with different kind of inputs in order to see how it works in different situations.

6.3.1. Common Examples In our first section we are going to give a set of common examples in order to see how our application works in real situations.

a. Encapsulating the year field of a Movie Our first example will be the one we demonstrated before in last chapter. Following the design of Figure 15, we use this CI:

We must convert this CI into pre-form in order to introduce it in our application as we see in Figure 29.

CI = (Title ≠ null) AND((NOT Director directorsDatabase.exists(D irector)) AND (hasIntValue(Year) AND intValue(Year)>0 AND int Value(Year) <= getCurrentYear())

Page 68: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

68

Josep Coves

Figure 29

We get the CI tree shown in Figure 30.

Figure 30

And we also get the extracted precondition from Year field as we see in Figure 31 .

Page 69: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

69

Josep Coves

Figure 31

As we can see, the application reached the same solution we demonstrated before.

b. Encapsulating the empty field of a bounded Stack The second example will be also another demonstrated before in last chapter. Following the design of Figure 21, we use this CI:

This in pre-form will be:

If we extract the empty field, we get the trees shown in Figure 32 and Figure 33 from our application.

CI = AND 0<=count AND count<=capacity AND capacit y=array.length AND AND OR !empty count=0 OR empty count!=0 OR count<=0 arr ay[count]!=null count > 0 → array[count] != null

CI = 0 <= count AND count <= capacity AND capacity = array.length AND ((NOT empty OR count = 0) AND (empty OR NOT count = 0)) AND

AND (NOT count > 0 OR array[count] ≠ null) →

Page 70: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

70

Josep Coves

Figure 32

Figure 33

As we can see the application reached the same solution we demonstrated before.

c. CNF Examples As we said in chapter 5, sometimes it is useful to convert our predicate into CNF to reach a better solution. We will convert all the following CIs into CNF in order to know if the CNF transformation was useful or not.

i. Good CNF Optimization In Figure 34 we can see an example where applying CNF transformation helps us to get a best precondition.

Page 71: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

71

Josep Coves

Figure 34

ii. Too long precondition

Figure 35

In Figure 35 we can see that applying the CNF transformation we reach a tree, in which not only appears the same clauses, but also is bigger than the one we reached without applying the CNF transformation. Therefore, applying CNF transformation in that example we get a worse solution.

Resulting precondition tree applying CNF transformation

Resulting precondition tree without applying CNF transformation

Resulting precondition tree applying CNF transformation

Resulting precondition tree without applying CNF transformation

Page 72: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

72

Josep Coves

7 Conclusion and future work After studying assertions and refactorings we tried to decide whether assertions are maintained after the refactoring process. After a first glance we realised we needed a tool to be able to judge when assertions were useful and how to decide whether they were correct or not. The solution was found in design-by-contract assuring the assertions are useful and correct with respect to the design of the program. We made a novel classification of refactorings based on their assertion maintenance. We found there are some refactorings which are automatically maintained (see 3.3.1. Trivial assertion maintenance), some which require changes (see 3.3.2. Refactorings which require changes) and finally some that need the intervention of the programmer because they can not be automated (see 3.3.3. Refactorings which require assertion redefinition). This makes us realise that is impossible to automatically maintain the assertions in some refactorings, breaking the idea proposed in [5]. Therefore, since we are not able to automate them, we can inform the programmer that his intervention is required by using a new kind of warnings, like compilation warnings. Despite the fact that we can not adapt all refactorings automatically, there are many refactorings which allow it. We described and demonstrated the augmented refactoring steps in order to maintain the assertions in Pull Up Field (see 4.2), Pull Up Method (see 4.3) and Self Encapsulate Field (see 4.4) refactorings. Following the same process and being helped with a small schema to maintain refactorings (see 3.4) we can extend other refactorings, or decide that they are not able to be maintained. After demonstrating their steps an implementation integration of these steps in a refactoring tool would be necessary to test how they work. In order to maintain Self Encapsulate Field refactoring, we described and demonstrated the procedure of extracting the precondition of a setting method from the class invariant (see chapter 5). This procedure can help future refactorings which require the extraction of information from the CI, and it also help in many other areas, like Artificial Intelligence in which the procedure of extracting data from predicates is very common. Finally, we developed a small package which implements the precondition extraction from a class invariant (see chapter 6) and also a visual interface in order to demonstrate the package works properly. This package can be in time used to extend the Self Encapsulate Field in a current IDE like Eclipse or NetBeans. Even though we demonstrated the procedure of extracting the precondition from a class invariant we only gave a few guidelines. This procedure could be improved in order to reach better predicates. To improve the procedure, it would be helpful to do a deeper study about logical predicate simplification[15]. Further research is still required.

Page 73: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

73

Josep Coves

8 References 1. Hoare, C.A.R., An Axiomatic basis for computer programming, in

Communications of the ACM. 1969.

2. Rosenblum, D.S., A practical Approach to Programming With Assertions in IEEE Transactions on Software Engineering. 1995.

3. Malloy, B.A. and J.M. Voas, Programming with Assertions: A Prospectus. 2003.

4. Satpathy, M., N.T. Siebel, and D. Rodríguez, Using Assertions in Object Oriented Software Maintenance: Experiences and Recommendations.

5. Satpathy, M., N.T. Siebel, and D. Rodríguez, Assertions in Object Oriented Software Maintenance: Analysis and a Case Study.

6. Meyer, B., Object Oriented Software Construction. 2nd ed, ed. P. Hall. 1997.

7. SunMicrosystems. Programming With Assertions. http://java.sun.com [cited; Available from: http://java.sun.com/j2se/1.4.2/docs/guide/lang/assert.html.

8. Martin, R.C., The Liskov Substitution Principle. C++ Report, 1996.

9. Fowler, M., et al., Refactoring: Improving the Design of Existing Code., ed. Addison-Wesley. 2000.

10. Opdyke, W.F., Refactoring Object Oriented Frameworks, in Department of Computer Science. 1992, University of Illionis Urbana-Champaign.

11. EclipseFoundation. [cited; Available from: http://www.eclipse.org.

12. Ribet, P., et al., Conformace of agents in the Eiffel language. Journal Of Object Technology, March-Aprile 2003. 2: p. 19.

13. Tour, T.B.d.l., An optimality result for clause form translation. Symbolic Computation 14:283-301, 1992.

14. Sheridan, D., The optimality of a fast CNF conversion and its use with SAT. Technical Report APES-82-2002, March 2004. APES Research Group.

15. Doherty, P., W. Lukaszewick, and A. Szalas, Computing Circumscription Revisited: A Reduction Algorithm. Journal of Automated Reasoning, 1997. 18: p. 297-336.

16. SunMicrosystems, Net Beans. http://www.netbeans.org/.

17. Flanagan, D., Java in a Nutshell: A Desktop Quick Reference, ed. O'Reilly. 2005 (5th Edition).

18. SunMicrosystems, The JAVA tutorial, http://java.sun.com/docs/books/tutorial/.

Page 74: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

74

Josep Coves

19. Gallardo, D. Refactoring for Everyone. IBM 2003 [cited; Available from: http://www-128.ibm.com/developerworks/opensource/library/os-ecref/.

20. Enns, R., Refactoring in Eclipse. 2004: Department of Computer Science, University of Manitoba, Canada.

Page 75: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

75

Josep Coves

9 Appendix – Bertrand Meyer’s rules i Type Conformance Rule An assignment x := y , or the use of y as actual argument corresponding to the formal argument x in a routine call, is only valid if the type of y conforms to the type of x. In an assignment x := y , the types of x and y do not have, with inheritance, to be identical; the rule is that the type of y must simply conform to the type of x . A class D conforms to a class A if and only if it is a descendant of A (which includes the case in which A and D are the same class) 6. Meyer, B., Object Oriented Software Construction. 2nd ed, ed. P. Hall. 1997. Chapter 14 ii Assertion Redeclaration Rule (1) A routine redeclaration may only replace the original precondition by one equal or weaker, and the original postcondition by one equal or stronger. 6. Meyer, B., Object Oriented Software Construction. 2nd ed, ed. P. Hall. 1997. Chapter 16 - p.573 iii Assertion Redeclaration Rule (2) In the redeclared version of a routine, it is not permitted to create a new assertion (starting from zero). Instead you may: Use a new clause added to be OR-ed with the original precondition Use a new clause to be AND-ed with the original postcondition. In the absence of such a clause, the original assertion is retained. 6. Ibid. Chapter 16 - p. 578 iv Invariant Rule An assertion I is a correct class invariant for class C if and only if it meets the following two conditions: E1. Every creation procedure of C, when applied to arguments satisfying its precondition in a state where the attributes have their default values, yields a state satisfying I. E2. Every exported routine of the class, when applied to arguments and a state satisfying both I and the routine’s precondition, yields a state satisfying I. 6. Ibid. Chapter 11 - p. 366 v Parent’s invariant rule The invariants of all the parents of a class apply to the class itself 6. Ibid. Chapter 16 - p. 570 vi Definition: class correctness The invariant of a class affects all the contracts between a routine of the class and a client. […] It was noted above that we may consider the invariant as being added to both the precondition and postcondition of every exported routine. Let body be the body of the routine, pre its precondition, post its postcondition and INV the class invariant: {INV and pre} body {INV and post} (any execution of body, started in any state in which INV and pre both hold, will terminate in a state in which both INV and post hold.)[…] A class is correct with respect to its assertions if and only if: C1 · For any valid set of arguments xp to a creation procedure p: {DefaultC and prep (xp)} Bodyp {postp(xp) and INV} C2 · For every exported routine r and any set of valid arguments xr: {prer (xr) and INV} Bodyr {postr(xr) and INV} 6. Ibid. Chapter 11 - p. 369 vii Assertion violation rule (2) A precondition violation is the manifestation of a bug in the client. A postcondition violation is the manifestation of a bug in the supplier. 6. Ibid. Chapter 11 - p. 347

Page 76: Maintenance of Assertions Consistency Across Code Refactorings

Maintenance of Assertions Consistency Across Code Refactorings

76

Josep Coves

viii Implementation Invariants Certain assertions appear in invariants although they have no direct counterparts in the abstract data type specifications. These assertions involve attributes, including some secret attributes which, by definition, would be meaningless in the abstract data type. Such assertions constitute the part of the class invariant known as the implementation invariant. They serve to express the consistency of the representation chosen in the class. 6. Ibid. Chapter 16 - p. 376