Click here to load reader

Software Engineering Design by Contract Software Engineering 2011 Department of Computer Science Ben-Gurion university Based on slides of: Mira Balaban

  • View
    217

  • Download
    0

Embed Size (px)

Citation preview

  • Slide 1
  • Software Engineering Design by Contract Software Engineering 2011 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
  • Slide 2
  • Design By Contract The 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, 2011 2 Design by Contract
  • Slide 3
  • A contract There are two parties A Client - requests a service A Supplier - supplies the service A Contract is the agreement between the client and the supplier Two major characteristics of a contract Each party expects some benefits from the contract and is prepared to incur some obligations to obtain them These benefits and obligations are documented in a contract document Benefit of the client is the obligation of the supplier, and vice versa. Software Engineering, 2011 3 Design by Contract
  • Slide 4
  • DbC The idea and metaphor Motivation: Organize communication between software elements By organizing mutual obligations and benefits Do it by a metaphor of clients request services from suppliers suppliers supply services ClientSupplier ObligationSatisfy supplier requirement Guarantee service BenefitGet serviceImpose requirements Software Engineering, 2011 4 Design by Contract
  • Slide 5
  • DbC The metaphor realization Obligations and benefits are specified using contracts Write contracts for classes and methods Methods: Preconditions Post-conditions Classes: Invariants ClientSupplier ObligationPreconditionPost-condition BenefitPost-conditionPrecondition Software Engineering, 2011 5 Design by Contract
  • Slide 6
  • What happens when a Contract Breaks? Software Engineering, 2011 6 If everyone does their job, there is no problem If the precondition is not satisfied the Customer is wrong! (The client has a bug). If the precondition is satisfied, but the postcondition is not the 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. Design by Contract
  • Slide 7
  • DbC 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, 2011 7 Design by Contract
  • Slide 8
  • Design by Contract, by Example Software Engineering, 2011 8 Design by Contract
  • Slide 9
  • QUERIES COMMANDS Notations features attributes routines procedures creation other functions Software Engineering, 2011 9 Design by Contract
  • Slide 10
  • Six Principles the SIMPLE_STACK example Software Engineering, 2011 10 Design by Contract
  • Slide 11
  • SIMPLE_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(): Integer is_empty(): Boolean Initialization: initialize() Commands: push(g:G)no return value. pop(): out parameter g:G; no return value. Software Engineering, 2011 11 Design by Contract SIMPLE_STACK count() is_empty() initialize() push(g:G) pop() G
  • Slide 12
  • Separate 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, 2011 12 Design by Contract
  • Slide 13
  • Separate commands from queries (2) Serious problem: Evaluation of the post-condition changes the stack! Solution: Split pop into two operations: 1. Query: 2. Command: push contract: top(): G purpose: 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 = g Software Engineering, 2011 13 Design by Contract
  • Slide 14
  • Separate commands from queries (3) Standardize names: Class: SIMPLE_STACK Queries: count(): Integer purpose: No of items on the stack. item(): G purpose: The top item is_empty(): Boolean purpose: 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 class Boolean queries have names that invite a yes/no question Software Engineering, 2011 14 Design by Contract
  • Slide 15
  • Separate 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 it Separate into two pore primitive command and query of which it is mixed Software Engineering, 2011 15 Design by Contract
  • Slide 16
  • Separate 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_empty is_empty(): Boolean purpose: Is the stack empty? ensure: consistent_with_count: Result = (count()=0) Software Engineering, 2011 16 Design by Contract
  • Slide 17
  • Separate 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, 2011 17 Design by Contract
  • Slide 18
  • Specify 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 of put() initialize() remove() in terms of the basic queries count() item() Software Engineering, 2011 18 Design by Contract
  • Slide 19
  • Specify 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() = g Software Engineering, 2011 19 Design by Contract
  • Slide 20
  • Specify 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() > 0 Software Engineering, 2011 20 Design by Contract
  • Slide 21
  • Specify 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 - 1 Software Engineering, 2011 21 Design by Contract
  • Slide 22
  • Specify 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
  • Specify how commands affect basic queries (6) The item query is no longer a basic query It turns into a derived query: item = item_at(count) item() : G purpose: The top of the stack. require: stack_not_empty: count() > 0 ensure: consistent_with_item_at: Result = item_at(count) Software Engineering, 2011 23 Design by Contract
  • Slide 24
  • Specify 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() ) = g Software Engineering, 2011 24 Design by Contract
  • Slide 25
  • Specify 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 defined Software Engineering, 2011 25 Design by Contract
  • Slide 26
  • Specify 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 - 1 Software Engineering, 2011 26 Design by Contract
  • Slide 27
  • Specify 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, 2011 27 Design by Contract
  • Slide 28
  • summary Every command specifies its effect on every basic query Sometimes the specification is direct an explicit assertion in the post condition Sometimes 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 called Indirectly 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 queries Software Engineering, 2011 28 Design by Contract
  • Slide 29
  • Class invariants and class correctness Software Engineering, 2011 Design by Contract 29 A class invariant is an assertion that holds for all instances (objects) of the class A class invariant must be satisfied after creation of every instance of the class The 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 exit We can think of the class invariant as conjunction added to the precondition and post-condition of each method in the class
  • Slide 30
  • Class 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() >= 0 Software Engineering, 2011 30 Design by Contract
  • Slide 31
  • The 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: Boolean purpose: Is the stack empty from items? Software Engineering, 2011 31 Design by Contract
  • Slide 32
  • The 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 = 0 Software Engineering, 2011 32 Design by Contract
  • Slide 33
  • The 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() > 0 ensure: count_decreased: count() = count()@pre 1 count_is-never_negative: count() >= 0 Software Engineering, 2011 33 Design by Contract
  • Slide 34
  • The basic queries form a conceptual model The two basic queries count and item_at give us a model of a stack object Using 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 decreased What the result of is_empty is: The same as count=0 What the result of item is: the same as item_at(count) Software Engineering, 2011 34 Design by Contract
  • Slide 35
  • The basic queries form a conceptual model We 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 are Count 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. 30 20 10 Count=3 item_at(3)=30 item_at(2)=20 item_at(1)=10 Software Engineering, 2011 35 Design by Contract
  • Slide 36
  • The 6 principles 1. Separate commands from queries. 2. Separate basic queries from derived queries. 3. For each derived query, write a post-condition that defines the query in terms of basic queries. 4. For each command, specify its effect on basic queries. 5. For each query and command, determine a pre-condition. 6. Write invariants to define unchanging properties of objects. Software Engineering, 2011 36 Design by Contract
  • Slide 37
  • Building Support for Contracts - Immutable (Value) Lists Software Engineering, 2011 37 Design by Contract
  • Slide 38
  • Contracts for Immutable (Value) Lists Contracts 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 list Software Engineering, 2011 38 Design by Contract
  • Slide 39
  • IMMUTABLE_LIST The 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 list Tail (): IMMUTABLE_LIST(G) Purpose: A new list, formed from the current list (termed self, minus the head is_empty (): Boolean Purpose: Does the list contain no items? count (): INTEGER Purpose: The number of items in the list Software Engineering, 2011 39 Design by Contract
  • Slide 40
  • IMMUTABLE_LIST The 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 ): G Purpose: 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 empty Software Engineering, 2011 40 Design by Contract
  • Slide 41
  • Contracts of the basic queries Basic Queries: No post conditions: regular for basic queries head (): 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: Boolean Purpose: Does the list contain no items? Software Engineering, 2011 41 Design by Contract
  • Slide 42
  • Contract of the creation command The creation command initialize empties a list (either new or old). It takes no arguments no precondition Following its application: 1. The list should be empty. 2. head() and tail() should not be applicable 3. is_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 Purpose: Initialize a list to be empty ensure: ensure: empty: is_empty() Software Engineering, 2011 42 Design by Contract
  • Slide 43
  • Contracts of the derived queries: count The post-condition of count():INTEGER 1. If the list is empty, count() is 0. 2. 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 Purpose: The number of items in the list ensure: ensure: count_zero_for_an_empty_list: count_zero_for_an_empty_list: is_empty() implies (Result = 0) is_empty() implies (Result = 0) count_for_a_non_empty_list: count_for_a_non_empty_list: not is_empty() implies (Result = tail().count() + 1) not is_empty() implies (Result = tail().count() + 1) Evaluated recursively Software Engineering, 2011 43 Design by Contract
  • Slide 44
  • Contracts of the derived queries: cons The post-condition of cons(g:G ):IMMUTABLE_LIST): 1. The new list has, g as its head. 2. The new list has self as its tail. 3. 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 Purpose: A new list, formed from self and g, as its head ensure: ensure: not_empty: not Result.is_empty() not_empty: not Result.is_empty() head_is_g: Result.head() = g head_is_g: Result.head() = g tail_is_self: Result.tail ().is_equal(self) tail_is_self: Result.tail ().is_equal(self) Software Engineering, 2011 44 Design by Contract
  • Slide 45
  • Contracts of the derived queries: item 1. The pre-condition of item(i:INTEGER):G is that i is in the range [1..count()] 2. The post-condition of item(i:INTEGER):G: 1. The 1 st item is the head 2. 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 Purpose: The i-th item on the list require: require: i_large_enough: i >= 1 i_large_enough: i >= 1 i_small_enough: i = 1 from_position_small_enough: from_position
  • Contract for the remove command (2) Command: remove () Purpose: remove the item at the head require: not_empty: count () > 0 ensure: count_decreased: count() = count()@pre -1 items_shifted: items().is_equal( items().tail()) @pre ) Items: [a,b,c].tail : [b,c] a new list of the right size (2) Our queue maybe implemented as an array (circular structure) 1 2 3 a b c 1 2 3 b c Software Engineering, 2011 55 Design by Contract
  • Slide 56
  • The status of count (1) 1. count() can be expressed in terms of the count feature of the items() list: count() = items().count() Therefore, it can be made a derived query: 2.But count() is used in the pre-condition of remove(). Therefore: count() Purpose: The number of items in the queue ensure: consistent_with_items: count() = items().count() require: not empty: items().count () > 0 require: not empty: count () > 0 Software Engineering, 2011 56 Design by Contract
  • Slide 57
  • The status of count (2) 3. The evaluation of the new not_empty pre-condition implies evaluation of items().count(), i.e., creation of a new items list and counting its length. 4. Recall: Pre-conditions are evaluated also in working mode: Guideline: Use cheap-to-evaluate queries in pre-conditions. 3. Conclusion: for performance reasons, count should be kept. 4. If count() is implemented as an attribute, it can be constrained by an invariant: invariant: count = items().count() Software Engineering, 2011 57 Design by Contract
  • Slide 58
  • Contract of the creation command The QUEUE creation initialize( a_capacity:INTEGER ) command empties a queue (either new or old), with a capacity given by a_capacity (for simplicity the capacity of a queue cannot be changed). 1. Pre-condition: Positive capacity. 2. Post-condition: 1. The queue should be empty. 2. The queue capacity is the a_capacity. This post-condition requires a new query about the capacity of a queue: Creation command: capacity() Purpose: The number of items that self can hold initialize ( a_capacity:INTEGER ) Purpose: Initialize a queue to be empty, with capacity a_capacity require: positive_a_capacity: a_capacity > 0 ensure: empty: count() = 0 capacity_set: capacity() = a_capacity Software Engineering, 2011 58 Design by Contract
  • Slide 59
  • Contract for the head query The head():G - query returns the head item 1. Pre-condition: Queue is not empty 2. 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 expensive Precondition is cheap Software Engineering, 2011 59 Design by Contract
  • Slide 60
  • Contract for the put command The put( g:G ) command adds g to the tail of the queue 1. Pre-condition: Queue is not full 2. 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( count() ) = g Software Engineering, 2011 60 Design by Contract
  • Slide 61
  • More derived queries Explicit 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, 2011 61 Design by Contract
  • Slide 62
  • The final class QUEUE interface Basic queries: capacity():INTEGER items() : IMMUTABLE_LIST Derived queries: head (): G count(): INTEGER is_empty(): BOOLEAN is_full(): BOOLEAN Creation command: initialize ( a_capacity:INTEGER ) Other commands: put ( g:G ) remove () Software Engineering, 2011 62 Design by Contract
  • Slide 63
  • Design by Contract and Inheritance Software Engineering, 2011 63 Design by Contract
  • Slide 64
  • Design by Contract and inheritance Subclasses inherit features of their super-classes Subclasses also inherit the contracts of their inherited features Subclasses can redefine features of their super-classes. Subclasses can redefine the contracts of their inherited features, BUT: They must respect the inherited contracts Class 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, 2011 64 Design by Contract
  • Slide 65
  • Redefining a pre-condition (1) Consider a subclass of COURIER that redefines the pre-condition on the deliver feature: class SPECIAL_COURIER extends COURIER redefine deliver and a COURIER client holding an object of SPECIAL_COURIER If 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() < 5 Therefore, 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() < 8 Software Engineering, 2011 65 Design by Contract
  • Slide 66
  • Redefining 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() < 5 But 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() < 3 Software Engineering, 2011 66 Design by Contract
  • Slide 67
  • Redefining 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 + 3 Therefore, its concrete SPECIAL_COURIER object satisfies its expected benefit. deliver( p: Package, t: Time, d: Destination ) : Time ensure: fast_delivery: Result < t + 2 Software Engineering, 2011 67 Design by Contract
  • Slide 68
  • Redefining 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 + 3 But 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 + 5 Software Engineering, 2011 68 Design by Contract
  • Slide 69
  • Redefining a contract pre-condition Redefined 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-class while which might 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() < 8 package_small_enough: p.weight() < 8 reduces to package_small_enough: p.weight() < 5 or package_small_enough: p.weight() < 3 package_small_enough: p.weight() < 5 reduces to Software Engineering, 2011 69 Design by Contract
  • Slide 70
  • Redefining a contract post-condition Redefined 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 + 2 reduces to fast_delivery: Result < t + 3 and fast_delivery: Result < t + 5 fast_delivery: Result < t + 3 reduces to Software Engineering, 2011 70 Design by Contract
  • Slide 71
  • Redefining a contract Redefined assertions are marked explicitly: class SPECIAL_COURIER extends COURIER redefine deliver feature 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 + 2 Require of super class or else require this ensure of super class and then ensure this Software Engineering, 2011 71 Design by Contract
  • Slide 72
  • Invariants and inheritance Invariants of a super-class are respected by its sub-classes Objects of a sub-class must satisfy the inherited invariants class COURIER invariant insurance > 1,000,000 class SPECIAL_COURIER extends COURIER invariant Good: insurance > 2,000,000 Bad: insurance > 800,000 Invariants of super-classes are anded with the invariants of the sub-classes A sub-class can only strengthen an invariant Software Engineering, 2011 72 Design by Contract
  • Slide 73
  • Guarded post-conditions in super-classes If post-conditions in a super-class are guarded (conditioned) by some pre-conditions, then sub-classes can relax (weaken) post-conditions class C feature put( g: G ) Purpose: Add g require g_not_in_aggregate: not has( g ) ensure g_added: has( g ) number_of_items_increases: count() = count()@pre + 1 class RELAXED_C extends C feature put( g: G ) Purpose: Add g; if g exists, do nothing require else g_in_aggregate: has( g ) ensure then g_added: has( g ) number_of_items_increases: count() = count()@pre PROBLEM Software Engineering, 2011 73 Design by Contract
  • Slide 74
  • Guarded post-conditions in super-classes A 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 OK Software Engineering, 2011 74 Design by Contract
  • Slide 75
  • Guidelines To support redefinition of features, guard each postcondition clause with its corresponding precondition. This allows unexpected redefinitions by those developing subclasses. Software Engineering, 2011 75 Design by Contract
  • Slide 76
  • Frame Rules Software Engineering, 2011 76 Design by Contract
  • Slide 77
  • Change specifications and FrameRules Frame rules specify what does not change. Added to post conditions Post conditions include2 kinds of assertions: Change specifications assert that certain thing changes Frame rules assert that certain thing remain unchanged 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( count() ) = g capacity _unchanged: capacity() = capacity()@pre Software Engineering, 2011 77 Design by Contract
  • Slide 78
  • Frame Rules for put Using Immutable Lists We can add a second frame rule concerning the items in the queue by adding a 4 th assertion to the postcondition on put. We assert that sublist containing the first 10 (i.e., [email protected]) of 11 items must equal the list contating 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: item.sublist(1,[email protected]).is_equall(items@pre) Software Engineering, 2011 78 Design by Contract
  • Slide 79
  • Frame Rules for put Using Immutable Lists Frame 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 another The contents of an object might change. The problem is not solved by changing the meaning of is_equall in class IMMUTABLE_LIST to check that elements are equall both by = and by is_equal. head = other.head and head.is_equal(other.head) and tail.is_equal(other.tail) Software Engineering, 2011 79 Design by Contract
  • Slide 80
  • Frame Rules for put Using Immutable Lists The 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. 1001 2002 3003 a b c 1001 2002 3003 z b c 500400 equal Software Engineering, 2011 80 Design by Contract
  • Slide 81
  • Frame Rules for put Using Immutable Lists At the time of the test, both head and other.head are pointing to the same object. We need 2 separate checks: 1. Does queue after put contains the same objects as before put? 2. 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_equall(item.sublist(1, items.count-1), old deep_clone (items))) Software Engineering, 2011 81 Design by Contract
  • Slide 82
  • Frame Rules for put Using Forall In 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 call A preprocessor can perform the following tasks to transform forall into Eiffel automatically: Create a collection object of the right size and type On entry to the put feature, store values of item(i) in this collection In 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, 2011 82 Design by Contract
  • Slide 83
  • Kinds of Frame Rules Regular 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 state Cannot assert that the state remain unchanged Basic queries return a result but not change the state Assert 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 parameter Assert a postcondition to express this effect. Software Engineering, 2011 83 Design by Contract
  • Slide 84
  • Frame Rules a practical viewpoint Is 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 bugs In design reviews check the code against both explicit and implicit frame rules Software Engineering, 2011 84 Design by Contract
  • Slide 85
  • Design by Contract pros and cons Software Engineering, 2011 85 Design by Contract
  • Slide 86
  • Benefits of Design by Contract Better designs More systematic designs Modularity No need to read whole class files Read contracts Implementations may change Contract guarantees certain relevant behaviour Helps ensure inheritance is properly handled Improve reliability Better understood and hence more reliable code Better tested and hence more reliable code Assertions are checked at runtime thereby testing that routines fulfill their stated contracts. Software Engineering, 2011 86 Design by Contract
  • Slide 87
  • Benefits of Design by Contract Better documentation Contracts form part of the public or client view of a class Assertions are checked at runtime thereby testing that the stated contracts are consistent with the actual routines Help debugging When 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 reuse Good documentation for library users Avoid defensive programming Software Engineering, 2011 87 Design by Contract
  • Slide 88
  • Meyers Perspective on Defensive Programming Software Engineering, 2011 88 Defensive 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. Design by Contract
  • Slide 89
  • Efficiency and Defensive programming Avoid inefficient defensive checks. For example, the sqrt method assumes that its argument is non-negative. 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, 2011 89 Design by Contract
  • Slide 90
  • Design by Contract Cons Cost of writing contracts New 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 doesn t 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, 2011 90 Design by Contract
  • Slide 91
  • Runtime checking In 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_MANAGER Feature: add Problem: Precondition violated Tag: id_not_already_active Arguments: a_customer: BASIC_CUSTOMER_DETAILS [0xE9697C] Call stack: CUSTOMER_MANAGER add was called by CUSTOMER_MANAGER_UIF change_customer This is the level of detail provided by the Eiffel Other environments provide similar detail (including other Eiffel environments and environments that add design by contract facilities to other programming languages, such as Java and C++). Software Engineering, 2011 91 Design by Contract
  • Slide 92
  • Runtime checking Working through this wealth of debugging information line by line, we can tell 1. That the application has stopped in some object (we could open the object with 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 feature when it was not legal to do so. Specifically, it was the part of the precondition with the id_not_already_active tag that was violated. 6. Which BASIC_CUSTOMER_DETAILS object was passed as an argument to the call. 7. The sequence of calls that led up to the problem: A change_customer feature in a CUSTOMER_MANAGER_UIF class (the user interface to the customer manager application) called the add feature in the CUSTOMER_MANAGER class. Software Engineering, 2011 92 Design by Contract
  • Slide 93
  • JML Java Modeling Language http://www.cs.iastate.edu/~leavens/JML/ An implementation of DBC for Java One of many combines the design by contract approach of Eiffel JMLEclipse is a JML front-end implemented as an Eclipse plugin. Open source Software Engineering, 2011 93 Design by Contract
  • Slide 94
  • JASS Java with ASSertions Pre-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, 2011 94 Design by Contract
  • Slide 95
  • jContractor 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 Java no need to learn a special contract specification language. Assertions are written as Java methods that return a boolean value jContractor 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, 2011 95 Design by Contract
  • Slide 96
  • Software Engineering, 2011 Design by Contract 96