Software Engineering Design by Contract

Click here to load reader

download Software Engineering  Design by Contract

of 96

description

Software Engineering Design by Contract. Software Engineering 2012 Department of Computer Science Ben-Gurion university. Based on slides of: Mira Balaban Department of Computer Science Ben-Gurion university R. Mitchell and J. McKim : Design by Contract by Example. - PowerPoint PPT Presentation

Transcript of Software Engineering Design by Contract

About the GOF Book

Software Engineering Design by ContractSoftware Engineering 2012Department of Computer Science Ben-Gurion universityBased on slides of: Mira Balaban Department of Computer Science Ben-Gurion university R. Mitchell and J. McKim: Design by Contract by ExampleDesign By ContractThe term Design by Contract was coined by Bertrand Meyer while designing the Eiffel programming language within Eiffel Software company.Eiffel implements the Design by Contract principles.Bertrand Meyer won the 2006 ACM Software System Award for Eiffel

Software Engineering, 2012Design by Contract2 The Eiffel Tower, built in 1887 for the 1889 World Fair, was completed on time and within budget, as will software projects written in Eiffel.A contractThere are two parties A Client - requests a serviceA Supplier - supplies the service

A Contract is the agreement between the client and the supplier Two major characteristics of a contractEach party expects some benefits from the contract and is prepared to incur some obligations to obtain themThese benefits and obligations are documented in a contract document

Benefit of the client is the obligation of the supplier, and vice versa.

Software Engineering, 2012Design by Contract3DbC The idea and metaphorMotivation: Organize communication between software elementsBy organizing mutual obligations and benefits Do it by a metaphor of clients request services from suppliers suppliers supply services

ClientSupplierObligationSatisfy supplier requirementGuarantee serviceBenefitGet serviceImpose requirementsSoftware Engineering, 2012Design by Contract4DbC The metaphor realization Obligations and benefits are specified using contracts Write contracts for classes and methods Methods:PreconditionsPost-conditionsClasses: InvariantsClientSupplierObligationPreconditionPost-conditionBenefitPost-conditionPreconditionSoftware Engineering, 2012Design by Contract5What happens when a Contract Breaks?If everyone does their job, there is no problemIf the precondition is not satisfied the Customer is wrong! (The client has a bug).If the precondition is satisfied, but the postcondition is notthe Service is wrong (The service has a bug).

From the Clients perspective, true is the best precondition. In general, weaker preconditions are better.From the Servers perspective, false is the best precondition. In general, stronger preconditions mean an easier job with the implementation.

Software Engineering, 2012Design by Contract6In Business, the Customer is thought to be always right This is a good model for software as well:Tradeoffs should generally be made in favor of the client. That isminimize preconditionsprovide usefully strong postconditionsBut there are limits

6DbC Nature Dbc promotes software specification together with or prior to code writing Writing contracts needs some principles and guidelines The DbC principles tell us how to organize a class features (attributes, methods) The contract of a class is its interface Software Engineering, 2012Design by Contract7Design by Contract, by Example

Software Engineering, 2012Design by Contract8QUERIESCOMMANDSNotationsfeaturesattributesroutinesprocedurescreationotherfunctionsSoftware Engineering, 2012Design by Contract9Six Principles the SIMPLE_STACK example Software Engineering, 2012Design by Contract10SIMPLE_STACK example initial try SIMPLE_STACK is a generic class, with type parameter G: Simple_stack of G. Features:Queries functions; No side effect:count(): Integeris_empty(): BooleanInitialization: initialize()Commands:push(g:G)no return value.pop(): out parameter g:G; no return value.SIMPLE_STACK

count()is_empty()initialize()push(g:G)pop()GSoftware Engineering, 2012Design by Contract11Separate commands from queries (1) Writing a contract for push: Takes a parameter g.Places g on the top of the stack.

but pop does not return a value. Just removes. Redesign pop:

New push contract: push(g:G)Purpose: Push g onto the top of the stack. ensure:g = pop ???pop(): G purpose: Remove top item and return it.push(g:G)Purpose: Push g onto the top of the stack. ensure: g = pop Software Engineering, 2012Design by Contract12Separate commands from queries (2) Serious problem: Evaluation of the post-condition changes the stack!Solution: Split pop into two operations:Query:

Command:

push contract:

top(): Gpurpose: return the item at the top of the stack.delete()purpose: deletes the item at the top of the stack.push(g:G)purpose: Push g onto the top of the stack.ensure:top = gSoftware Engineering, 2012Design by Contract13Separate commands from queries (3)Standardize names: Class: SIMPLE_STACKQueries:count(): Integer purpose: No of items on the stack.item(): G purpose: The top itemis_empty(): Booleanpurpose: Is the stack empty?Creation commands: initialize()purpose: Initialize a stack (new or old) to be empty.Operations (other commands):put(g:G)purpose: Push g on top of the stack.remove()purpose: Removes the top item of the stack.A standard name to add /delete item from any container classBoolean queries have names that invite a yes/no questionSoftware Engineering, 2012Design by Contract14Separate commands from queries (4) Principle 1: Separate commands from queries.

Queries: Return a result. No side effects. Pure functions.Commands: Might have side effects. No return value.

Some operations are a mixture:pop() removes the top item and returns itSeparate into two pore primitive command and query of which it is mixedSoftware Engineering, 2012Design by Contract15Separate basic queries from derived queries (1) Post-condition of is_empty:

Result is a contract built-in variable that holds the result that a function returns to its caller. The effect of is_empty() is defined in terms of the count query. is_empty() is a derived query: It can be replaced by the test: count() = 0. Contracts of other features can be defined in terms of basic queries alone. No need to state the status of derived queries no need to state in the post-condition of put() that is_empty() is false.state: count is increased, infer : is_empty=false from the contract of is_emptyis_empty(): Booleanpurpose: Is the stack empty?ensure: consistent_with_count:Result = (count()=0) Software Engineering, 2012Design by Contract16Separate basic queries from derived queries (2) Principle 2: Separate basic queries from derived queries.Derived queries can be specified in terms of basic queries.

Principle 3: For each derived query, write a post-condition that defines the query in terms of basic queries. Software Engineering, 2012Design by Contract17Specify how commands affect basic queries (1)Queries provide the interface of an object:all information about an object is obtained by querying it.Derived queries are defined in terms of basic queries (principle3). The effect of a command on an object should be specified in terms of basic queries. For Simple_stack, define the effects ofput()initialize()remove()in terms of the basic queriescount()item()Software Engineering, 2012Design by Contract18Specify how commands affect basic queries (2)The put command:put() increases count by 1.put() affects the top item.

@pre is borrowed from OCL (Object Constraint Language)count()@pre refers to the value of the query count() when put() is called. put(g:G)Purpose: Push g onto the top of the stack.ensure: count_increased:count() = count()@pre + 1 g_on_top:item() = gSoftware Engineering, 2012Design by Contract19Specify how commands affect basic queries (3)The initialize() command:Turns count() to 0: post-condition count() = 0.

Following initialization the stack includes no items. Therefore, no top item The query item() cannot be applied.Implies a pre-condition for the query item:

Together, the 2 contracts, guarantee that applying item after initialize is illegal! initialize() purpose: Turns a stack (new or old) to be empty.

ensure: stack_is_empty:count() = 0 item() : G purpose: The top item on the stack. require: stack_is_not_empty:count() > 0Software Engineering, 2012Design by Contract20Specify how commands affect basic queries (4)The remove() command:Two effects:Reduces the number of items by one.Removes the top item, and uncovers the item pushed before the top one.Pre-condition: Stack is not empty.

Problem: How to express the 2nd post-condition? The only queries are count and item. No way to refer to previous items. remove() purpose: The top item on the stack. require: stack_not_empty: count() > 0 ensure: count_decreased: count() = count()@pre - 1Software Engineering, 2012Design by Contract21Specify how commands affect basic queries (5)Rethink the basic queries.Needed: A basic query that enables querying any item on the stack.New basic query:

The new basic queries are:count()item_at() The contracts of all other queries and commands need to be redefined! item_at(i : Integer) : G purpose: The i-th item on the stack. item_at(1) is the oldest; item_at(count) is the youngest, and the stack top. require: i_large_enough: i > 0 i_small_enough: i 0 ensure: consistent_with_item_at: Result = item_at(count)Software Engineering, 2012Design by Contract23Specify how commands affect basic queries (7)The put command revisited:

put(g : G) purpose: Push g on top of the stack. ensure: count_increased: count() = count()@pre + 1 g_on_top: item_at(count() ) = gSoftware Engineering, 2012Design by Contract24Specify how commands affect basic queries (8)The initialize creation command revisited:

The precondition of item_at(i) together with count()=0 implies the second post-condition this is summarized as a comment initialize() purpose: Initialize the stack to be empty. ensure: empty_stack: count() = 0 item_at is undefined: --For item_at(i), i must be in the interval [1,count], which is empty, since count = 0 -- there are no values of i for which item_at(i)_ is definedSoftware Engineering, 2012Design by Contract25Specify how commands affect basic queries (9)The remove command revisited:

No need for another post-condition about the new top: Once count is decreased, the new top of the stack item() - is item_at(count() ) remove() purpose: Remove the top item from the stack. The new top is the one, put before the last one. require:stack_not_empty: count() > 0 ensure: count_decreased: count() = count()@pre - 1Software Engineering, 2012Design by Contract26Specify how commands affect basic queries (10) Principle 4: For each command, specify its effect on basic queries.Implies its effect on derived queries.Usually: Avoid specifying queries that do not change.

Principle 5: For each query and command, determine a pre-condition.Constrains clients. Software Engineering, 2012Design by Contract27summaryEvery command specifies its effect on every basic querySometimes the specification is direct an explicit assertion in the post conditionSometimes the specification is indirect Initialize specifies that count =0. The precondition of item_at() implies that there are no valid values of i for which item_at can be calledIndirectly initialize specifies the effect on item_at: it makes it invalid to call item_at()All the derived queries have post conditions that specify their results in terms of the basic queriesSoftware Engineering, 2012Design by Contract28Class invariants and class correctnessA class invariant is an assertion that holds for all instances (objects) of the classA class invariant must be satisfied after creation of every instance of the classThe invariant must be preserved by every method of the class, i.e., if we assume that the invariant holds at the method entry it should hold at the method exitWe can think of the class invariant as conjunction added to the precondition and post-condition of each method in the class

Software Engineering, 2012Design by Contract29Class invariants Capture unchanging properties of a class objects by invariants For the SIMPLE_STACK class, the non-negative value of count is an invariant:

Argument (proof):For an initialized object: count() = 0 Count() is decreased by remove, but it has the precondition: count() > 0

