Five external criteria of software quality

(see OOSC chapter 1)

1. Correctness
Exactly performs its tasks, according to specification

2. Robustness
Function even in abnormal conditions

3. Extendability
Can be adapted to change in specifications

4. Reusability
Can be reused in new applications

5. Compatibility
Can be combined with other products


Reliability = Correctness + Robustness

Today: Focus on how to writing reliable software
- systems that work.

Aside: These are "software engineering" issues. They are only indirectly helped by O-O. They have been addressed by non O-O languages such as Ada.

class Account {
    int balance;

    public void withdraw(amount: int) {
	balance = balance - amount;
    }
}

Question: What should happen if the balance was 200 and amount was 1000?

  1. abort
  2. write a message
  3. set a flag
    extra withdrawal_done attribute in class?
    extra parameter in procedure?
    make procedure a boolean function?
  4. reduce balance by 200?
  5. reduce balance by 1000?
  6. don't do anything? (just return)

What to do? Observation: Your final product is not a withdraw function, or even an Account class. It is a system which uses the withdraw function.


Think of the withdraw function of the Account class as the service provider and the user of it as the client.


Whose responsibility is it to check that
amount <= balance ?



It is the responsibility of the client to ensure that data is correct.


Design by contract

(This idea of viewing a routine as a contract is due to Bertrand Meyer, author of Eiffel.
Rebecca Wirfs-Brock has a similar idea in her responsibility driven approach.)

Reference: B. Meyer "Applying Design by Contract" Computer, Oct 1992, pp 40 - 51 (see also OOSC chapter 11)


If you can't do something yourself, you may decide to get someone else to do it. You enter into a contract. Each party to the contract expects benefits, and is prepared to incur obligations. These benefits and obligations are spelled out in a contract document.


Party

Obligations

Benefits

Client

Provide package within weight and dimension limits.
Pay $2

Package delivered same day

Supplier

Deliver package same day

Don't have to worry about heavy, large or unpaid packages

An obligation on one party is usually a benefit to another.

But the obligation also provides a benefit in that all the requirements are spelled out - there are no hidden clauses.

Obligations

Benefits

Student

assignments handed in on time

prompt feedback

Tutor

assignments marked by next tutorial

weekend to mark assignments

all assignments marked in one block


Software Contracting

Obligations

Benefits

Client

only call withdraw with
amount <= balance

the balance is correctly adjusted

Server

ensure that the withdrawal is done according to business rules

don't have to worry whether there are enough funds in account

Pre and post conditions

It is necessary to specify the relationship between the client and the supplier as precisely as possible.
This is the "no hidden clause" principle.
The mechanisms for expressing such conditions are called assertions.

Preconditions express the requirements that any call must satisfy if it is to be correct.
Postconditions express properties that are ensured in return by the execution of the call.

Each routine has pre and post conditions which bind the routine and its callers. Can be viewed as a contract between server and client.

precondition: binds the caller
postcondition: binds the routine

The issue here is software quality In particular two concerns

The issue of correctness is addressed by assertions
(pre and post conditions, class invariants, etc).

The issue of robustness is addressed by exception handling


A class is correct when it does everything it is supposed to do and nothing else.


Every component must have a well defined task.
It does it all and it does it only.

Expressing of pre and post conditions varies from formal specification languages such as Object Z or formal algebra to informal English statements. Which approach you use will depend on the domain in which the model is developed and the experience of the modeller. Java + iContract uses Boolean expressions, which can be tested.

Examples: (informal English)


Object

Routine

Precondition


sqrt

argument non-negative

stack

pop

stack not empty

push

stack not full


Object

Routine

Postcondition


sqrt

(result)2 reasonably close to argument

stack

push E

E is top of stack
stack not empty
one more element in stack

table

insert E with key K

table has E with key K
table not empty
one more element in table

account

deposit D

balance greater by D

Expressing pre and post conditions as assertions
(Boolean expressions)


x non-negative


(result)2 close to x


stack can't be empty


size is 1 greater

num_elts = old num_elts + 1

E is the top of the stack

top = E

balance is D greater

