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