Principle 6: Write invariants to define unchanging properties of objects.Provide a proof for each invariant.A good collection of class invariants might involve all method contracts (in their proofs) (if the invariant can be inferred from the contracts of the features it is redundant.Include it ? invariant:count_is_never_negative: count() >= 0Software Engineering, 2012Design by Contract30Yes- an invariant that summarizes a logical argument concerning properties of several features can help the reader to make sure he understand right (or realize he is wrong)30The SIMPLE_STACK class interface (1)Class SIMPLE_STACK(G)1. Basic queries:

2. Derived queries: count(): Integer purpose: The number of items on the stack

item_at(i : Integer) : G purpose: The i-th item on the stack. item_at(1) is the oldest; item_at(count) is the youngest, and the stack top. require: i_large_enough: i > 0 i_small_enough: i 0 ensure: consistent_with_item_at: Result = item_at( count () )

is_empty: Booleanpurpose: Is the stack empty from items?Software Engineering, 2012Design by Contract31The SIMPLE_STACK class interface (2)Class SIMPLE_STACK(G)3. Creation commands:

initialize() purpose: Initialize the stack to be empty. ensure: empty_stack: count() = 0 item_at is undefined: For item_at(i), i must be in the interval [1,count], which is empty, since count = 0Software Engineering, 2012Design by Contract32The SIMPLE_STACK class interface (3)4. Other commands:

5. Invariant:put(g : G) purpose: Push g on top of the stack.ensure: count_increased: count() = count()@pre + 1 g_on_top: item_at(count() ) = g

remove purpose: Remove the top item from the stack. The new top is the one, put before the last one. require: stack_not_empty: count() > 0ensure: count_decreased: count() = count()@pre 1count_is-never_negative: count() >= 0Software Engineering, 2012Design by Contract33The basic queries form a conceptual modelThe two basic queries count and item_at give us a model of a stack objectUsing this model we can say all there is to say about stacks:What the stuck looks like when it is just been initialized:Count = 0 and there are no items since there is no i for which items_at(i) is valid.What the effect of put(g) is : count is increased a g is item_at(count)What the effect of remove is: count has decreasedWhat the result of is_empty is: The same as count=0What the result of item is: the same as item_at(count)

Software Engineering, 2012Design by Contract34The basic queries form a conceptual modelWe have devised a conceptual model of stacks.Stacks have an ordered set of items (item_at(1), item_at(2),item_at(3), and so on)We know how many items there areCount

The class designer devises the model and uses it as the basis of the contracts that specify the features of the class.The programmer of the class can see the model and devise a suitable implementation model to represent it.302010

Count=3item_at(3)=30item_at(2)=20item_at(1)=10Software Engineering, 2012Design by Contract35The 6 principlesSeparate commands from queries.Separate basic queries from derived queries.For each derived query, write a post-condition that defines the query in terms of basic queries. For each command, specify its effect on basic queries.For each query and command, determine a pre-condition.Write invariants to define unchanging properties of objects.

Software Engineering, 2012Design by Contract36Building Support for Contracts -Immutable (Value) ListsSoftware Engineering, 2012Design by Contract37Why do we need support?We cannot inspect the items37Contracts for Immutable (Value) ListsContracts are written in expression languages, without side effects (functional languages). (Why? -- recall the Simple_stack class)

Contracts for clients of collection classes need to inspect the members of their collections. Such contracts need side-effect protected operations on collections.A conventional approach: Contracts that handle collection objects create them as value (immutable objects).A client can hold a regular collection object, like a hash table, but create an immutable copy for the purpose of contract evaluation.

We start by defining the code for Immutable listSoftware Engineering, 2012Design by Contract38Expression language The examples here are written with OCLOCL is a pure expression language. Therefore, an OCL expression is guaranteed to be without side effect. It cannot change anything in the model. This means that the state of the system will never change because of an OCL expression, even though an OCL expression can be used to specify such a state change (e.g., in a post-condition). All values for all objects, including all links, will not change. Whenever an OCL expression is evaluated, it simply delivers a value

38The IMMUTABLE_LIST class interface (1) IMMUTABLE_LIST is a generic class, with type parameter G: IMMUTABLE_LIST of G. Features:Basic Queries:

Derived queries: Head (): G Purpose: The first item on the listTail (): IMMUTABLE_LIST(G) Purpose: A new list, formed from the current list (termed self, minus the headis_empty (): BooleanPurpose: Does the list contain no items?count (): INTEGER Purpose: The number of items in the listSoftware Engineering, 2012Design by Contract39The IMMUTABLE_LIST class interface (2)Features:derived Queries:

Creation commend: cons(g:G): IMMUTABLE_LIST(G) Purpose: A new list, formed from g as a head and the self list as a tail

is_equal( other: IMMUTABLE_LIST(G) ) : BOOLEAN Purpose: Compare all ordered elements in self and in other

item( i:INTEGER ): GPurpose: The i-th item in the list (starting from 1)

sublist( from_position:INTEGER, to_position:INTEGER ) : IMMUTABLE_LIST(G) Purpose: A new list, formed from the self items at from_position to to_position initialize () Purpose: Initialize a list to be emptySoftware Engineering, 2012Design by Contract40consecutive40Contracts of the basic queriesBasic Queries:

No post conditions: regular for basic querieshead (): G Purpose: The first item on the list require: not_empty: not is_empty()

tail (): IMMUTABLE_LIST(G) Purpose: A new list, formed from the self list, minus the head require: not_empty: not is_empty()

is_empty: BooleanPurpose: Does the list contain no items?Software Engineering, 2012Design by Contract41Contract of the creation commandThe creation command initialize empties a list (either new or old).It takes no arguments no preconditionFollowing its application:The list should be empty.head() and tail() should not be applicableis_empty() should be true

Arguments for the post conditions on head() and tail():Their pre-condition require: not is_empty(), which is false following initialize()Creation command:initialize () Purpose: Initialize a list to be empty ensure:empty: is_empty()Software Engineering, 2012Design by Contract42Contracts of the derived queries: countThe post-condition of count():INTEGERIf the list is empty, count() is 0.If the list is not empty, count() is the count() on the tail of the list + 1. Derived query:count ():INTEGER Purpose: The number of items in the list ensure: count_zero_for_an_empty_list: is_empty() implies (Result = 0) count_for_a_non_empty_list: not is_empty() implies (Result = tail().count() + 1)Evaluated recursively Software Engineering, 2012Design by Contract43a implies b -- evaluate a; if a is false the result is true -- if a is true, the result is the value of b 43Contracts of the derived queries: consThe post-condition of cons(g:G ):IMMUTABLE_LIST):The new list has, g as its head.The new list has self as its tail.The new list is not empty Derived query:cons ( g:G ):IMMUTABLE_LIST) Purpose: A new list, formed from self and g, as its head ensure: not_empty: not Result.is_empty() head_is_g: Result.head() = g tail_is_self: Result.tail ().is_equal(self)Software Engineering, 2012Design by Contract44Contracts of the derived queries: itemThe pre-condition of item(i:INTEGER):G is that i is in the range [1..count()]The post-condition of item(i:INTEGER):G:The 1st item is the head For i>=1, the i-th item is the (i-1)-th item of the tail Derived query:

The if operator evaluates its else component only if its predicate evaluates to false (or else eiffel)item ( i:INTEGER ):G Purpose: The i-th item on the list require: i_large_enough: i >= 1 i_small_enough: i =1, the i-th item is the (i-1)-th item of the tail Derived query:

The if operator evaluates its else component only if its predicate evaluates to false (or else eiffel)item ( i:INTEGER ):G Purpose: The i-th item on the list require: i_large_enough: i >= 1 i_small_enough: i 0 ensure:empty: count() = 0 capacity_set: capacity() = a_capacitySoftware Engineering, 2012Design by Contract58Contract for the head queryThe head():G - query returns the head item

Pre-condition: Queue is not empty

Post-condition: Returns the head item. This is expressed in terms of the head of the items() list. Therefore, head() is a derived query.

Derived query:head():G Purpose: The head item require: not_empty: count () > 0 // count>0 ensure: consistent_with_items: Result = items().head()Post condition is expensivePrecondition is cheapSoftware Engineering, 2012Design by Contract59Contract for the put commandThe put( g:G ) command adds g to the tail of the queue

Pre-condition: Queue is not full

Post-condition: the number of queue items increases by 1, the added item is at position count()

Command:put( g:G ) Purpose: Add g to the tail require: not_full: count () < capacity() ensure: number_of_items_increases: count() = count()@pre + 1 g_at_tail: items.item( count() ) = gSoftware Engineering, 2012Design by Contract60More derived queriesExplicit queries for emptiness and being full can be added:Derived queries:is_empty() Purpose: Is the queue empty? ensure: consistent_with_items.count: Result = (items.count() = 0 )

is_full() Purpose: Is the queue full? ensure: consistent_with_items.count: Result = (items.count() = capacity() )Software Engineering, 2012Design by Contract61The final class QUEUE interface Basic queries:capacity():INTEGERitems() : IMMUTABLE_LIST

Derived queries:head (): Gcount(): INTEGERis_empty(): BOOLEANis_full(): BOOLEAN

Creation command: initialize ( a_capacity:INTEGER )

Other commands: put ( g:G )remove ()Software Engineering, 2012Design by Contract62Design by Contract and InheritanceSoftware Engineering, 2012Design by Contract63Design by Contract and inheritanceSubclasses inherit features of their super-classesSubclasses also inherit the contracts of their inherited featuresSubclasses can redefine features of their super-classes.Subclasses can redefine the contracts of their inherited features, BUT: They must respect the inherited contractsClass COURIER Features:Mixed Command:deliver( p: Package, t: Time, d: Destination ) : Time Purpose: Deliver package p accepted at time t to destination d. result is the delivery time. require: package_small_enough: p.weight() < 5 ensure: fast_delivery: Result < t + 3 Software Engineering, 2012Design by Contract64Redefining a pre-condition (1)Consider a subclass of COURIER that redefines the pre-condition on the deliver feature:class SPECIAL_COURIER extends COURIER redefine deliverand a COURIER client holding an object of SPECIAL_COURIERIf the new pre-condition is:feature

then the client will have no problem:A client of COURIER knows the pre-condition of COURIER on deliver: package_small_enough: p.weight() < 5Therefore, if it respects the COURIER pre-condition, its concrete SPECIAL_COURIER object will perform the delivery. deliver( p: Package, t: Time, d: Destination ) : Time require: package_small_enough: p.weight() < 8Software Engineering, 2012Design by Contract65Redefining a pre-condition (2)If the new pre-condition is:feature

a COURIER client that actually holds a SPECIAL_COURIER object might have a problem:A client of COURIER knows the pre-condition of COURIER on deliver: package_small_enough: p.weight() < 5But respecting the COURIER pre-condition might still invalidate the pre-condition of the concrete SPECIAL_COURIER object on the deliver, and the delivery would be rejected.Conclusion: A subclass can only weaken a pre-condition: Pre-condition of super implies pre-condition of sub-class deliver( p: Package, t: Time, d: Destination ) : Time require: package_small_enough: p.weight() < 3Software Engineering, 2012Design by Contract66Redefining a post-condition (1)If the new post-condition is:feature

