refactoring with contracts

10
Refactoring with Contracts Maayan Goldstein School of Computer Science Tel Aviv University [email protected] Yishai A. Feldman Efi Arazi School of Computer Science The Interdisciplinary Center, Herzliya [email protected] Shmuel Tyszberowicz School of Computer Science The Academic College of Tel Aviv Yaffo [email protected] Abstract Design by contract is a practical methodology for developing code together with its specification. The contract consists of class invariants and method pre- and postconditions. As the code is refactored, specifi- cations of internal units change with the code. There are mutual influences between the code and the con- tract. The assertions that constitute the contract are Java expressions; refactorings such as Rename Method must change these assertions as well as the code. The contract has methodological implications, which serve as preconditions on some refactorings; these must be checked before performing those refac- torings. In addition, some contract modifications fol- low from certain refactorings, and can be done auto- matically. Development environments that support design by contract must take these influences into account. We report on the implementation in Eclipse of sev- eral refactorings that involve both code and con- tract. These show how contracts are modified in re- sponse to code changes, how contracts prevent cer- tain changes, and how new contracts are computed for newly-generated methods and classes. 1 Introduction Design by contract [13] is a practical methodol- ogy for developing object-oriented programs together with their specifications. It offers immediate benefits This research was supported by a grant from the Israel Science Foundation (grant No. 1011/04). in terms of early error detection as well as long-term process improvement. The specifications are given as part of the code itself in the form of assertions such as class invariants and method pre- and postconditions. The contract and the code have mutual influences. In one direction, changes in the code require corre- sponding contract changes. For example, renaming fields, methods, or classes should modify the relevant references in the contract as well. Also, the creation of new methods or classes requires contracts for the new elements. In the other direction, the contract im- plies preconditions for certain transformations. For ex- ample, adding an inheritance link between two classes may be illegal if it causes preconditions to be strength- ened or postconditions and invariants to be weakened in the subclass. (See Section 2.) In order to demonstrate the required techniques, we have implemented an Eclipse [5] plugin called Crepe, 1 which contributes a number of refactorings that in- volve both code and contract. Some of these tech- niques are relatively simple, others require theorem- proving capability, and some require advances in the state of the art. This paper is organized as follows. The next sec- tion discusses related work, and explains the technical background needed for the rest of the paper. Section 3 gives a detailed example of the use of Crepe, show- ing the requirements from a tool that refactors con- tracts with the code and demonstrating its capabilities. Section 4 discusses the implementation of Crepe and the general techniques it uses, and Section 5 concludes with a discussion and future work. 1 Crepe stands for Contract REfactoring Plugin for Eclipse. 1

Upload: independent

Post on 19-Nov-2023

1 views

Category:

Documents


0 download

TRANSCRIPT

Refactoring with Contracts

Maayan GoldsteinSchool of Computer Science

Tel Aviv [email protected]

Yishai A. FeldmanEfi Arazi School of Computer ScienceThe Interdisciplinary Center, Herzliya

[email protected]

Shmuel TyszberowiczSchool of Computer Science

The Academic College of Tel Aviv [email protected]

Abstract

Design by contract is a practical methodology fordeveloping code together with its specification. Thecontract consists of class invariants and method pre-and postconditions. As the code is refactored, specifi-cations of internal units change with the code. Thereare mutual influences between the code and the con-tract. The assertions that constitute the contractare Java expressions; refactorings such as RenameMethod must change these assertions as well as thecode. The contract has methodological implications,which serve as preconditions on some refactorings;these must be checked before performing those refac-torings. In addition, some contract modifications fol-low from certain refactorings, and can be done auto-matically.

Development environments that support design bycontract must take these influences into account.We report on the implementation in Eclipse of sev-eral refactorings that involve both code and con-tract. These show how contracts are modified in re-sponse to code changes, how contracts prevent cer-tain changes, and how new contracts are computed fornewly-generated methods and classes.

1 Introduction

Design by contract [13] is a practical methodol-ogy for developing object-oriented programs togetherwith their specifications. It offers immediate benefits

This research was supported by a grant from the Israel ScienceFoundation (grant No. 1011/04).

in terms of early error detection as well as long-termprocess improvement. The specifications are given aspart of the code itself in the form of assertions such asclass invariants and method pre- and postconditions.

The contract and the code have mutual influences.In one direction, changes in the code require corre-sponding contract changes. For example, renamingfields, methods, or classes should modify the relevantreferences in the contract as well. Also, the creationof new methods or classes requires contracts for thenew elements. In the other direction, the contract im-plies preconditions for certain transformations. For ex-ample, adding an inheritance link between two classesmay be illegal if it causes preconditions to be strength-ened or postconditions and invariants to be weakenedin the subclass. (See Section 2.)

