Foundations of Software Engineering Design by Contract

Similar documents
Software Engineering Design by Contract

UC Santa Barbara. CS189A - Capstone. Christopher Kruegel Department of Computer Science UC Santa Barbara

Assertions. Assertions - Example

Design by Contract in Eiffel

References: internet notes; Bertrand Meyer, Object-Oriented Software Construction; 10/14/2004 1

Object Oriented Program Correctness with OOSimL

MSO Lecture Design by Contract"

a correct statement? You need to know what the statement is supposed to do.

Adding Contracts to C#

Readability [Skrien 4.0] Programs must be written for people to read, and only incidentally for machines to execute.

Why Design by Contract! CS 619 Introduction to OO Design and Development. Design by Contract. Fall 2012

Test-Driven Development (TDD)

Lecture 1 Contracts : Principles of Imperative Computation (Fall 2018) Frank Pfenning

Lecture Notes on Contracts

Lecture 1 Contracts. 1 A Mysterious Program : Principles of Imperative Computation (Spring 2018) Frank Pfenning

Contracts. Dr. C. Constantinides. June 5, Department of Computer Science and Software Engineering Concordia University Montreal, Canada 1/71

Motivation State Machines

CS 161 Computer Security

Method Description for Semla A Software Design Method with a Focus on Semantics

Assertions & Design-by-Contract using JML Erik Poll University of Nijmegen

Assertions, pre/postconditions

Repetition Through Recursion

Comparing procedure specifications

Software Development. Modular Design and Algorithm Analysis

Designing Robust Classes

Lecture 10 Notes Linked Lists

CS 370 The Pseudocode Programming Process D R. M I C H A E L J. R E A L E F A L L

Lecture 10 Notes Linked Lists

Control flow in Eiffel

AXIOMS OF AN IMPERATIVE LANGUAGE PARTIAL CORRECTNESS WEAK AND STRONG CONDITIONS. THE AXIOM FOR nop

Softwaretechnik. Lecture 08: Testing and Debugging Overview. Peter Thiemann SS University of Freiburg, Germany

Assertions and Exceptions Lecture 11 Fall 2005

CSE 331 Software Design & Implementation

Softwaretechnik. Lecture 08: Testing and Debugging Overview. Peter Thiemann SS University of Freiburg, Germany

Design by Contract: An Overview

17. Assertions. Outline. Built-in tests. Built-in tests 3/29/11. Jelle Slowack, Bart Smets, Glenn Van Loon, Tom Verheyen

Hoare logic. A proof system for separation logic. Introduction. Separation logic

17. Assertions. Jelle Slowack, Bart Smets, Glenn Van Loon, Tom Verheyen

Agenda. More on the Unified Modeling Language. UML diagram types. Packages

12/30/2013 S. NALINI,AP/CSE

1B1b Inheritance. Inheritance. Agenda. Subclass and Superclass. Superclass. Generalisation & Specialisation. Shapes and Squares. 1B1b Lecture Slides

Lecture 12: Abstraction Functions

Violations of the contract are exceptions, and are usually handled by special language constructs. Design by contract

JML. Outline. Métodos Formais em Engenharia de Software. MI, Braga these slides were prepared by adopting/adapting teaching material

6.170 Lecture 6 Procedure specifications MIT EECS

CS558 Programming Languages

Reasoning about programs

n Specifying what each method does q Specify it in a comment before method's header n Precondition q Caller obligation n Postcondition

Program Design with Abstract Data Types

Last time. Reasoning about programs. Coming up. Project Final Presentations. This Thursday, Nov 30: 4 th in-class exercise

EXAMINATIONS 2009 END-OF-YEAR. COMP 202 / SWEN 202 Formal Methods of Computer Science / Formal Foundations of Software Engineering

Thread Safety. Review. Today o Confinement o Threadsafe datatypes Required reading. Concurrency Wrapper Collections

1. Quality issues (5 points)

Specification and Verification of Garbage Collector by Java Modeling Language

The Contract Pattern. Design by contract

CSE 331 Final Exam 3/16/15 Sample Solution

PROCESS DEVELOPMENT METHODOLOGY The development process of an API fits the most fundamental iterative code development

Type Hierarchy. Comp-303 : Programming Techniques Lecture 9. Alexandre Denault Computer Science McGill University Winter 2004