balance = old balance + D


Implications of Software Contracts

Java does not currently support contracts. There are tools such as iContract which add contract support to Java. iContract puts pre- and post-conditions in the documentation comments for methods.

Requirements should appear as preconditions or in the body of the code, but not both.


/**
 * @pre ! isEmpty()
 */
int pop() {
    ...
}
OR

int pop() {
    if (isEmpty()) {
        ...
    } else {
        ...
    }
}

This focuses on whose responsibility it is to check.

It is the opposite of "defensive programming" - it avoids redundant tests.

Who should check?

No absolute rule.
Demanding style: precondition is strong, with responsibility on clients.
Tolerant style: more obligation on the routine.

Experience in writing Eiffel libraries is that demanding style works well. Each routine has a well-defined task which it does well, as opposed to trying to handle every imaginable case.

Ultimate test is to adopt the solution which achieves the simplest architecture.

Setting error flags seems appropriate for databases.

"Demanding" pop


/**
 * Remove top element
 * @pre ! isEmpty()
 */
int pop() {
    ...
}

"Tolerant" pop


/**
 * Remove top element if it exists
 * else set error flag.
 * No precondition (precondn = True)
 */
int pop() {
    if( isEmpty()) {
        error = true;
    } else {
        error = false;
        ...
    }
}

Assertions

Assertions are specifications, not instructions.

They describe the conditions under which the software elements will work, and the conditions they will satisfy in return.

Any runtime violation of an assertion is not a special case, but a manifestation of a bug:

Preconditions on the state of an object

Postconditions on the state of an object

Class invariants

Relate to the global properties of a class - they tell us what is common to all legal states of the object.


number of elements >= 0
number of elements <= max elements
empty = (number of elements = 0)

At the analysis/specification level they correspond to 'business rules'.


The bank balance must be positive
   (or >= overdraft limit)
No more than 5 withdrawals per month
There must be between 3 and 8 members in a committee

Class invariants

Class invariants which summarise information regarding the interaction of two or more features of a class are especially helpful.

At creation of an account, balance >= overdraft limit

To withdraw amount requires balance - amount >= overdraft limit

So we always have balance >= overdraft limit This makes it a class invariant

Types of Class Invariants

  1. On the state of an object

    
    balance >= 0
    count <= max
    

    This is the most common

  2. On relationships between features

For example, postconditions on reserving a book may be


reserved
not reserveList.isEmpty()
if there is only 1 reserver, and that reserver borrows the book, the postconditions are

not reserved
reserveList.isEmpty()

where there is a link like this, you can pin it down as a class invariant


reserved = ! reserveList.isEmpty()

then the postconditions above simplify to


reserved

and


not reserved
respectively

Design by Contract example

Committee

Business rule:
Must have between 3 and 8 members
Service:
Add a person to the committee

Adding new member to committee

Obligations

Benefits

Client

only add a person if there are fewer than 8 members

person is added to cttee,
cttee has right number of members

Server
(cttee)

person is member of cttee
# of members is incremented
cttee doesn't have too many members

don't need to worry about whether the committee is full

Precondition:number of members is less than maximum

Postconditions:


/**
 * @invariant count <= MAX_COUNT
 * @invariant count >= MIN_COUNT
 */
class Committee {

    List memberList;
    int count;
    final int MAX_COUNT = 8;
    final int MIN_COUNT = 3;

    /**
     * pre count < MAX_COUNT
     * post memberList.has(p) &&
     *      count = count@pre + 1
     */
    void addMember(Person p) {
        ...
    }
}

Testing of data

Data does have to be checked, but this approach focuses on whose responsibility it is.

Data should be checked at input time.

Programs should be simple. Complexity is the single most important enemy of software quality.

Redundant checking is not harmless

Should you trust the client ?
Maybe not, but if you can't trust them to test before why trust them to test after?

What to do if a condition is not met is the issue of robustness, addressed by exception-handling.

Guarantee, not test

Preconditions must be guaranteed, but that may not mean testing. The context may be enough to guarantee the precondition.


while event queue is not empty
    get next event // no testing necessary