then the client will have no problem:A client of COURIER knows the post-condition of COURIER on deliver: fast_delivery: Result < t + 3Therefore, its concrete SPECIAL_COURIER object satisfies its expected benefit. deliver( p: Package, t: Time, d: Destination ) : Time ensure: fast_delivery: Result < t + 2Software Engineering, 2012Design by Contract67Redefining a post-condition (2)If the new post-condition is:feature

then a COURIER client that actually holds a SPECIAL_COURIER object might have a problem:A client of COURIER knows the post-condition of COURIER on deliver: fast_delivery: Result < t + 3But its concrete SPECIAL_COURIER object might not satisfy its expected benefit from the delivery service.

Conclusion: A subclass can only strengthen a post-condition: Post-condition of sub-class implies post-condition of super-class deliver( p: Package, t: Time, d: Destination ) : Time ensure: fast_delivery: Result < t + 5Software Engineering, 2012Design by Contract68Redefining a contract pre-conditionRedefined pre-conditions are combined with their super-class assertions.A redefined pre-condition is or-ed with its super pre-condition:

which indeed is within the obligation of the delivery service of the sub-classwhile

which might not be within the obligation of the delivery service of the sub-class!DbC languages do not enable strengthening a pre-condition package_small_enough: p.weight() < 5 or package_small_enough: p.weight() < 8package_small_enough: p.weight() < 8reduces to package_small_enough: p.weight() < 5 or package_small_enough: p.weight() < 3package_small_enough: p.weight() < 5reduces to Software Engineering, 2012Design by Contract69Redefining a contract post-conditionRedefined post-conditions are combined with their super-class assertions.A redefined post-condition is and-ed with its super post-condition:

which indeed, is provided by the delivery service of the sub-class while

which might not be provided by the delivery service of the sub-class!

DbC languages do not enable weakening a post-condition fast_delivery: Result < t + 3 and fast_delivery: Result < t + 2 fast_delivery: Result < t + 2reduces to fast_delivery: Result < t + 3 and fast_delivery: Result < t + 5 fast_delivery: Result < t + 3reduces to Software Engineering, 2012Design by Contract70Redefining a contractRedefined assertions are marked explicitly:class SPECIAL_COURIER extends COURIER redefine deliverfeature deliver( p: Package, t: Time, d: Destination ) : Time Purpose: Deliver package p accepted at time t to destination d. result is the delivery time. require else: package_small_enough: p.weight() < 8 ensure then: fast_delivery: Result < t + 2Require of super class or else require thisensure of super class and then ensure thisSoftware Engineering, 2012Design by Contract71Invariants and inheritanceInvariants of a super-class are respected by its sub-classesObjects of a sub-class must satisfy the inherited invariantsclass COURIERinvariant insurance > 1,000,000

class SPECIAL_COURIER extends COURIERinvariantGood: insurance > 2,000,000 Bad: insurance > 800,000 Invariants of super-classes are anded with the invariants of the sub-classesA sub-class can only strengthen an invariantSoftware Engineering, 2012Design by Contract72Guarded post-conditions in super-classesIf post-conditions in a super-class are guarded (conditioned) by some pre-conditions, then sub-classes can relax (weaken) post-conditions

class Cfeature put( g: G ) Purpose: Add grequire g_not_in_aggregate: not has( g )ensure g_added: has( g ) number_of_items_increases: count() = count()@pre + 1 class RELAXED_C extends Cfeature put( g: G ) Purpose: Add g; if g exists, do nothingrequire else g_in_aggregate: has( g )ensure then g_added: has( g ) number_of_items_increases: count() = count()@pre PROBLEMSoftware Engineering, 2012Design by Contract73Guarded post-conditions in super-classesA version with guarded post-conditions:

Now the relaxed subclass can be properly defined: class C put( g: G ) require g_not_in_aggregate: not has( g )ensure g_added: ( ( not has(g) )@pre ) implies has( g ) number_of_items_increases: ( ( not has(g) )@pre ) implies count() = count()@pre + 1 class RELAXED_C extends C put( g: G ) require else g_in_aggregate: has( g )ensure then g_added: has( g ) number_of_items_unchanged: ( has(g)@pre ) implies count() = count()@pre OKSoftware Engineering, 2012Design by Contract74a implies b -- evaluate a; if a is false the result is true -- if a is true, the result is the value of b 74Guidelines To support redefinition of features, guard each postcondition clause with its corresponding precondition. This allows unexpected redefinitions by those developing subclasses.Software Engineering, 2012Design by Contract75Frame RulesSoftware Engineering, 2012Design by Contract76Change specifications and FrameRulesFrame rules specify what does not change.Added to post conditionsPost conditions include2 kinds of assertions:Change specifications assert that certain thing changesFrame rules assert that certain thing remain unchangedput( g:G ) Purpose: Add g to the tail require: not_full: count () < capacity() ensure: number_of_items_increases: count() = count()@pre + 1 g_at_tail: items( count() ) = g capacity _unchanged:capacity() = capacity()@preSoftware Engineering, 2012Design by Contract77Frame Rules for put Using Immutable ListsWe can add a second frame rule concerning the items in the queue by adding a 4th assertion to the postcondition on put.

