software design and implementation: composition and substitution dror feitelson software engineering...
TRANSCRIPT
Software Design and Implementation:Composition and Substitution
Dror FeitelsonSoftware Engineering
Hebrew University
Composition
• Software systems are composed of multiple modules
• Modules are built and tested in isolation– Should be enough because modules are defined
by their interface• But still may not work correctly when
composed together(that’s why we need integration testing)
Agenda
• Preconditions and postconditions– Defining the interface
• Contracts– Integrating with the programming language
• Liskov substitution principle– When can a subclass be used in place of a
superclass
C. A. R. Hoare, “An Axiomatic Basis for Computer Programming”. Comm. ACM 12(10) pp. 576-580,583, Oct 1969.
Inventor of Quicksort (at age 26)Inventor of switch/case constructTook responsibility for null referencesInventor of Hoare Logic (this paper)Inventor of formalisms like CSPRecipient of 1980 Turing Award(and many other awards)
r := xq := 0while (r >= y) do
r := r – yq := q + 1
done
What does this code do?
And how can we express this?
Hoare Triplet
{P} S {Q}• P and Q are predicates• P is the precondition• S is a statement• Q is the postcondition• Semantics: if P holds before you execute S,
then Q will hold after you execute it
{what is the precondition?}r := xq := 0while (r >= y) do
r := r – yq := q + 1
done{what is the postcondition?}
{x >= 0 y > 0}r := xq := 0while (r >= y) do
r := r – yq := q + 1
done{¬(y <= r) x = q·y+r}
Axioms
Rule of consequence:If {P0} S {Q0}
and PP0
and Q0Q
Then {P} S {Q}
Axioms
Rule of composition:If {P} S1 {R}
and {R} S2 {Q}
Then {P} S1; S2 {Q}
Axioms
Rule of iteration:If {P B} S {P}
Then {P} while(B) do S done {¬B P}
• P is loop invariant• B is loop condition
Axioms
Rule of conditional:If {P B} S {Q}
and {P ¬B} T {Q}
Then {P} if(B) then S else T end {Q}
Axioms
Axiom of assignment:Denote by P[E/x] the predicate P with free
instances of variable x replaced by expression EThen {P[E/x]} x := E {P}• Not the other way around!• P will hold after the assignment if it would have
held before the assignment had the assignment been made
Bertrand Meyer, “Applying ‘Design by Contract’”. Computer 25(10) pp. 40-51, Oct 1992.
Famous for:Advocacy and teaching of object-oriented programmingEiffel programming language and IDEDesign by contractProf. at ETH ZurichRecipient of ACM Software Systems Award
The problem:• Coordinate between the two sides of an
interface• Who is responsible for what?Solution 1:• Defensive programming• Assume the other side is stupid and
irresponsibleSolution 2:• Contracts
Contracts
Provider Client
ObligationsWhat provider will do
What client must conform to
BenefitsWhat provider does not have to worry about
What client may expect to get
Contracts
Provider Client
ObligationsWhat provider will do
What client must conform to
BenefitsWhat provider does not have to worry about
What client may expect to get
Contracts
Provider Client
ObligationsWhat provider will do
What client must conform to
BenefitsWhat provider does not have to worry about
What client may expect to get
preconditions
postconditions
Indexingdescription: "Simple bank accounts"
classACCOUNT
feature -- Accessbalance: INTEGER
-- Current balancedeposit_count: INTEGER is
-- Number of deposits made since openingdo
if all_deposits /= Void thenResult := all_deposits.count
endend
Automatic documentation
Public member
Default initial value is 0
feature -- Element changedeposit (sum: INTEGER) is
-- Add sum to account.require
non_negative: sum >= 0do
if all_deposits = Void thencreate all_deposits
endall_deposits.extend (sum)balance := balance + sum
ensureone_more_deposit:
deposit_count = old deposit_count + 1updated: balance = old balance + sum
end
Pre-condition
Post-condition
Optional label used in error
messages
feature {NONE} -- Implementationall_deposits: DEPOSIT_LIST
-- List of deposits since account’s opening.invariant
consistent_balance:(all_deposits /= Void) implies (balance =
all_deposits.total)zero_if_no_deposits:
(all_deposits = Void) implies (balance = 0)end -- class ACCOUNT
Class invariant
Private member (seen by no other class)
Holds before and after every call to
every function
Invariants
• A general property of the class• Expressed as a predicate• Holds after the constructor• Holds before and after each method call– Not necessarily during method execution
• Effective precondition is precondition AND invariant
• Effective postcondition is postcondition AND invariant
Advantages
• Better methodology– Think clearly and express your assumptions
• Documentation– The contract is part of the interface– This is all the client needs to know
• Runtime verification by the system– Debugging (identify bugs early)– During production execution
Potential Disadvantage
• Provider x function f has precondition p• All clients will replicate the calling code
if p then x.felse …
• But:– p may be known already from context
(postcondition of previous operation)– else may be different in different clients (e.g. what
to do if pop’ing from an empty stack)
Inheritance
• Class SUB inherits from class SUPER• SUB may have a weaker precondition– SUPER.pre SUB.pre– If caller satisfies SUPER, will also work with SUB
• SUB may have a stronger postcondition– SUB.post SUPER.post– What SUB does is good enough for callers of SUPER
• SUB may also have stronger invariant• Part of the Liskov Substitution Principle
Violations
• Contracts do not check for special conditions• A runtime assertion violation is the
manifestation of a bug– Precondition: bug in client– Postcondition or invariant: bug in supplier
Verification
• Client is responsible for precondition• Supplier is responsible for postcondition• System can check this– Contract is part of the language
• Supplier can ignore bad input– Simpler code– Safe
• Client can assume result is correct– If not there should have been an exception
Verification Options• None– If program is correct, why waste time on checks?
• Preconditions only (default)– Useful for libraries with unknown clients
• Preconditions and postconditions• Preconditions, postconditions, and invariants• All (development and debugging): the above plus– Loop invariants– Loop variants (monotonically decreasing positive value
to ensure termination)– Assertions
Verification Options• None– If program is correct, why waste time on checks?
• Preconditions only (default)– Useful for libraries with unknown clients
• Preconditions and Postconditions• Preconditions, Postconditions, and invariants• All (development and debugging): the above plus– Loop invariants– Loop variants (monotonically decreasing positive value
to ensure termination)– Assertions
What would we think of a
sailing enthusiast who wears
his lifejacket when training on
dry land, but takes it off as
soon as he goes to sea?
-- C. A. R. Hoare
Exceptions
• Violating a monitored assertion causes an exception
• An exception may cause a failure• But you can sometimes avoid this!– Catch exception– Change something in conditions– Retry
connect_to_server (server: SOCKET)-- Connect to a server or give up after 10 attemptsrequire
server /= Void and then server.address /= Voidlocal
attempts: INTEGERdo
server.connectensure
connected: server.is_connectedrescue
if attempts < 10 thenattempts := attempts + 1retry
endend
Examples
r = sqrt( x )
l = log( x )
Examples
{ x ≥ 0 }r = sqrt( x ){ x = r² }
{ x > 0 }l = log( x ){ el = x }
Precondition is an algorithmic
necessity
Examples
account.deposit( s )
Examples
{ s > 0 }account.deposit( s ){ balance = old.balance + s }
Precondition reflects logic and semantics: if s < 0 it is a withdrawal
Examples
account.withdraw( s )
Examples
{ s ≤ balance}account.withdraw( s ){ balance = old.balance - s }
Precondition reflects design decision: does this
function deal with attempted overdraft or does it just handle the
technicalities?
B. H. Liskov and J. M. Wing, "A behavioral notion of subtyping". ACM Trans. Programing Languages and Systems 16(6) pp. 1811–1841, Nov 1994
Famous for:Contributions to programming languages and distributed computingLiskov Substitution Principle (this paper)Recipient of IEEE John von Neumann Medal (2004)Recipient of Turing Award (2008)
Substitution
If S is a subtype of Tthen
objects of type S may be used in place of objects of type T
without affecting correctness
Polymorphismabstract class Animal {
abstract String sound();}class Cat extends Animal {
String sound() { return "Meow!"; }}class Dog extends Animal {
String sound() { return "Woof!"; }}Vector<Animal> macdonalds = new…;for (Animal farmAnimal : macdonalds) {
farmAnimal.sound();}
Each farmAnimal at MacDonald’s is some
subtype of Animal and can be used in its place
Problem Example
• Inheritance embodies the ISA relationship
• A square is a special case of a rectangle
• So it seems natural to use inheritance
Rectangle
Square
Problem Example• But a rectangle has
separate width and height
• And they can be changed independently
• While in a square setting one changes the other
• So a square does not behave like a rectangle
Rectangleint: widthint: heightsetWidth()setHeight()
Square
Boolean checkArea(Rectangle r){
r.setWidth(4);r.setHeight(5);return (r.getWidth()*r.getHeight() == 20);
}
Formal Statement of LSP
If x is of type T and P(x) // property P holds for x and S is a subtype of T and y is of type SthenP(y) // property P also holds for y
Requirements
• Method signatures should be compatible• Contracts and invariants should be compatible• Exceptions should be compatible• Behavior should be indistinguishable– When using the subtype via the supertype
interface– Subtype may add things that are invisible via the
supertype interface
Syntactic Constraints
Method Signatures
• Return type of a method in a subtype can be more specific
returning a Dog instead of an Animal is OK• Argument types of a method in a subtype can
be more generalaccepting any Animal instead of just a Cat is
OK
Example
What happens if we substitute a DogShelter for an AnimalShelter?• Try to accept a horse
to the shelter• Try to adopt an
animal from the shelter
AnimalShelter
Void: accept(Animal)Animal: adopt()
DogShelter
Void: accept(Dog)Dog: adopt()
Exceptions
• Subtype may not throw new exceptions• But thrown exceptions may be subtypes of
exceptions of the supertype• The idea: calling environment must expect
these exceptions– So it can handle them
Semantic Constraints
Contracts
• Subtype cannot have stronger precondition• Subtype cannot have weaker postcondition• Subtype must preserve supertype’s invariant– Can be stronger
Example• Original postcondition
of setWidth(w):(width == w) && (height ==
old.height)• Square weakens this
by removing second clause
• Violation of LSP
Rectangleint: widthint: heightsetWidth()setHeight()
Square
Example
• In Naturals, subtract cannot accept large argument because result may not be negative
• Precondition is stronger
• Violation of LSP
Integers
int: add(int)int: subtract(int)
Naturals
int: add(int)int: subtract(int)
Example
• Naturals have the invariant that the value is non-negative
• Integers do not maintain this invariant
• Violation of LSP
Naturals
int: add(int)int: subtract(int)
Integers
int: add(int)int: subtract(int)
History Constraint
• Subtype may add new methods• These methods may change the object’s state• But the subtype may not change in ways that
the supertype could not• Changing new members that did not exist in
the supertype is OK
Example
• Immutable point cannot be moved
• Mutable point adds a function to move it
• But this can lead to a history (where point moves) which contradicts the possible histories of an immutable point (where it stays put)
ImmutablePointint: xint: y
MutablePoint
void: moveTo(x,y)
The Test
• I promise to give you an object of type T• Instead I give you an object of type S which is
a subtype of T• You may perform any test you wish on S using
T’s signature• If you cannot tell that the object is actually not
a T, then the LSP holds
Applicability
Mutability
• Problems mainly for mutable objects– If object is immutable (only getters, no setters)
then the behavior is simplified– ImmutableSquare inheriting from
ImmutableRectangle is OK• Subtype can have new fields that are mutable
Example
• Immutable point cannot be moved
• Center of fixed circle cannot be moved
• But its radius can be changed
ImmutablePointint: xint: y
ImmovableCircle
int: radius
void: setRadius(r)
Problem with ContractsPostcondition of setWidth(w):
(width == w) && (height == old.height) && (color == old.color) && (shadow == old.shadow) && (line == old.line) && …
Rectangleint: widthint: heightsetWidth()setHeight()
Square
Strong Behavioral Substitution
• The requirements of LSP are strong• Many hierarchies that look natural violate LSP• May not really be a problem– Depends on what the environment actually
expects and uses• A safety condition– Without LSP, when using a type T you need to
know about all possible subtypes S
Documentation
• The LSP is about expected behavior• Expected behavior often cannot be deduced
from code• Thus it is important to document expected
behavior• Especially crucial in methods that may be
overridden
Rectangle / Square Resolved• Violating LSP
indicates a problematic design
• Extract interfaces and/or inherit from abstract base classes
Boxint: widthint: height
Square
setSide()
Rectangle
setWidth()setHeight()