JAVA BASICS II. Example: FIFO

Abstraction and Specification

Index. Index. More information. block statements 66 y 107 Boolean 107 break 55, 68 built-in types 107

15-122: Principles of Imperative Computation, Fall 2015

Eiffel: Analysis, Design and Programming. ETH Zurich, September-December Exception handling

Racket Style Guide Fall 2017

The Java Modeling Language (Part 2)

Integrating verification in programming languages

Object-Oriented Design

Programming Paradigms for Concurrency Lecture 3 Concurrent Objects

3. Design by Contract

CPSC 221: Algorithms and Data Structures Lecture #1: Stacks and Queues

Object-Oriented Design

Lecture 13 Hash Dictionaries

Programming By Contract: Designing for Correctness

Verification and Validation. Verification and validation

CPSC 221: Algorithms and Data Structures ADTs, Stacks, and Queues

Specifications. CSE 331 Spring 2010

Reasoning About Imperative Programs. COS 441 Slides 10

Software Engineering Testing and Debugging Testing

Advanced JML Erik Poll Radboud University Nijmegen

JML Class Specifications The Java Modeling Language (Part 2) A Java Class

CS 315 Software Design Homework 3 Preconditions, Postconditions, Invariants Due: Sept. 29, 11:30 PM

Software Architecture 4. July 2005

Type Checking and Type Equality

CSC Advanced Object Oriented Programming, Spring Specification

Chapter 1: Programming Principles

CS 3 Introduction to Software Engineering. 3: Exceptions

CSE331 Winter 2014, Midterm Examination February 12, 2014

The architecture of Eiffel software 3.1 OVERVIEW classes clusters systems

OO Design Principles

Java: advanced object-oriented features

A Third Look At Java. Chapter Seventeen Modern Programming Languages, 2nd ed. 1

Chapter 1: Principles of Programming and Software Engineering

CSE331 Autumn 2011 Midterm Examination October 28, 2011

Top Down Design vs. Modularization

Fortgeschrittene objektorientierte Programmierung (Advanced Object-Oriented Programming)

6.001 Notes: Section 8.1

OOP Design by Contract. Carsten Schuermann Kasper Østerbye IT University Copenhagen

Lecture 10 Linked Lists

What This Course Is About Design-by-Contract (DbC)

Lecture Notes on Memory Layout

Transcription:

Foundations of Software Engineering Fall 2017 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: by Example

Design By Contract The term was coined by Bertrand Meyer while designing the Eiffel programming language within Eiffel Software company. Eiffel implements the principles. Bertrand Meyer won the 2006 ACM Software System Award for Eiffel 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. 2 Foundations of Software Engineering, Fall 2016

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. 3 Foundations of Software Engineering, Fall 2016

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 Obligation Client Satisfy supplier requirement Supplier Guarantee service Benefit Get service Impose requirements 4 Foundations of Software Engineering, Fall 2016

DbC The metaphor realization Obligations and benefits are specified using contracts Write contracts for classes and methods Methods: Preconditions Post-conditions Classes: Invariants Client Supplier Obligation Precondition Post-condition Benefit Post-condition Precondition 5 Foundations of Software Engineering, Fall 2016

What happens when a Contract Breaks? 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 Client s perspective, true is the best precondition. In general, weaker preconditions are better. From the Server s perspective, false is the best precondition. In general, stronger preconditions mean an easier job with the implementation. 6 Foundations of Software Engineering, Fall 2016

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 7 Foundations of Software Engineering, Fall 2016

, by Example 8 Foundations of Software Engineering, Fall 2016

Notations features attributes routines functions procedures creation COMMANDS other 9 Foundations of Software Engineering, Fall 2016

Six Principles the SIMPLE_STACK example 10 Foundations of Software Engineering, Fall 2016

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. SIMPLE_STACK count() is_empty() initialize() push(g:g) pop() G 11 Foundations of Software Engineering, Fall 2016

Example (1) Writing a contract for push: Takes a parameter g. Places g on the top of the stack. push(g:g) Purpose: Push g onto the top of the stack. ensure: g = pop??? 12 Foundations of Software Engineering, Fall 2016

Example (2) 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) pop(): G Purpose: Push g onto the top of the stack. ensure: g = pop??? purpose: Remove top item and return it. push(g:g) Purpose: Push g onto the top of the stack. ensure: g = pop 13 Foundations of Software Engineering, Fall 2016

