SlideShare a Scribd company logo
1 of 76
Download to read offline
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
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
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.
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
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
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.
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
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
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(){
…
}
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
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
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.
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)
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;
}
}
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)vi
we 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 == top &&
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 );
}
}
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.
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..
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.
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
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
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.
Maintenance of Assertions Consistency Across Code Refactorings
22
Josep Coves
3 Maintaining Assertions through Refactoring Process
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 && Inv());
return(result);
}
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;
…
}
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 && Inv());
//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 && Inv());
//Postcondition
return(result);
}
…
}
class B{
public String value;
...
}
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”)
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;
}
…
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 Refactoring
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
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() * _numberOfCoconuts;
}
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() * _numberOfCoconuts;
}
}
Class Norwgian_blue Extends Bird{
public int getSpeed(){
return (_isNailed) ? 0 : getBaseSpeed(_voltage);
}
}
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);
Maintenance of Assertions Consistency Across Code Refactorings
30
Josep Coves
3.3.3. Refactorings which require assertion redefinition
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
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)}
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
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.
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
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
∈⇒
⊆
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.
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
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’’()
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.
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)*amountPerDay()}*/
}
In Preferred Customer class
double chargeFor (date Start, date End){
/* Pre = {Start ≠ null AND End ≠ null AND Start <= End} */
…
/* Post = {result = (numberOfDays(Start, End)*amountPerDay()) –
discountPercentage(Start,End)}*/
}
void createBill (date Date){
/* Pre = {Date ≠ null} */
double chargeAmount = chargeFor (lastBillDate, date);
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)
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, date);
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)*amountPerDay()) 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)
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.
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 refactoring
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)
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
Maintenance of Assertions Consistency Across Code Refactorings
Maintenance of Assertions Consistency Across Code Refactorings
Maintenance of Assertions Consistency Across Code Refactorings
Maintenance of Assertions Consistency Across Code Refactorings
Maintenance of Assertions Consistency Across Code Refactorings
Maintenance of Assertions Consistency Across Code Refactorings
Maintenance of Assertions Consistency Across Code Refactorings
Maintenance of Assertions Consistency Across Code Refactorings
Maintenance of Assertions Consistency Across Code Refactorings
Maintenance of Assertions Consistency Across Code Refactorings
Maintenance of Assertions Consistency Across Code Refactorings
Maintenance of Assertions Consistency Across Code Refactorings
Maintenance of Assertions Consistency Across Code Refactorings
Maintenance of Assertions Consistency Across Code Refactorings
Maintenance of Assertions Consistency Across Code Refactorings
Maintenance of Assertions Consistency Across Code Refactorings
Maintenance of Assertions Consistency Across Code Refactorings
Maintenance of Assertions Consistency Across Code Refactorings
Maintenance of Assertions Consistency Across Code Refactorings
Maintenance of Assertions Consistency Across Code Refactorings
Maintenance of Assertions Consistency Across Code Refactorings
Maintenance of Assertions Consistency Across Code Refactorings
Maintenance of Assertions Consistency Across Code Refactorings
Maintenance of Assertions Consistency Across Code Refactorings
Maintenance of Assertions Consistency Across Code Refactorings
Maintenance of Assertions Consistency Across Code Refactorings
Maintenance of Assertions Consistency Across Code Refactorings
Maintenance of Assertions Consistency Across Code Refactorings
Maintenance of Assertions Consistency Across Code Refactorings
Maintenance of Assertions Consistency Across Code Refactorings
Maintenance of Assertions Consistency Across Code Refactorings
Maintenance of Assertions Consistency Across Code Refactorings

More Related Content

Similar to Maintenance of Assertions Consistency Across Code Refactorings

Enhancing the interaction space of a tabletop computing system to design pape...
Enhancing the interaction space of a tabletop computing system to design pape...Enhancing the interaction space of a tabletop computing system to design pape...
Enhancing the interaction space of a tabletop computing system to design pape...Francesco Bonadiman
 
A DISSERTATION SUBMITTED IN PARTIAL FULFILLMENT OF THE REQUIREMENTS FOR THE D...
A DISSERTATION SUBMITTED IN PARTIAL FULFILLMENT OF THE REQUIREMENTS FOR THE D...A DISSERTATION SUBMITTED IN PARTIAL FULFILLMENT OF THE REQUIREMENTS FOR THE D...
A DISSERTATION SUBMITTED IN PARTIAL FULFILLMENT OF THE REQUIREMENTS FOR THE D...Jim Jimenez
 