In order to demonstrate the required techniques, wehave implemented an Eclipse [5] plugin called Crepe,1

which contributes a number of refactorings that in-volve both code and contract. Some of these tech-niques are relatively simple, others require theorem-proving capability, and some require advances in thestate of the art.

This paper is organized as follows. The next sec-tion discusses related work, and explains the technicalbackground needed for the rest of the paper. Section 3gives a detailed example of the use of Crepe, show-ing the requirements from a tool that refactors con-tracts with the code and demonstrating its capabilities.Section 4 discusses the implementation of Crepe andthe general techniques it uses, and Section 5 concludeswith a discussion and future work.

1Crepe stands for Contract REfactoring Plugin for Eclipse.

1

2 Motivation and Related Work

The design-by-contract methodology was intro-duced by Meyer [13] and is an integral part of the Eif-fel programming language [12]. While not part of theJava language, it is supported by various external tools[1, 2, 4, 10, 11, 15]. These tools typically look for thecontract in specially-formatted comments.

The contract consists of class invariants, andmethod pre- and postconditions. Method precondi-tions must hold on entry to the method; it is the respon-sibility of the caller to make sure they hold when thecall is made. Method postconditions must hold on exitfrom the method, but only in the case that the methodwas called correctly (that is, if the preconditions heldon entry). Class invariants must hold for every objectof the class on entry to and exit from every non-privatemethod. Postconditions and invariants must be main-tainted by the implementer of the class. The contractis part of the public information of the class, for use byclients.

The contract has methodological implications [13]regardless of whether it is checked at runtime or not.First, it clearly specifies the assumptions underlyingthe use of existing classes. Second, it places con-straints on inheritance (see below). Finally, the as-sertions can be used to prove the correctness of theprogram at various levels of rigor.

If a class B inherits from another class A, all in-stances of B are ipso facto elements of A as well.If B has a stronger precondition than A on somemethod, a client of A might inadvertently be in vio-lation of the precondition when using an element ofB polymorphically. For example, suppose that A rep-resents a data structure that may hold null elements,but the implementation of B does not support nullelements. The method that inserts elements into thedata structure will not have any precondition in A (for-mally, its precondition there is true), but will have aprecondition in B. A client of A may not even beaware of the existence of B, let alone of its precon-ditions. Such a client is justified in calling the inser-tion method with a null argument, but the program willfail if the actual data structure is of type B. This isclearly unacceptable, and therefore inheriting classesmay only weaken preconditions, that is, demand lessfrom callers. Analogously, inheriting classes are onlyallowed to strengthen invariants and postconditions.These, as the supplier’s responsibility, can only pro-vide more to polymorphic clients, not less than whatthey can expect from the contracts they are aware of.

It is important to note that these constraints are nota result of using the design-by-contract methodology;

the bugs caused by inconsistent assumptions betweenclient and supplier or within the inheritance hierar-chy are the same in any case. The design-by-contractmethodology helps by identifying those bugs earlier.

Refactoring [8] is a crucial technique in agile soft-ware development, allowing developers to maintaina clean and simple design as the implementationchanges. Like design by contract, the refactoringmethodology is also useful without tool support. Yethere, too, refactoring tools can be very effective inspeeding up development and making it more reliable.Modern Java IDEs such as Eclipse [5], NetBeans [14],and IntelliJ IDEA [9] all support various refactorings.This is an active area of research and development, andthe range of refactorings available in these and othertools is continuously growing.

Refactoring requires a comprehensive suite of auto-matic unit tests. Unfortunately, writing all the neces-sary tests is hard work. What’s more, unit tests need tobe refactored together with the code, since many refac-torings invalidate existing unit tests even though theydo not change the overall functionality. In addition,it is practically impossible to tell when enough unittests have been written to precisely define the desiredfunctionality of the units being tested. Thus, writingunit tests is a never-ending task, which even the great-est proponents of test-driven development [3] see as anecessary evil.