A. Separate commands from queries (1) 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 14 Foundations of Software Engineering, Fall 2016

A. Separate commands from queries (2) 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. Boolean queries have names that invite a yes/no question A standard name to add /delete item from any container class 15 Foundations of Software Engineering, Fall 2016

A. Separate commands from queries 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 16 Foundations of Software Engineering, Fall 2016

B. Separate basic queries from derived queries (1) Post-condition of is_empty: is_empty(): Boolean purpose: Is the stack empty? ensure: consistent_with_count: Result = (count()=0) 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 17 Foundations of Software Engineering, Fall 2016

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. 18 Foundations of Software Engineering, Fall 2016

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. 19 For Simple_stack, define the effects of put() initialize() remove() in terms of the basic queries count() item() Foundations of Software Engineering, Fall 2016

Specify how commands affect basic queries (2) The put command: put() increases count by 1. put() affects the top item. put(g:g) Purpose: Push g onto the top of the stack. ensure: count_increased: count() = count()@pre + 1 g_on_top: item() = g @pre is borrowed from OCL (Object Constraint Language) count()@pre refers to the value of the query count() when put() is called. 20 Foundations of Software Engineering, Fall 2016

Specify how commands affect basic queries (3) The initialize() command: Turns count() to 0: post-condition count() = 0. initialize() purpose: Turns a stack (new or old) to be empty. ensure: stack_is_empty: 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: item() : G count() = 0 purpose: The top item on the stack. require: stack_is_not_empty: count() > 0 Together, the 2 contracts, guarantee that applying item after initialize is illegal! 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. remove() purpose: The top item on the stack. require: stack_not_empty: count() > 0 ensure: count_decreased: count() = count()@pre - 1 Problem: How to express the 2nd post-condition? The only queries are count and item. No way to refer to previous items. 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: 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 <= count The new basic queries are: count() item_at() The contracts of all other queries and commands need to be redefined! 23

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()) 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() item_at(count() = g ) = g 25

Specify how commands affect basic queries (8) The initialize creation command revisited: 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 The precondition of item_at(i) together with count()=0 implies the second post-condition this is summarized as a comment 26

Specify how commands affect basic queries (9) The remove command revisited: 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 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() ) 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:. Constrains clients. For each query and command, determine a pre-condition 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 29

Class invariants and class correctness 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 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: invariant: count_is_never_negative: Argument (proof): count() >= 0 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? 31

The SIMPLE_STACK class interface (1) Class SIMPLE_STACK(G) 1. Basic queries: count(): Integer purpose: The number of items on the stack 32 2. Derived queries: 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 <= count() item() : G purpose: The top of the stack. require: stack_not_empty: count() > 0 ensure: consistent_with_item_at: Result = item_at( count () ) is_empty: Boolean purpose: Is the stack empty from items?

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 33

The SIMPLE_STACK class interface (3) 4. Other commands: 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 5. Invariant: count_is-never_negative: count() >= 0 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) 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 30 20 10 Count=3 item_at(3)=30 item_at(2)=20 item_at(1)=10 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. 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. 37

and Inheritance 38 Software Engineering, 2012

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 39

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 deliver( p: Package, t: Time, d: Destination ) : Time require: package_small_enough: p.weight() < 8 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. 40

Redefining a pre-condition (2) If the new pre-condition is: feature deliver( p: Package, t: Time, d: Destination ) : Time require: package_small_enough: p.weight() < 3 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 41

Redefining a post-condition (1) If the new post-condition is: feature deliver( p: Package, t: Time, d: Destination ) : Time ensure: fast_delivery: Result < t + 2 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. 42

Redefining a post-condition (2) If the new post-condition is: feature deliver( p: Package, t:time, d: Destination ) : Time ensure: fast_delivery: Result < t + 5 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 superclass 43

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: package_small_enough: p.weight() < 5 or package_small_enough: p.weight() < 8 reduces to package_small_enough: p.weight() < 8 which indeed is within the obligation of the delivery service of the subclass while package_small_enough: p.weight() < 5 or package_small_enough: p.weight() < 3 reduces to package_small_enough: p.weight() < 5 44 which might not be within the obligation of the delivery service of the sub-class! DbC languages do not enable strengthening a pre-condition

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: and fast_delivery: Result < t + 3 fast_delivery: Result < t + 2 reduces to fast_delivery: Result < t + 2 which indeed, is provided by the delivery service of the sub-class while and fast_delivery: Result < t + 3 fast_delivery: Result < t + 5 reduces to fast_delivery: Result < t + 3 which might not be provided by the delivery service of the sub-class! DbC languages do not enable weakening a post-condition 45

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 46

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 47