We assert that sublist containing the first 10 items (i.e., [email protected]) of 11 items must equal the list containing all the items that were there before the call (i.e., items@pre).The frame rule makes use of the is_equal query on lists.Two lists are equal if they hold the same items.What if two lists hold references to the same objects but these objects are in different states?original_items_unchanged: items.sublist(1,[email protected]).is_equal(items@pre)Software Engineering, 2012Design by Contract78Frame Rules for put Using Immutable ListsFrame rules constrain objects not to change between the time just before call to a feature and the time just after that call.There are 2 properties of a list that can change independently:One object in the list might be replaced by anotherThe contents of an object might change.

The problem is not solved by changing the meaning of is_equal in class IMMUTABLE_LIST to check that elements are equal both by = and by is_equal. head = other.headandhead.is_equal(other.head)andtail.is_equal(other.tail)Software Engineering, 2012Design by Contract79Frame Rules for put Using Immutable ListsThe item query returns a list of addresses of the objects that are the items in the queue.If the body of put contains a bug and changes the contents of an item already in the queue the list of addresses will not have changed and the test that the head of the list after the call to put is equal to the head before the call succeeds.

100120023003abc100120023003zbc500400equalSoftware Engineering, 2012Design by Contract80Frame Rules for put Using Immutable ListsAt the time of the test, both head and other.head are pointing to the same object.We need 2 separate checks:Does queue after put contains the same objects as before put?Do these objects have the same content as before put?At present, we can only perform the first check. To be able to perform the second check we need to keep a copy of the objects content in the queue before the put, and to compare it with the content of the object after put.Use Eiffels library feature deep_clone and deep_equal.The post condition:

Original_items_unchange: (deep_equal(item.sublist(1, items.count-1), old deep_clone (items)))Software Engineering, 2012Design by Contract81Deep/shallow copy/clone/equal81Frame Rules for put Using ForallIn the postcondition of put, make sure that the items that were in the queue are still there and in their logical positions:Forall i in the range 1 to count,The item at position i is what was at position i before the callA preprocessor can perform the following tasks to transform forall into Eiffel automatically:Create a collection object of the right size and typeOn entry to the put feature, store values of item(i) in this collectionIn the postcondition of the put feature, call a Boolean-valued function to compare the stored values of item(i) with values calculated now.Software Engineering, 2012Design by Contract82Kinds of Frame RulesRegular commands - change the state of the object A frame rule for put may assert that the items already in a queue are unchanged by putting a new item at the tail. Creation commands before these command the object has no particular stateCannot assert that the state remain unchangedBasic queries return a result but not change the stateAssert that calling the query (capacity) does not change the object state ( increment the capacity, change the queues values)Parameters usually, a routine that is passed a parameter is not supposed the modify the parameterAssert a postcondition to express this effect.Software Engineering, 2012Design by Contract83Frame Rules a practical viewpointIs it realistic to develop frame rules to cover all these issues?

Suggested approach:Add frame rules occasionally, particularly where there is evidence that client programmers misunderstand what a feature does and does not do.Adopt a convention that all classes come with an implicit frame rule, which states that, unless a postcondition asserts that some property changes, it does not change.Unexpected changes then indicates either poor documentation or bugsIn design reviews check the code against both explicit and implicit frame rules Software Engineering, 2012Design by Contract84Design by Contract pros and consSoftware Engineering, 2012Design by Contract85Benefits of Design by ContractBetter designs More systematic designsModularityNo need to read whole class filesRead contractsImplementations may changeContract guarantees certain relevant behaviourHelps ensure inheritance is properly handled Improve reliabilityBetter understood and hence more reliable codeBetter tested and hence more reliable codeAssertions are checked at runtime thereby testing that routines fulfill their stated contracts.

Software Engineering, 2012Design by Contract86Benefits of Design by ContractBetter documentationContracts form part of the public or client view of a classAssertions are checked at runtime thereby testing that the stated contracts are consistent with the actual routinesHelp debuggingWhen an assertion fails its location is precisely known.When assertions are switched on in production, customer may provide the support developer with information regarding a failure.Support reuseGood documentation for library usersAvoid defensive programming