Feldman [6] argues that design by contract can re-place most of the assertions in unit-testing code. Testcases are then left with the responsibility of exercis-ing the code. This enables programmers to write testcases at a higher level of abstraction; for example,writing tests for classes or even sets of classes insteadof unit tests for each method. Testing at higher lev-els is less sensitive to internal changes, and such testsneed to be modified less often. For example, one ofus (Feldman) has had occasion to implement the RSApublic-key algorithm on a number of different archi-tectures. The algorithm is computation-intensive, andthe large-number modular operations at its heart areaggressively optimized. In order to unit-test the com-ponents of the program, it would be necessary to writespecial programs to generate correct data for testing!This problem is exacerbated by the fact that the variousimplementations of the algorithm are very different, totake advantage of the capabilities of the hardware. Incontrast, a single test program can easily generate ran-dom data, encrypt it and decrypt the result, and com-pare the final data with the original. Such a programworks on all architectures without modification, and isguaranteed to fully exercise the whole implementationof the RSA algorithm. Of course, when this test fails,

2

void testStack(){Stack s = new Stack();for (int i = 0; i < 100; i++)

s.push(new Integer(i));assertEquals(100, s.size());for (int i = 0; i < 100; i++)

s.pop();assertEquals(0, s.size());

}

Figure 1. A test case for the Stack class.

it is quite difficult to pinpoint the error; this is wherethe contract exhibits its power of catching errors closeto their sources.