Design and Development of a Knowledge Community System
Design and Development of a Knowledge Community SystemDesign and Development of a Knowledge Community System
Design and Development of a Knowledge Community SystemHuu Bang Le Phan
 
The dark and the bright side of Solid State Drives for computer forensics in ...
The dark and the bright side of Solid State Drives for computer forensics in ...The dark and the bright side of Solid State Drives for computer forensics in ...
The dark and the bright side of Solid State Drives for computer forensics in ...gaestar
 
A Thesis Submitted To The School Of Graduate Studies Of Addis Ababa Universit...
A Thesis Submitted To The School Of Graduate Studies Of Addis Ababa Universit...A Thesis Submitted To The School Of Graduate Studies Of Addis Ababa Universit...
A Thesis Submitted To The School Of Graduate Studies Of Addis Ababa Universit...Sophia Diaz
 
Internship Final Report
Internship Final Report Internship Final Report
Internship Final Report Nadia Nahar
 
Computational methods of Hepatitis B virus genotyping
Computational methods of Hepatitis B virus genotypingComputational methods of Hepatitis B virus genotyping
Computational methods of Hepatitis B virus genotypingNguyen Nhat Tien
 
Translation quality evaluation of Ankit Pandas article Why Did China Set Up a...
Translation quality evaluation of Ankit Pandas article Why Did China Set Up a...Translation quality evaluation of Ankit Pandas article Why Did China Set Up a...
Translation quality evaluation of Ankit Pandas article Why Did China Set Up a...TieuNgocLy
 
Teachingandlearningwithweb2 0-110523172514-phpapp01
Teachingandlearningwithweb2 0-110523172514-phpapp01Teachingandlearningwithweb2 0-110523172514-phpapp01
Teachingandlearningwithweb2 0-110523172514-phpapp01Francesca Ferrante
 
Santa maria-ph d-thesis 2009_visual genre conventions and user performance on...
Santa maria-ph d-thesis 2009_visual genre conventions and user performance on...Santa maria-ph d-thesis 2009_visual genre conventions and user performance on...
Santa maria-ph d-thesis 2009_visual genre conventions and user performance on...luisphd
 
Serendipitous Web Applications through Semantic Hypermedia
Serendipitous Web Applications through Semantic HypermediaSerendipitous Web Applications through Semantic Hypermedia
Serendipitous Web Applications through Semantic HypermediaJyotirmoy Dey
 

Similar to Maintenance of Assertions Consistency Across Code Refactorings (20)

Enhancing the interaction space of a tabletop computing system to design pape...
Enhancing the interaction space of a tabletop computing system to design pape...Enhancing the interaction space of a tabletop computing system to design pape...
Enhancing the interaction space of a tabletop computing system to design pape...
 
A DISSERTATION SUBMITTED IN PARTIAL FULFILLMENT OF THE REQUIREMENTS FOR THE D...
A DISSERTATION SUBMITTED IN PARTIAL FULFILLMENT OF THE REQUIREMENTS FOR THE D...A DISSERTATION SUBMITTED IN PARTIAL FULFILLMENT OF THE REQUIREMENTS FOR THE D...
A DISSERTATION SUBMITTED IN PARTIAL FULFILLMENT OF THE REQUIREMENTS FOR THE D...
 
Exploring online identity with LEGO
Exploring online identity with LEGOExploring online identity with LEGO
Exploring online identity with LEGO
 
Design and Development of a Knowledge Community System
Design and Development of a Knowledge Community SystemDesign and Development of a Knowledge Community System
Design and Development of a Knowledge Community System
 
TR1643
TR1643TR1643
TR1643
 
The dark and the bright side of Solid State Drives for computer forensics in ...
The dark and the bright side of Solid State Drives for computer forensics in ...The dark and the bright side of Solid State Drives for computer forensics in ...
The dark and the bright side of Solid State Drives for computer forensics in ...
 
A Thesis Submitted To The School Of Graduate Studies Of Addis Ababa Universit...
A Thesis Submitted To The School Of Graduate Studies Of Addis Ababa Universit...A Thesis Submitted To The School Of Graduate Studies Of Addis Ababa Universit...
A Thesis Submitted To The School Of Graduate Studies Of Addis Ababa Universit...
 