Guarded post-conditions in super-classes If post-conditions in a super-class are guarded (conditioned) by some preconditions, 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 48 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

Guarded post-conditions in super-classes A version with guarded post-conditions: 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 Now the relaxed subclass can be properly defined: 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 49

Guidelines To support redefinition of features, guard each postcondition clause with its corresponding precondition. This allows unexpected redefinitions by those developing subclasses. 50

Building Support for Contracts - Immutable (Value) Lists לא נכלל בחומר 51 Software Engineering, 2012

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 52

The IMMUTABLE_LIST class interface (1) IMMUTABLE_LIST is a generic class, with type parameter G: IMMUTABLE_LIST of G. Features: Basic 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? Derived queries: count (): INTEGER Purpose: The number of items in the list 53

The IMMUTABLE_LIST class interface (2) Features: derived Queries: 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 Creation commend: initialize () Purpose: Initialize a list to be empty 54

Contracts of the basic queries 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? No post conditions: regular for basic queries 55

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. 2. 3. The list should be empty. head() and tail() should not be applicable is_empty() should be true Creation command: initialize () Purpose: Initialize a list to be empty ensure: empty: is_empty() Arguments for the post conditions on head() and tail(): Their pre-condition require: not is_empty(), which is false following initialize() 56

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 Evaluated recursively 57 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)

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 ensure: not_empty: not Result.is_empty() head_is_g: Result.head() = g tail_is_self: Result.tail ().is_equal(self) 58

Contracts of the derived queries: item The pre-condition of item(i:integer):g is that i is in the range [1..count()] The post-condition of item(i:integer):g: 1. 2. 1. 2. The 1st item is the head For i>=1, the i-th item is the (i-1)-th item of the tail Derived query: item ( i:integer ):G Purpose: The i-th item on the list require: i_large_enough: i >= 1 i_small_enough: i <= count() ensure: correct_item: if i=1 Evaluated then Result = head() recursively else Result = tail().item(i-1) The if operator evaluates its else component only if its predicate evaluates to false (or else eiffel) 59

Contracts of the derived queries: item The pre-condition of item(i:integer):g is that i is in the range Evaluation of postcondition for the list [5,4,7,2] with i=3 [1..count()] 2.[5,4,7,2].item(3)= The post-condition of item(i:integer):g: 1. [4,7,2].item(2)= --i.e.isitem(i-1) of tail 1. The 1st item the head [7,2].item(1)= --again of tail 2. For i>=1, the i-thitem(i-1) item is the (i-1)-th item of the tail 7 --this time i=1 so the result is the head of the list Derived query: item ( i:integer ):G Purpose: The i-th item on the list require: i_large_enough: i >= 1 i_small_enough: i <= count() ensure: correct_item: if i=1 Evaluated then Result = head() recursively else Result = tail().item(i-1) The if operator evaluates its else component only if its predicate evaluates to false 60

Contracts of the derived queries: is_equal The pre-condition of is_equal(other:immutable_list):boolean is that the argument list exists (e.g., is not a null pointer). The post-condition of is_equal(other:immutable_list):boolean : 1. 2. Both lists might be empty Both lists are not empty, and their heads and tails are equal. Derived query: is_equal(other:immutable_list):boolean Purpose: Are the 2 lists, self and other equal in elements and order? require: other_exists: other /= null ensure: same_content: Result = ( is_empty() and other.is_empty() ) or_else ( ( (not is_empty()) and (not other.is_empty()) ) and then ( head() = other.head() Evaluated recursively and tail().is_equal( other.tail() ) ) ) or_else and and_then are Eiffel s operators for optimized evaluation of or and and 1. 2. 61

Contracts of the derived queries: sublist sublist( from_position:integer, to_position:integer ):IMMUTABLE_LIST: 1. The pre-condition requires that the positions are in the [1..count()] range, and are consistent: from_position is <= to_position+1 (to enable extracting an empty sublist). 2. The post-condition: 1. If to_position is smaller than from_position the extracted sublist is empty 2. Otherwise, the extracted list consists of the list elements at positions from_position to to_position A new list is constructed The contract: 62