As another example, think of a class that imple-ments a stack. Each method, such as push and pop,needs to be tested under a variety of conditions. Infact, it is usually impossible to test pop without usingpush. This complicates test cases, creates strong re-lationships between them, requires code copying, andimpedes refactoring. Given the appropriate contract, aclass-level test can create a stack and perform variousoperations on it, relying on the contract for catching er-rors, with perhaps a few assertions as sanity checks fora long operation. An example of such a test is shown inFigure 1. Such tests can completely replace method-level tests, and can be more stable. (The contract for asimilar class is shown in Figure 2.

In terms of testing (but not methodologically!), as-sertions in unit tests may be interchangeable with post-conditions and class invariants. However, precondi-tions are the client’s responsibility, and therefore as-serting preconditions in test cases can only serve tocheck the test case itself, not the application code. Theonly way to test for preconditions is on entry to thecorresponding methods, and only when they are calledfrom inside the application rather than directly from atest case. Unit tests usually try to avoid this complex-ity, and are therefore unsuitable for testing that clientcode satisfies the preconditions of the methods it calls.

Of course, as the code is developed and refactored,the contract also changes (perhaps more slowly).While refactoring does not change the overall func-tionality, the specifications of internal classes andmethods do change. When refactoring their code, pro-grammers think about the intended functionality ofeach part being modified. Thus, it is more naturalfor them to modify the contract, which expresses thisfunctionality, than to refactor the test cases [6]. How-

ever, the contract has sometimes subtle implications,and a good development environment therefore needsto support refactoring of the code together with thecontract. Such an environment will support the in-herent synergy between design by contract and otheragile practices, making the development process lesspainful and error-prone. Crepe is a first step in thisdirection.

3 Example

Suppose we have two classes in the implemen-tation of the scheduler of an operating system:ReadyQueue, which is a queue that holds jobs readyfor execution, and FreeJobs, a collection of unusedjob objects, maintained as a stack. At some point werealize that a stack and a queue are instances of a moregeneral data structure, a dispenser. A dispenser holdsitems added to it, and, whenever non-empty, has a“top” element. For a stack, the top element is the lastone inserted, whereas for a queue it is the earliest.

We would like to create a new class calledJobDispenser, from which both our existingclasses will inherit. The Extract Superclass refactoringtakes two classes and creates a new superclass fromwhich they are made to inherit. Crepe implements theExtract Superclass refactoring, and can apply it to twoor more classes. In addition, it includes the contract inthe refactoring.

The first obstacle we face in our example is thatmethod names are inconsistent. For the queue weuse enqueue and dequeue, whereas for the stackwe use push and pop. We can use the RenameMethod refactoring to change the names to insertand remove. However, if the contract contains anyreferences to the old method names, these referencesshould be changed as well. In the same way, many ex-isting refactorings need to treat the contract as part ofthe code. This is relatively easy to implement, and allCrepe refactorings do that.

The code after renaming is shown in Figure 2. Forbrevity, many details have been omitted, includingmost of the implementation and parts of the contract.

Both classes start with the same invariant (lines1 and 18), which states that their sizes (that is, thenumber of elements they contain) may not be nega-tive. The first method, insert, has two precondi-tions (lines 4–5, 20–21). The first is the same in bothsubclasses, and states that the parameter may not benull. The second puts different requirements on thevalue of job.status(); in ReadyQueue it mustbe equal to the constant READY (which is defined inthe class Job, not shown here), while in FreeJobs

3

1 /** @inv size() >= 0 */2 public class ReadyQueue {3 private List elements = new LinkedList();

4 /** @pre job != null5 * @pre job.status() == Job.READY6 * @post elements.get(0).equals(job)7 * @post size() == $prev(size()) + 18 */9 public void insert(Job job) { /* ... */ }

10 /** @pre size() > 011 * @post size() == $prev(size()) - 112 */13 public void remove() { /* ... */ }

14 /** @post (size() == 0) == ($ret == null) */15 public Job top() { /* ... */ }

16 public int size() { /* ... */ }17 }

18 /** @inv size() >= 0 */19 public class FreeJobs {20 /** @pre job != null21 * @pre job.status() == Job.INACTIVE22 * @post top() == job23 * @post size() == $prev(size()) + 124 */25 public void insert(Job job) { /* ... */ }

26 /** @pre size() > 027 * @post size() == $prev(size()) - 128 */29 public void remove() { /* ... */ }

30 /** @pre size() > 031 * @post $ret != null32 * @post $ret.status() == Job.INACTIVE33 */34 public Job top() { /* ... */ }

35 public int size() { /* ... */ }36 }

Figure 2. Two classes before Extract Superclass refactoring (excerpt).

it must be equal to INACTIVE, a different constant.

Each of the two classes has two postconditionsfor this method (lines 6–7, 22–23). The first isdifferent, and is related to the different ways inwhich the two data structures manage their elements.

(Note that the postcondition of ReadyQueue is notmeant for clients, because it refers to the privatefield elements; the corresponding postcondition forFreeJobs is understandable by clients, because itrefers to a public method.) The second postcondition

4

is the same in both cases, and states that the number ofelements in the data structure increases by one. Thisdemonstrates the special syntax $prev(e), whichdenotes the value of the expression e on entry to themethod.

The method remove has identical contracts in bothclasses, stating that the method may only be calledwhen the data structure is not empty (that is, its sizeis positive), and that when it returns, the number of el-ements in the data structure is reduced by one (lines10–11, 26–27).

The method top is treated in different ways in thetwo classes. In ReadyQueue, top has no precondi-tions; if the method is called when the queue is empty,the method simply returns null. The postcondition(line 14) states that this is the one and only case inwhich the method returns null. (The special vari-able $ret denotes the method’s return value.) In con-trast, in FreeJobs this method does have a precon-dition (line 30), which states that it may not be calledwhen the data structure is empty. The postcondition onthe following line guarantees, in turn, that the methodnever returns null. In this class, this method has anadditional postcondition, addressing the status of thereturned job.

3.1 Extract Superclass

Since the two classes were developed indepen-dently, there are various discrepancies between them,like the contract for top. Such inconsistencies maycause difficulties when trying to unify the two classesunder a single superclass.

As mentioned above, preconditions may not bestrengthened in subclasses, while postconditions andinvariants may not be weakened. Therefore, whencomputing a precondition for a method in the newly-generated superclass, Crepe takes the strongest com-mon precondition. Similarly, invariants and postcon-ditions are taken to be the weakest of the common partof the corresponding assertions from the subclasses.(See Section 4.3 for more details.) Figure 3 shows thenewly-generated superclass.

This example shows that common assertions havebeen moved to the new superclass. The most sig-nificant part of the new contract is the preconditionfalse for insert (line 3). This indicates that thepreconditions in the two subclasses are contradictory.And indeed they are: one requires job.status()to be equal to Job.READY, while the other requiresit to be equal to Job.INACTIVE; these are two dif-ferent constants. A method with a false preconditionmay seem unusual (and Crepe issues a warning about

it), but it is not illegal. All it means is that this methodcannot be called in the class JobDispenser, onlyin subclasses. This is similar to the method being ab-stract. (It is not exactly the same, since a class withsuch a method need not be abstract itself. This is simi-lar to an array of length zero, which is a perfectly legalobject on which indexing may not be performed.)

It would seem that this method should also havethe precondition job != null, which is commonto both subclasses. However, Crepe removes redun-dant assertions, and since this (like any logical expres-sion) is implied by the computed precondition, false,it was removed. (More details on this generally use-ful optimization appear in Section 4.4.) A human pro-grammer may decide to keep this redundant assertionanyway, as a declaration of intent. However, Crepedoes not reason at this level.

At some later point, the programmer may de-cide to remove the preconditions on job.status()from both subclasses. At that point, the preconditionjob != null can be moved to the superclass. Thissuggests the need for a Recompute Superclass Con-tract refactoring. Such a refactoring could easily beimplemented in Crepe using existing techniques, butthis has not yet been done.

Another interesting point to notice is that the post-condition of remove (line 8 of Figure 3), while equiv-alent to the original postconditions (lines 11 and 27 ofFigure 2), has a slightly different form. This is a resultof the use of a theorem prover to simplify assertions;more details appear in Section 4.3.

Note also that top has the preconditionsize() > 0, even though only one of the sub-classes had this precondition. This is necessary,since without an explicit precondition, the methodhas an implicit precondition of true. In that case,any subclass, including FreeJobs, will not beable to add any additional precondition, since thiswould strengthen the precondition. By moving theprecondition size() > 0 to JobDispenser, theclass FreeJobs inherits it, while ReadyQueue canweaken the precondition to true.

While this is certainly the theoretically correcttreatment of the common contract, if we want to keepthe original contracts of the two subclasses, it createsa strange situation. Usually, preconditions representobligations of the client, which are benefits of the sup-plier, and postconditions represent obligations of thesupplier, which are benefits of the client. Strongerpreconditions typically go together with stronger post-conditions, since the implementer can take advan-tage of the more restricted domain. In our exam-ple, because of the precondition size() > 0 on

5

1 /** @inv size() >= 0 */2 public abstract class JobDispenser {

3 /** @pre false4 * @post size() == 1 + $prev(size())5 */6 public abstract void insert(Job job);

7 /** @pre size() > 08 * @post 1 + size() == $prev(size())9 */10 public abstract void remove();

11 /** @pre size() > 0 */12 public abstract Job top();

13 public abstract int size();14 }

Figure 3. The generated superclass (excerpt).

FreeJobs.top, the implementation could guaran-tee the postcondition of $ret != null. The newsuperclass has the stronger precondition, but cannotguarantee the strong postcondition, because subclasses(such as ReadyQueue) may weaken this precondi-tion and therefore not be able to satisfy the strongerpostcondition. A human programmer might thereforedecide to change the behavior of one of the subclassesand harmonize it with that of the other, thus movingthe stronger postcondition to the superclass, or, alter-natively, removing the precondition altogether. How-ever, such reasoning is outside the scope of the ExtractSuperclass refactoring.

Most design-by-contract tools for Java (as well asthe Eiffel language) implicitly combine the assertionsgiven in a type with those from its supertypes. Pre-conditions of a method are disjoined with those ofthe same method in supertypes; thus, it is impossi-ble to strengthen preconditions in subtypes. Simi-larly, postconditions and invariants are implicitly con-joined with the corresponding assertions from super-types, and subtypes can only strengthen them. Thismeans that when Crepe moves assertions to the newsuperclass, it can remove some assertions from thesubclasses. Figure 4 shows the modifications to theoriginal classes in our example.

The common assertions that have been moved tothe superclass have been removed from the subclasses.The insert methods in both subclasses still havethe common precondition job != null. This isdue to the fact that the precondition computed for

this method in the superclass was false. Had therebeen no contradiction in the other preconditions (onjob.status()), this precondition would have beenmoved from the subclasses to the superclass.

Because the precondition size() > 0 wasmoved from FreeJobs.top to the superclass,it is necessary to weaken this precondition inReadyQueue. This could be done by adding theprecondition true to this method, but we choose tomake the additional legal inputs explicit by using thenegation of the inherited precondition. That wouldresult in the precondition size() <= 0. Becauseof the implicit disjunction between this and the inher-ited precondition, the end result is still true. How-ever, the size cannot be negative because of the invari-ant (size() >= 0), and Crepe uses the invariant tosimplify the precondition to size() == 0.

3.2 Add Inheritance

Continuing our example, assume that the classPriorityQueue (Figure 5) is an alternative im-plementation to ReadyQueue that manages its el-ements by their priorities. We would like to makethis a subclass of ReadyQueue class. This requiresmore than just adding “extends ReadyQueue”to the class declaration, since the contract must betaken into account. First, it is necessary to verifythat preconditions in PriorityQueue are indeed nostronger than those in ReadyQueue, and invariantsand postconditions are no weaker. Once that is done,

6

1 public class ReadyQueue extends JobDispenser {2 /** @pre job != null3 * @pre job.status() == Job.READY4 * @post elements.get(0).equals(job)5 */6 public void insert(Job job) { /* ... */ }

7 public void remove() { /* ... */ }

8 /** @pre size() == 09 * @post (size() == 0) == ($ret == null)10 */11 public Job top() { /* ... */ }

12 public int size() { /* ... */ }13 }

14 public class FreeJobs extends JobDispenser {15 /** @pre job != null16 * @pre job.status() == Job.INACTIVE17 * @post top() == job18 */19 public void insert(Job job) { /* ... */ }

20 public void remove() { /* ... */ }

21 /** @post $ret != null22 * @post $ret.status() == Job.INACTIVE23 */24 public Job top() { /* ... */ }

25 public int size() { /* ... */ }26 }

Figure 4. The modified classes (excerpt).

it may be possible to remove some of the assertions inPriorityQueue. Crepe performs these activities aspart of its Add Inheritance refactoring.

In this example, the third precondition of insertin PriorityQueue (line 5) is problematic, becauseit is stronger than the preconditions in ReadyQueue.Crepe will flag this problem, as well as any invariantsand postconditions that are weakened. If the program-mer removes the offending precondition (or decidesto postpone handling the problem and instructs Crepeto go ahead with the refactoring), Crepe will also re-move the invariant from PriorityQueue, since itis subsumed by the one inherited (indirectly) fromJobDispenser.

In general, when comparing the contracts of twoclasses, Crepe takes into account the full contract,

which includes any inherited contracts from super-classes and any implemented interfaces.

4 The Crepe Recipe

This section describes the implementation ofCrepe, with emphasis on general principles. Crepeuses several types of techniques, at various levels ofsophistication. First, Crepe needs to treat assertionsas code, even though they appear inside Javadoc com-ments. The contract is composed of assertions, whichare Java expressions; any refactoring that may modifyexpressions (e.g., Rename Method) must change theseassertions as well as the code inside method bodiesand initializers. This is easily obtained by using theEclipse Java parser on the assertions extracted from the

7

1 /** @inv size() >= 0 */2 public class PriorityQueue {3 /** @pre job != null4 * @pre job.status() == Job.READY5 * @pre job.priority() < Scheduler.current_priority()6 * @post size() == $prev(size()) + 17 */8 public void insert(Job job) { /* ... */ }9 }

Figure 5. A priority queue (excerpt).

Javadoc comments. Unfortunately, this change needsto be made in each of the relevant existing refactoringsseparately.

Second, Crepe needs to verify relationships be-tween assertions; for example, to show that precon-ditions are only weakened by the subclass in the AddInheritance refactoring. This is done by using a the-orem prover; currently Crepe uses Mathematica [16]for this purpose.

The third technique is used to combine existing as-sertions to create new ones, as was done to create thecontract for the new class in the Extract Superclassrefactoring, as well as to suitably modify the contractsof the existing classes. This is discussed in more detailbelow.

Finally, it may be necessary to compute completelynew contracts from the code. For example, the ExtractMethod refactoring takes an arbitrary piece of codeand creates a new method from it. This new methodneeds a contract (pre- and postconditions), but thereis no existing contract from which to create it. We aredeveloping a system called Discern [7], which uses thepropagation of weakest preconditions and strongestpostconditions to compute contracts for arbitrary Javacode. This capability has not yet been integrated withCrepe.

4.1 Representing Assertions

Mathematica uses mathematical notation (witha somewhat non-conventional syntax) rather thanobject-oriented notation. Crepe therefore needs totranslate assertions, originally written as Java expres-sions, into Mathematica. Each Java method is trans-lated into a Mathematica function that has an addi-tional first parameter, representing the target of thecall. Crepe analyzes the inheritance hierarchy of theprogram in order to use different function names torepresent overloaded methods, but the same names foroverriden methods. Since parameter names can be

changed without affecting method signatures, Crepenormalizes all parameter names to be P1, P2, etc.

Types (especially the primitive types) are, ofcourse, crucial to the reasoning process. Crepe keepstrack of the types of variables and expressions, andadds appropriate type assertions as assumptions to theformulas it is trying to prove or simplify. Postcondi-tions can include $prev expressions (as in Figure 2);these are replaced by special constants for reasoning,taking care to use the same constant name for $prevexpressions with identical contents. This is necessaryin order for Crepe to eliminate redundant assertions(see Section 4.4).

4.2 Checking Consistency

When checking consistency between assertions, weneed to show that one set of assertions implies another.For example, the (conjunction of the) preconditions ofa given method in the superclass must imply the (con-junction of the) preconditions in the proposed subclassin order for the Add Inheritance refactoring to be ap-plicable. However, we want to have more informa-tional error messages than just “precondition strength-ened for method m”; it is much better to point to thespecific offending assertion. This is achieved by sepa-rately checking that each assertion in the preconditionof the subclass is implied by the conjunction of theassertions in the precondition of the superclass, and is-suing warnings about those that are not.

Similarly, postconditions and invariants in the su-perclass must follow from those in the subclass. Here,too, we check each assertion in the superclass sepa-rately, so that in case of violation we can point to thespecific assertions in the superclass that do not hold inthe subclass.

8

4.3 Combining Assertions

As mentioned above, preconditions in the su-perclass created by the Extract Superclass refac-toring need to be stronger than the correspondingpreconditions in the subclasses. If we want theweakest such preconditions, we can take the con-junction of the preconditions from the subclasses.For example, when creating the precondition forJobDispenser.top, we took the conjunction ofthe precondition from ReadyQueue, which was justtrue, and the one from FreeJobs, which wassize() > 0, resulting in the latter as the newprecondition. Similarly, for insert we took theconjunction of job.status() == Job.READYand job.status() == Job.INACTIVE, yield-ing the result false. The conjunction of the precon-ditions is simplified using Mathematica.

Similarly, the postconditions and invariants in thenewly-generated superclass need to be weaker than thecorresponding postconditions or invariants in the sub-classes. This time, we want the strongest assertions,which suggests that we take the disjunction of the cor-responding assertions from the subclasses. However,while this yields the most accurate assertion possible,it may not be what the programmer really intends.Sometimes, a weaker assertion is simpler. For ex-ample, suppose that the method foo affects the val-ues of the queries x() and y(). In one subclass,foo has four postconditions: x()>=0, x()<10,y()>=0, and y()<10. In the other subclass, foo hasfour different postconditions: x()>=20, x()<30,y()>=20, and y()<30.

The disjunction of both sets of postconditions is theexpression:

(x()>=0 && x()<10 &&y()>=0 && y()<10) ||

(x()>=20 && x()<30 &&y()>=20 && y()<30)

This cannot be simplified further. (You can thinkof this as describing two disjoint squares in the x–yplane.) However, the following assertions are simpler:

x()>=0x()<30y()>=0y()<30

While this is weaker than the “accurate” result (itdescribes a single square that inscribes the two squaresmentioned above), it may well be closer to the pro-grammer’s intentions. Since there is no single “cor-rect” result, we prefer to err on the side of simplicity.

The algorithm for combining postconditions andinvariants therefore works as follows. First, Crepebreaks any conjunctions in the original assertions intoseparate assertions. Thus, for example, the single post-condition x()>=0 && x()<10 would be split intothe two assertions x()>=0 and x()<10. Next, Crepeexamines each assertion in the second class and checks(using Mathematica) whether it is implied by the con-junction of all assertions in the first one. If it is, itis weaker than the disjunction of both groups of as-sertions, and is added to the result set. Then Crepeexamines each of the assertions in the first class, andchecks whether it is implied by the current result set.If so, it is added to the result. The final result thus con-tains a subset of the original assertions, and is weakerthan the disjunction of both sets (but it is not necessar-ily the strongest such set). Finally, Crepe creates theconjunction of all assertions in the result set, and sim-plifies this conjunction in order to get the final result.

4.4 Simplifying Subclass Assertions

Having chosen the assertions to be put in the newsuperclass, Crepe turns to the simplification of theoriginal assertions in the subclasses. Anything that isimplied by superclass assertions can be removed. Theimplicit precondition in a type is the disjunction of itsown precondition assertions with those inherited fromsupertypes. We would like to remove all redundan-cies, so that this disjunction is equivalent to the orig-inal set of assertions. In order for the remaining setof assertions to be minimal, we can require that theconjunction of this set with the inherited assertions beminimal, that is, false. Denoting the inherited precon-dition by I , the original precondition by O, and thedesired new precondition by N , we have the follow-ing: N ∨ I ≡ O and N ∧ I ≡ false. These twoconditions imply that N ≡ ¬I ∧ O. In this way, theprecondition of top in ReadyQueue would becomesize() <= 0. However, Crepe uses the class in-variant to simplify the resulting precondition, whichis how the final precondition became size() == 0(Figure 4, line 8).

During the implementation of this technique wediscovered that Mathematica was not able to sim-plify the expression x <= 0 under the assumptionx >= 0 to x == 0, and as a result the computationof the precondition above did not happen. In order towork around this problem, we added the assumptionto the expression to be simplified as well as kept itas an assumption. (This is correct because the invari-ants must also hold on entry to the method.) Mathe-matica is able to simplify x <= 0 && x >= 0 to

9

x == 0, and in this way we got the expected result.In general, we still need the assumption, since the in-variant may contain many assertions that are unrelatedto the precondition; keeping them as simplifying as-sumptions allows the theorem prover to eliminate theinvariants from the result.

For invariants and postconditions, Crepe uses atechnique similar to that described in Section 4.3.Crepe examines each of the assertions in the subclassand checks whether it is implied by the set of cor-responding assertions in the superclass. If so, it isdropped, since it is redundant.

5 Discussion and Future Work

We have demonstrated several techniques for refac-toring contracts together with the associated code.Some are easily applicable, while others requiretheorem-proving capability. These techniques arewidely applicable. According to Feldman’s analysis[6], of the 68 refactorings mentioned in Chapters 6–11of Fowler’s book [8], 32% do not interact with con-tracts except for syntactic matters that require treatingassertions as code. Some 13% of Fowler’s refactoringsrequire checking contracts as preconditions on theirapplicability. These can all be implemented using thetechniques already present in Crepe. In addition, 59%of the refactorings require some new contracts (thisnumber includes 4% that also need checking for appli-cability). Of Fowler’s 68 refactorings, we estimate that83% can be implemented using techniques similar tothose described in this paper, while the other 17% re-quire more sophisticated methods for computing con-tracts from arbitrary code. As mentioned above, workis in progress on developing such techniques [7].

In addition, there are refactorings such as Add In-heritance (introduced in this paper) and Pull Up Asser-tion and Pull Down Assertion [6], whose main impactis on the contract. Such refactorings would be veryuseful for any tool that refactors contracts.

We believe that widespread practical acceptance ofthe design-by-contract methodology depends on con-venient tools for manipulating contracts and for check-ing contract violations at run-time. While there areseveral tools for the latter task, Crepe is the first toolfor the former. We intend to extend Crepe to coverthe existing Eclipse refactorings, as well as add newones, focusing on those that have the most significantinteraction with the contract. We are also investigatingthe possibility of replacing Mathematica by an open-source theorem prover, so that Crepe could be madefreely available. (Any good theorem prover would beable to check the correctness of the required implica-

tions, but previous experience with a different theoremprover has shown that simplification of arbitrary ex-pressions under a set of assumptions is considerablymore difficult.)

Acknowledgment

We are grateful to Amiram Yehudai for helpfulcomments on an earlier draft of this paper.

References

[1] D. Bartetzko, C. Fischer, M. Moller, and H. Wehrheim.Jass—Java with assertions. Electronic Notes in Theo-retical Computer Science, 55(2), 2001.

[2] O. Barzilay, Y. A. Feldman, and S. Tyszberowicz.Jose: Aspects for design by contract. In preparation.

[3] K. Beck. Test Driven Development: By Example.Addison-Wesley, 2002.

[4] L. Burdy, Y. Cheon, D. Cok, M. Ernst, J. Kiniry, G. T.Leavens, K. R. M. Leino, and E. Poll. An overview ofJML tools and applications. In T. Arts and W. Fokkink,editors, Eighth Int’l Workshop on Formal Methods forIndustrial Critical Systems (FMICS ’03), pages 73–89,June 2003.

[5] The Eclipse project, 2006. http://www.eclipse.org.

[6] Y. A. Feldman. Extreme design by contract. In FourthInt’l Conf. Extreme Programming and Agile Processesin Software Engineering (XP 2003), pages 261–270,Genova, Italy, 2003.

[7] Y. A. Feldman and L. Gendler. Automatic discoveryof software contracts. In preparation.

[8] M. Fowler. Refactoring: Improving the Design of Ex-isting Code. Addison-Wesley, 2000.

[9] JetBrains. IntelliJ IDEA, 2006. http://www.jetbrains.org/idea.

[10] R. Kramer. iContract—the Java design by contracttool. In Proc. Technology of Object-Oriented Lan-guages and Systems, TOOLS-USA. IEEE Press, 1998.

[11] Man Machine Systems. Design by contracttool for Java—JMSAssert, 2002. http://www.mmsindia.com/JMSAssert.html.

[12] B. Meyer. Eiffel: The Language. Prentice Hall, 1990.[13] B. Meyer. Object-Oriented Software Construction.

Prentice Hall, 2nd edition, 1997.[14] NetBeans, 2006. http://www.netbeans.org.[15] Parasoft Corp. Jcontract home page, 2002–

2004. http://www.parasoft.com/jsp/products/home.jsp?product=Jcontract.

[16] S. Wolfram. The Mathematica Book. Wolfram Media,Inc., 5th edition, 2003.

10