Internship Final Report
Internship Final Report Internship Final Report
Internship Final Report
 
Computational methods of Hepatitis B virus genotyping
Computational methods of Hepatitis B virus genotypingComputational methods of Hepatitis B virus genotyping
Computational methods of Hepatitis B virus genotyping
 
PhD report
PhD reportPhD report
PhD report
 
PhD report
PhD reportPhD report
PhD report
 
ThesisCIccone
ThesisCIcconeThesisCIccone
ThesisCIccone
 
Translation quality evaluation of Ankit Pandas article Why Did China Set Up a...
Translation quality evaluation of Ankit Pandas article Why Did China Set Up a...Translation quality evaluation of Ankit Pandas article Why Did China Set Up a...
Translation quality evaluation of Ankit Pandas article Why Did China Set Up a...
 
Teachingandlearningwithweb2 0-110523172514-phpapp01
Teachingandlearningwithweb2 0-110523172514-phpapp01Teachingandlearningwithweb2 0-110523172514-phpapp01
Teachingandlearningwithweb2 0-110523172514-phpapp01
 
PhD Thesis
PhD ThesisPhD Thesis
PhD Thesis
 
NBLovett
NBLovettNBLovett
NBLovett
 
Santa maria-ph d-thesis 2009_visual genre conventions and user performance on...
Santa maria-ph d-thesis 2009_visual genre conventions and user performance on...Santa maria-ph d-thesis 2009_visual genre conventions and user performance on...
Santa maria-ph d-thesis 2009_visual genre conventions and user performance on...
 
Serendipitous Web Applications through Semantic Hypermedia
Serendipitous Web Applications through Semantic HypermediaSerendipitous Web Applications through Semantic Hypermedia
Serendipitous Web Applications through Semantic Hypermedia
 
Kumar_Akshat
Kumar_AkshatKumar_Akshat
Kumar_Akshat
 
Tesi
TesiTesi
Tesi
 

Maintenance of Assertions Consistency Across Code Refactorings

  • 1. 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
  • 2. 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
  • 3. 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.
  • 4. 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
  • 5. 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
  • 6. 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.
  • 7. 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
  • 8. 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
  • 9. 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(){ … }
  • 10. 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
  • 11. 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
  • 12. 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.
  • 13. 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)
  • 14. 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; } }
  • 15. 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)vi we 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 == top && 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 ); } }
  • 16. 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.
  • 17. 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..
  • 18. 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.
  • 19. 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
  • 20. 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
  • 21. 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.
  • 22. Maintenance of Assertions Consistency Across Code Refactorings 22 Josep Coves 3 Maintaining Assertions through Refactoring Process 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 && Inv()); return(result); }
  • 23. 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; … }
  • 24. 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 && Inv()); //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 && Inv()); //Postcondition return(result); } … } class B{ public String value; ... }
  • 25. 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”)
  • 26. 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; } …
  • 27. 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 Refactoring 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
  • 28. 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() * _numberOfCoconuts; } 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() * _numberOfCoconuts; } } Class Norwgian_blue Extends Bird{ public int getSpeed(){ return (_isNailed) ? 0 : getBaseSpeed(_voltage); } }
  • 29. 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);
  • 30. Maintenance of Assertions Consistency Across Code Refactorings 30 Josep Coves 3.3.3. Refactorings which require assertion redefinition 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
  • 31. 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)}
  • 32. 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
  • 33. 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.
  • 34. 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
  • 35. 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 ∈⇒ ⊆
  • 36. 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.
  • 37. 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
  • 38. 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’’()
  • 39. 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.
  • 40. 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)*amountPerDay()}*/ } In Preferred Customer class double chargeFor (date Start, date End){ /* Pre = {Start ≠ null AND End ≠ null AND Start <= End} */ … /* Post = {result = (numberOfDays(Start, End)*amountPerDay()) – discountPercentage(Start,End)}*/ } void createBill (date Date){ /* Pre = {Date ≠ null} */ double chargeAmount = chargeFor (lastBillDate, date); 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)
  • 41. 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, date); 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)*amountPerDay()) 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)
  • 42. 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.
  • 43. 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 refactoring 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)
  • 44. 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