Contracts of the derived queries: sublist sublist( from_position:integer, to_position:integer ):IMMUTABLE_LIST Purpose: A new list, formed from the items at positions from_position to to_position require: from_position_large_enough: from_position >= 1 from_position_small_enough: from_position <= to_position + 1 to_position_small_enough: to_position <= count() ensure: is_empty_is _consistent_with_from_and_to_position: Result.is_empty() = ( from_position > to_position ) result_head _is_at _from_position_in_current: (from_position <= to_position) implies (Result.head() = item( from_position ) ) result_tail _is_correct_sublist_within_current: (from_position <= to_position) implies To allow zero sized sublist [] (Result.tail().is_equal( sublist( from_position+1, to_position ) ) ) 63

Assertion checking modes In full assertion checking mode, pre-conditions, post-conditions and invariants are always checked. For expensive contracts like those of the IMMUTABLE_LIST class, full contract evaluation is a performance penalty. Therefore, there are different modes of contract evaluation. Testing mode: Full evaluation Working mode: Only pre-condition evaluation Argument: Once the class is believed to be bug-free, the post-conditions and invariants are believed to hold as stated. checking pre-conditions is needed to protect against irresponsible clients 64

Contracts for the class QUEUE 65 Software Engineering, 2012

The Queue class Queue is a generic class, with type parameter G: QUEUE of G. The key features of a SIMPLE_QUEUE class: SIMPLE_QUEUE Initialize(capacity:INTEGER) Put(g:G) Head:G remove G 66

Contracts for the class QUEUE The contracts for QUEUE are based on a query that returns an immutable list of the elements in the queue, in the same order. Initial class interface: Features: Query: Creation command: Other commands: head (): G Purpose: The item at the head of the queue initialize ( a_capacity:integer ) Purpose: Initialize a queue to be empty and to have the given capacity (no changing capacities) put ( g:g ) Purpose: Add g to the tail of the queue remove () Purpose: Remove the item from the head of the queue 67

Contract for the remove command(1) The pre-condition of remove() is that the queue is not empty: Requires a new (basic) query for checking emptiness. Options: count() or is_empty(). We pick count() useful for the post-condition (..The decision is really a matter of class design rather than contracts) The post-condition of remove(): 1. 2. 1. 2. The number of queue items decreases by 1 -is it enough? The i-th element is the (i + 1) -th element in the original queue: Requires a new items inspection query: items():immutable_list that creates an immutable_list of the queue items Queries: count() Purpose: The number of items in the queue items():immutable_list Purpose: A new list, created from the queue items, in the same order 68

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 69

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: count() Purpose: The number of items in the queue ensure: consistent_with_items: count() = items().count() 2. But count() is used in the pre-condition of remove(). Therefore: require: require: not empty: count () > 0 70 not empty: items().count () > 0

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() 71

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: 72 1. 2. The queue should be empty. The queue capacity is the a_capacity. This post-condition requires a new query about the capacity of a queue: capacity() Purpose: The number of items that self can hold Creation command: 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

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 73

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.item( count() ) = g 74

More derived queries Explicit queries for emptiness and being full can be added: Derived queries: is_empty() is_full() Purpose: Is the queue empty? ensure: consistent_with_items.count: Result = (items.count() = 0 ) Purpose: Is the queue full? ensure: consistent_with_items.count: Result = (items.count() = capacity() ) 75

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 () 76

pros and cons 77 Software Engineering, 2012

Benefits of 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. 78

Benefits of 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 79

Meyer s Perspective on Defensive Programming 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. Meyer s advice is, Don t do it! Think about this in the context of preconditions and exception handling. 80

Efficiency and Defensive programming Avoid inefficient defensive checks. For example, the sqrt method assumes that its argument is nonnegative. 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. 81

Cons Cost of writing contracts New language Takes practice - writing good contracts is a skill. False sense of security contract improve programs they don t 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. 82

Runtime checking In a programming environment that understands contracts, we would be told something like the following (we ll 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: was called by CUSTOMER_MANAGER add CUSTOMER_MANAGER_UIF change_customer 83

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 class s 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. 84

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 85

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/ 86

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. 87

88