Software Engineering, 2012Design by Contract87Meyers Perspective on Defensive ProgrammingDefensive programming:leads to duplicate checks on preconditions and therefore code bloat.leads to implementers checking preconditions when they have no idea what to do if the precondition is false.leads to confusion over responsibility for ensuring certain constraints.Meyers advice is, Dont do it!Think about this in the context of preconditions and exception handling.Software Engineering, 2012Design by Contract88Rescue and Retry in eiffel read_next_character (f: FILE) is -- Make next character available in last_character ; -- if impossible, set failed to True. require readable: file . readable local impossible: BOOLEAN do if impossible then failed := True else last_character := low_level_read_function ( f ) end rescue impossible := True retry end

88Efficiency and Defensive programmingAvoid inefficient defensive checks. For example, the sqrt method assumes that its argument is non-negative. When this method is called from trusted code ( e.g. , their input is trusted)this trust is validated by checking the preconditions during debugging, but these checks can be turned off for production use of the program.Defensive checks are sometimes not possible to execute efficiently. For example, a binary search method requires that its array argument be sorted. Checking that an array is sorted requires time linear in the length of the array, but the binary search routine is supposed to execute a logarithmic time. Therefore the defensive checks would slow down the method unacceptably. contracts, are easier to automatically remove when the program goes into production, much more efficient.Software Engineering, 2012Design by Contract89Design by Contract ConsCost of writing contractsNew language Takes practice - writing good contracts is a skill.False sense of security contract improve programs they dont make them perfect.Quality not always the primary goal (e.g, early release)Not all specifications can be described with existing facilities of DbC. Example: DbC doesnt support specifications that define performance issues such as execution time and required resources performance contracts.

Checking contract violation may be more time consuming than the method execution. Example: Class that works on Hamiltonian cycle graphs. Its preconditions need to solve NP-Complete problem.

Software Engineering, 2012Design by Contract90Runtime checkingIn a programming environment that understands contracts, we would be told something like the following (well assume the CUSTOMER_MANAGER component has been implemented by a class of the same name):Stopped in object [0xE96978]Class: CUSTOMER_MANAGERFeature: addProblem: Precondition violatedTag: id_not_already_activeArguments:a_customer: BASIC_CUSTOMER_DETAILS [0xE9697C]Call stack: CUSTOMER_MANAGER addwas called by CUSTOMER_MANAGER_UIF change_customerSoftware Engineering, 2012Design by Contract91Runtime checkingWorking through this wealth of debugging information line by line, we can tell1. That the application has stopped in some object (we could open the objectwith an object browser and examine its attributes).2. That this object is of the class CUSTOMER_MANAGER.3. That a problem arose when that classs add feature was called.4. That the problem was that some part of the precondition on add was violated.5. That if a precondition is violated, it means some client called the add featurewhen it was not legal to do so. Specifically, it was the part of the preconditionwith the id_not_already_active tag that was violated.6. Which BASIC_CUSTOMER_DETAILS object was passed as an argument tothe call.7. The sequence of calls that led up to the problem: A change_customer featurein a CUSTOMER_MANAGER_UIF class (the user interface to the customermanager application) called the add feature in the CUSTOMER_MANAGERclass.Software Engineering, 2012Design by Contract92JML Java Modeling Languagehttp://www.cs.iastate.edu/~leavens/JML/An implementation of DBC for JavaOne of manycombines the design by contract approach of Eiffel JMLEclipse is a JML front-end implemented as an Eclipse plugin. Open source

Software Engineering, 2012Design by Contract93JASS Java with ASSertionsPre-compiler tool written in Java.Translates annotated contracts into dynamic checking code.Violation of specification is indicated by Java exception.Free of charge.Website: http://csd.informatik.uni-oldenburg.de/~jass/

Software Engineering, 2012Design by Contract94jContractor implementation of Design By Contract Contracts in jContractor are written as Java methods that follow a simple naming convention. All contracts are written in standard Javano need to learn a special contract specification language. Assertions are written as Java methods that return a boolean valuejContractor provides runtime contract checking by instrumenting the bytecode of classes that define contracts.jContractor can either add contract checking code to class files to be executed later, or it can instrument classes at runtime as they are loaded.Contracts can be written in the class that they apply to, or in a separate contract class.

Software Engineering, 2012Design by Contract9595

Software Engineering, 2012Design by Contract96