330 likes | 373 Views
Ranga Rodrigo based on Mark Priestley's Lectures. Design by Contract. Quiz: Question 1. State whether the following statements are true or false. Subclasses can safely strengthen invariants. Weakened preconditions in a subclass are implied by the preconditions in the superclass.
E N D
Ranga Rodrigo based on Mark Priestley's Lectures Design by Contract
Quiz: Question 1 • State whether the following statements are true or false. • Subclasses can safely strengthen invariants. • Weakened preconditions in a subclass are implied by the preconditions in the superclass. • Trying to write a stronger precondition has no effect. • Strengthened preconditions in a subclass imply the postconditions in the superclass. • An attempt to write a weaker postcondition in a subclass has no effect.
Quiz: Soulution 1 • State whether the following statements are true or false. • Subclasses can safely strengthen invariants. • Weakened preconditions in a subclass are implied by the preconditions in the superclass. • Trying to write a stronger precondition has no effect. • Strengthened preconditions in a subclass imply the postconditions in the superclass. • An attempt to write a weaker postcondition in a subclass has no effect. True 2 marks 2 marks True True 2 marks False 2 marks True 2 marks
Contract • A contract is a mechanism which specifies exactly how an interaction---between people, organizations etc---will take place. • The purpose of a contract is so that each party knows exactly what they must do, and what they can expect from the other party.
Contract • For example, a contract of employment specifies: • the hours an employee must work, the duties to be carried out, the applicable codes of conduct etc. i.e., the employee's responsibilities; • the salary paid to the employee, holiday allowance, pension arrangements etc. i.e., the employer's responsibilities.
Design by Contract • A software design technique introduced by Bertrand Meyer (the primary developer of Eiffel). • It applies to the specification of software modules (classes, methods): the idea is that the specification should be viewed as a contract between the supplier and the client of the module. • The supplier has the responsibility to provide certain services (in the module) • The client has the responsibility to use the module in certain ways • This can be viewed as a compile-time relationship between the people writing the code, or a run-time relationship between calling and called routines.
Design by Contract • The supplier has the responsibility to provide certain services (in the module). • The client has the responsibility to use the module in certain ways. • This can be viewed as a compile-time relationship between the people writing the code, or a run-time relationship between calling and called routines.
Contract of a Routine • For example, in the case of a routine the supplier provides parameters when calling the routine, which then carries out some operations and possibly returns a value. • The obligations on the client (caller) can be expressed as preconditions: they define what must be true before the routine starts. • The obligations on the supplier can be expressed as postconditions: they define what must be true after the routine completes.
The contract is therefore: • If the client calls the routine in a situation where the preconditions are true, the supplier guarantees to complete the routine in such a way that the postconditions are true.
Interaction with Other Features • The DBC mechanism interacts in various ways with other language features: • 1.Preconditions should be visible to the client, so that clients can write code which ensures that the preconditions are true before a routine is called. This means that preconditions cannot contain references to "private" features of the class.
Interaction with Other Features • The same argument does not apply to postconditions: client code would not normally check that a postcondition had been satisfied, so postconditions can contain "private" data. (The contract view in EiffelStudio displays "private" postconditions, but not the private features they refer to, which is a bit peculiar.) • In subclasses, contracts can be redefined by weakening preconditions and strengthening postconditions. • Notice that a class invariant is not really part of a class's external contract.
Defensive Programming • Defensive programming is the idea that a module should anticipate everything that could go wrong and include code to handle exceptional situations. • A divide routine coded defensively might look like this:
safe_divide( top : DOUBLE ; bottom : DOUBLE ) : DOUBLE is do if bottom = 0 then -- do some error handling end Result := top / bottom end • DBC discourages this approach, which on the face of it seems a bit odd given the emphasis placed on secure programming.
The arguments in favour of the DBC view: • Unlike defensive programming, the use of a precondition makes the client's responsibilities explicit. • The supplier shouldn't have to compensate for deficiencies in the client: the whole idea of a contract is that if the precondition is not met, it's the client's fault. • In many cases, there is no sensible error handling code that can be written in a routine when its precondition is false. All that can be done is to raise a run-time exception, and this can more simply be done by an assertion checker built in to the run-time system. This means that the design of an exception handling mechanism is closely related to the implementation of DBC.
Exceptions in Java and C++ • Java and C++ have very similar exception mechanisms. Here is a simple Java program that generates a run-time exception when an attempt is made to divide by zero. (Exceptions are normally said to be raised or thrown.)
public class Zerodiv { public static void main(String args[]) { int x = 5 ; int y = 0 ; System.out.println("Quotient = " + quotient(x,y)) ; } static int quotient(int x, int y) { return x/y ; } }
The output from this program is the following: Exception in thread "main" java.lang.ArithmeticException: / by zero at Zerodiv.quotient(Zerodiv.java:12) at Zerodiv.main(Zerodiv.java:7)
An Exception • The exception is a combination of two things: • A run-time signal which is passed up the call stack, interrupting the normal flow of control of the program. • An object, of class java.lang.ArithmeticException, defining the type of the exception, and possibly containing some data relating to the failure.
Exceptions can be caught, or handled, by enclosing the relevant code in a try block: • Exceptions can be caught, or handled, by enclosing the relevant code in a try block: static int quotient(int x, int y) { try { return x/y ; } catch (Exception e) { return 0 ; } }
Catch Statement • The catch statement prevents an exception being passed up the call stack, and allows the programmer to provide alternative code to be executed in the exceptional case. • An exception will be caught if its type conforms to the type specified in the catch clause.
Try Statement • Any statement can be enclosed in a try block, so the exception here could equally well be caught in the main function as follows. • Notice that this code specifies the type of the exception more carefully, and provides error handling code that is more appropriate to the situation.
public static void main(String args[]) { int x = 5 ; int y = 0 ; try { System.out.println("Quotient = " + quotient(x,y)) ; } catch (ArithmeticException e) { System.out.println("Can't divide by zero") ; } }
C++ and Java Mechanisms • There are various refinements and complications which are being ignored here, but the main features of the C++/ Java mechanism are illustrated, namely: • Exceptions are modelled as objects, of library or user-defined types. • Any statement can be included in a try block, and exceptions caught and handled at that point.
C++ and Java Mechanisms • This is therefore a very general mechanism for transferring control and data between arbitrary points in a program, which is being applied to the specific problem of handling exceptions. • It raises the possibility of misusing the mechanism to achieve arbitrary transfers (a bit like the famous goto statement), to the detriment of good program structure.
Exceptions in Eiffel • Eiffel, by contrast, defines a more restricted notion of exception handling, which is meant to capture only those aspects which are crucial to the problem, and it expresses this syntactically. • It is therefore aiming to be more restrictive than C++ and Java, but to provide a simple mechanism which is more secure, in being less open to misuse for other purposes.
Exceptions in Eiffel • In Eiffel, exceptions are related to the idea of design by contract. • A routine call is said to succeed if it terminates in a state which satisfies its contract, and to fail otherwise. • An exception is an event which causes a routine to fail, such as the following:
Trying to call a feature on Void. • Trying to assign Void to an expanded target. • An abnormal condition being detected by the hardware or operating system. • Calling a routine that fails (i.e., exceptions are passed up the call stack, as in Java and C++). • An assertion not being satisfied, if the appropriate check is being performed at run-time. • Executing an explicit instruction to trigger an exception.
Legitimate Responses in Eiffel • Java and C++ essentially allow a program to respond in any way to an exception. • By contrast, Eiffel recognizes only two legitimate responses: • To retry the routine, presumably after making some attempt to ensure that the exception will not occur again. • To fail: restore the environment to a stable and legal state, and report failure to the calling routine.
The Eiffel syntax for exceptions reflects this, and is very simple: a routine can have a rescue clause containing code which is executed if an exception occurs in the body of the routine, and the rescue can contain a retry statement, which causes the body of the routine to be executed again from the beginning
A rescue clause that does not include a retry statement causes the exception to be passed up the call stack. • This means that a routine is forced to satisfy its contract or to fail: you are not meant simply to patch things up in the rescue clause before carrying on. • This principle is meant to ensure that if a routine finishes without an exception, the caller can assume that it has met its contract (i.e., its postconditions are true).
As a result of these rules, it is difficult to write Eiffel routines which do arbitrary things like return zero when a division by zero is attempted: quotient(x : INTEGER ; y : INTEGER) : REAL is local division_tried : BOOLEAN do if not division_tried then Result := x / y end rescue division_tried := True retry end
This returns zero if y is zero, because of the automatic initialization of Result when the routine is retried. • However, from the DBC point of view, this is a strength, not a weakness. • The argument is that if an attempt is made to divide by zero, there is mathematically no correct answer that can be returned. If the routine was equipped with pre and post conditions:
quotient(x : INTEGER ; y : INTEGER) : REAL is require NonZeroDenominator: y /= 0 do Result := x / y ensure IgnoreRoundingErrors: x = Result * y end • an exception will automatically be raised if a zero denominator is detected. • There is no value that could be returned that would make the postcondition true in all cases, and therefore no way that the routine can meet its contract in this case. There is no alternative but to return an exception.