Sei sulla pagina 1di 10

Lezione 30-05-18

Asserzione(assert), è una funzione di java che serve per verificare la robustezza o


la correttezza di un sistema, un codice. Contiene codice che non contiene effetti
collaterali, che non modifica nessuna variabile, o se la modificano è perché
servono alla verifica di una certa proprietà.

Un altra funzione per verificare il codice, sono le eccezioni, che si possono gestire
o non gestire, a seconda dei casi.

DESIGN BY CONTRACT: (vedere slide di Nigro IS_5)

Design by Contract
Il termine, avocato ad es. da Bertrand Meyer in
“Object Oriented Sotware Construction-Second Edition,
Prentice Hall (1997)”, intende porre in evidenza
l’importanza di progettare il software secondo una
“logica contrattuale” tra fornitore (es.
progettista/creatore di una classe) e clienti
Il D-by-C si basa sulla definizione di un accordo formale
tra le parti che stabilisce “diritti e obbligazioni” da
ciascun lato, un pò come si fà nella vita quotidiana in
diverse circostanze (negoziazione di servizi,
acquisti/vendite etc.)
Attraverso la precisa definizione delle richieste e
responsabilità di ogni modulo software, e la disponibilità
di appositi strumenti da utilizzare durante lo sviluppo e
talora anche dopo (nel prodotto), si può sperare di
ottenere un elevato grado di fiducia circa la correttezza
di sistemi software complessi
IS_5 - L. Nigro 3

SCRIVI PER INSERIRE UNA DIDASCALIA.

si basa su un accordo tra i reparti, stabilisce diritti e obbligazioni di ciascun lato.


Cosi abbiamo la certezza che il sistema sia corretto. Possiamo andare a vedere se
il contratto non è stato soddisfatto, e anche da quale parte non è stato soddisfatto.
Nel contratto ci sono anche le pre-condizioni. Un esempio in programmazione lo
abbiamo quando andiamo ad invocare il metodo next(), infatti prima di invocarlo ad
a verificare la pre-condizione ovvero se ci sono ancora elementi, dunque faccio
hasNext(). Il design by contract mette anche una post-condizione. Però il design
by contract si utilizza in altri linguaggi di programmazione, tipo Eiffel. In java
abbiamo le asserzioni.

Correttezza
Una corrente di pensiero sullo sviluppo ad oggetti
del sw, propone sistematicamente di utilizzare le
asserzioni per esprimere la specificazione degli
elementi sw (es. singoli metodi di una classe)

Le asserzioni dovrebbero essere introdotte nelle fasi


di specificazione al fine di fornire argomenti
espliciti per mostrare la correttezza del sw

Le asserzioni, sistematicamente utilizzate, sono utili


per generare automaticamente la documentazione
e per guidare le fasi di testing e debugging del sw
IS_5 - L. Nigro 5
SCRIVI PER INSERIRE UNA DIDASCALIA.

Asserzioni
Usualmente, la specifica di P e Q viene concretizzata
mediante asserzioni, che verificano se la condizione-
proprietà è soddisfatta o meno
La violazione di un’asserzione è causa di eccezioni
Le eccezioni dunque si possono e debbono essere poste
come indicatori che da qualche parte sussiste una
violazione di contratto e dunque è necessario correre ai
ripari, per quanto è possibile
Java, come gran parte dei linguaggi, non supporta (al
momento) direttamente il D-by-C, ma potrebbe farlo in una
sua prossima release. Un linguaggio esplicitamente basato
su D-by-C è, invece, il già ricordato Eiffel di B. Meyer
Java, come altri linguaggi, rende disponibile (a partire dalla
versione 1.4) un costrutto assert che si accompagna ad
una espressione booleana, che può essere utilizzato per
“simulare” pre/post condizioni (si veda più avanti)

IS_5 - L. Nigro 16

SCRIVI PER INSERIRE UNA DIDASCALIA.

Un altro concetto che ha il design by contract, oltre alla pre e post condizione, è il
concetto di invariante di classe: è una condizione logica, che riguarda lo stato
dell’oggetto. Infatti dato lo stato dell’oggetto, dice quali sono i valori validi per
questo stato. È una proprietà che deve essere soddisfatta da tutti gli stati visibili di
un oggetto, per esempio quando abbiamo un metodo pubblico, l’invariante è
soddisfatto, però se questo metodo va ad invocarne uno privato che porta
l’oggetto non in uno stato consistente, non importa, basta che alla fine del metodo
sarà di nuovo soddisfatto l’invariante, non ci interessa lo stato intermedio, perché
l’invariante deve essere soddisfatto al momento che l’oggetto nasce, ovvero
quando c’è il metodo di costruzione, e alla fine del metodo.

Invariante di classe
Il progetto di una classe consiste dunque nei contratti
relativi ai suoi vari metodi; ma non solo …
Una classe è caratterizzata anche da una proprietà
invariante (INV) che in tutti gli stati stabili (es. non
durante l’esecuzione di un metodo) deve essere
verificata. Tutto ciò esprime il fatto che una classe
definisce uno stato interno dei dati che es. subito prima
o subito dopo l’esecuzione di un qualsiasi metodo deve
essere consistente
L’invariante deve essere stabilita dai metodi costruttori e
mantenuta dai normali metodi. Critico è il discorso dei
metodi pubblici. Quelli non pubblici (helper methods)
svolgono funzioni ausiliarie o interne e non devono
verificare l’invariante
IS_5 - L. Nigro 17

SCRIVI PER INSERIRE UNA DIDASCALIA.

Come si può controllare l’invariante? Se assicuriamo che alla fine del costruttore,
l’invariante è soddisfatto, e alla fine dell’invocazione di tutti i metodi pubblici è
soddisfatto, abbiamo risolto.

Quando una classe è corretta?


Quando:

C1: per ogni metodo pubblico m con argomenti argsm si


ha:

{prem(argsm) and INV} mbody {postm(argsm) and INV}

C2: per ogni costruttore c con argomenti argsc e le


inizializzazioni di default operate dal linguaggio sugli
attributi si ha:

{default and prec(argsc)}cbody{postc(argsc) and INV}

IS_5 - L. Nigro 19

SCRIVI PER INSERIRE UNA DIDASCALIA.

(sempre sulle slide ci sono degli esempi e il costrutto assert dopo la slide 19)

C’è un modo per rappresentare il D-by-C in java:

Il pattern D-by-C in Java


Nel pensare ad uno schema di supporto di D-by-C in Java, è
importante riflettere che la precondizione deve essere
responsabilità del client. Questo al fine di evitare, come si è
detto, uno stile di programmazione “troppo difensivo”
Il fornitore dei servizi deve poter procedere “contanto” sulla
precondizione e poi verificando (questa è sua responsabilità)
la postcondizione e l’invariante di classe (se non espliciti,
queste condizioni si valutano a true)
Se l’esecuzione del metodo genera un’eccezione perchè la
precondizione è violata allora l’eccezione potrebbe in primo
luogo essere trattata localmente al metodo con uno o più
tentativi di recovery seguiti da retry (ri-esecuzione del
metodo da capo, non chiamata ricorsiva). Dopo un numero
massimo di tentativi, l’eccezione (checked) dovrebbe
essere riportata al chiamante per le azioni del caso
La retry dopo un recovery deve ristabilire lo stato
dell’oggetto esistente all’ingresso nel metodo
IS_5 - L. Nigro 28

SCRIVI PER INSERIRE UNA DIDASCALIA.


Qua c’è un esempio:

Il pattern D-by-C in Java


public tipo m( args ) throws eccezioni {
int contaTentativo=0;
contaTentativo=0;
retry:for(;;){
try{
corpo di m
}catch(Throwable e){
fai tentativo di recovery
contaTentativo++;
contaTentativo++;
if( recovery non riuscito || raggiunto limite dei tentativi )
throw new PreMViolatedException();
PreMViolatedException();
continue retry;
}
break; //se il controllo arriva qui, l’esecuzione e’ andata a buon fine
}//for
assert postm;
assert INV;
}//m
IS_5 - L. Nigro 29

SCRIVI PER INSERIRE UNA DIDASCALIA.

Non è troppo corretto, perché la pre-condizione non è un problema di chi


implementa il servizio, è un problema di chi invoca il servizio. Qua l’errore è che si
fa carico di responsabilità che non è di chi implementa, perché se la pre-
condizione non è soddisfatta non esegue il metodo senza fare la prova, ovvero
senza andare a verificare anche la pre-condizione. Un esempio è il metodo
binarySearch, e gli passiamo un array non ordinato, non ci restituisce un
eccezione, anzi ci restituisce pure un valore, però non assicura che il risultato è
corretto. Alla fine invece fa bene, perché fa l’assert della post-condizione e
dell’invariante. Perché chi scrive deve garantire questo servizio, se non sono
soddisfatte sollevo l’eccezione, e trovo il problema. Errato mettere assert all’inizio,
o fare altre verifiche. Quello che si può fare invece, è mettere l’assert nel codice
che invoca il metodo. L’assert può essere abilitato o disabilitato. Quando lo
scriviamo nel codice, è già disabilitato, e per il metodo è come se non ci fosse. Se
ci accorgiamo che il programma non funziona correttamente, abilitiamo l’assert e
andiamo a vedere dov’è il problema.

Il problema della verifica della post-condizione: si potrebbero instaurare delle


relazioni tra lo stato dell’oggetto prima dell’esecuzione del metodo e dopo
l’esecuzione, quindi serve il vecchio stato. Prendiamo in esempio lo stack.
Possiamo mettere nella classe gli invarianti, che possono riguardo lo stato
generale dello stack:

Stack limitato
Qual è l’invariante di classe (INV) ?

Consistenza dimensionale: cima >=0 && cima<=n


Stato di vuoto: se cima==0, empty() è true
Stato di pieno: se cima==n, full() è true

La sussistenza di INV equivale a dire che la


congiunzione (and) di tutte le sottocondizioni
che formano l’invariante, è true in tutti gli stati
stabili di un oggetto stack

IS_5 - L. Nigro 42

SCRIVI PER INSERIRE UNA DIDASCALIA.

Post-condizione:

Stack limitato
Agevole è individuare le postcondizioni di
top, push e pop. In quanto segue si indica
con x l’elemento inserito da push o
restituito da top/pop:
top_post: stack==old stack &&
cima==old cima && x==stack[cima-1]

push_post: cima==old cima+1 && stack[old cima]==x

pop_post: cima==old cima-1 && x==old stack[old


cima-1]

IS_5 - L. Nigro 43

SCRIVI PER INSERIRE UNA DIDASCALIA.


Per esempio, quando facciamo push, stiamo modificando lo stato, e dobbiamo
dire com’è il nuovo stato rispetto al vecchio. “old” sta per il vecchio valore della
variabile d’istanza, all’inizio dell’invocazione del metodo.

E vediamo la verifica delle post-condizioni:

Verifica Postcondizioni
La funzione INV è programmata col modificatore protected in quanto è utile
anche nelle sottoclassi
Si nota che la funzione INV va invocata da assert in modo da attivarla solo
in fase di testing. I vari tipi di inconsistenze rilevate vengono segnalate dal
messaggio generato da AssertionError.
AssertionError.
Per verificare le post condizioni dei metodi top, push e pop occorre una
simulazione del costrutto old.
old. Di seguito si suggerisce un modo generale di
procedere,
procedere, agevolmente estrapolabile ad altri casi. casi.
Si introduce una classe inner di Stack nella quale si salvano,
salvano, durante le fasi
di testing e se le asserzioni sono abilitate,
abilitate, i dati dello stato di uno stack utili
condizioni. Nel caso dell’
per la verifica delle post condizioni. dell’esempio si ha:

public class Stack{


class Old{
int []stack;
int cima;
cima;
public Old( int []stack, int cima ){
this.stack=new
this.stack=new int[stack.length];
int[stack.length];
System.arraycopy(stack,
System.arraycopy(stack, 0, this.stack,
this.stack, 0, stack.length);
stack.length);
this.cima=
this.cima=cima;
cima;
}
}//Old

}//Stack IS_5 - L. Nigro 52

SCRIVI PER INSERIRE UNA DIDASCALIA.

La inner class old è un ricordo, ovvero un “memento” (pattern già visto), dello
stato, quindi quando invochiamo il metodo, creiamo un memento dello stato.

Come prima cosa, per verificare l’invariante o la post-condizione: assert=…(slide


sotto)

Verifica Postcondizioni
A questo punto, ogni metodo, come prima azione esegue
l’istruzione:

assert (old=new Old( stack, cima ))!=null;

dove la variabile old di classe Old è un nuovo attributo di Stack


utile solo per ragioni di testing

Per la verifica delle post si inseriscono alla fine dei metodi delle
apposite assert come segue:

metodo top:
assert Arrays.equals(stack, old.stack) &&
cima==old.cima &&
x==stack[cima-1] : “top inconsistente”;

IS_5 - L. Nigro 53

SCRIVI PER INSERIRE UNA DIDASCALIA.

Quindi faccio in modo che crei una copia dell’oggetto, solo quando siano abilitate
le asserzioni, per questo lo mettiamo in assert, che però richiede un’espressione
booleana, facendo la new restituisce un oggetto, ma mettendo !=null, diventa
un’espressione booleana e abbiamo la copia dell’oggetto.

Per il metodo top facciamo (slide sopra), sempre in assert perché pago la copia
dell’oggetto solo se sono abilitate le asserzioni.

Per il problema del D-by-C dell classi eredi:

D-by-C and Inheritance


Il D-by-C si estende anche alle gerarchie di ereditarietà
delle classi, che sono comuni in un approccio OO
Prima di discutere l’adattamento dei concetti D-by-C alle
classi eredi, è opportuno ricordare il principio di Liskov
sulla sostituibilità (o conformabilità) dei tipi:
Principio di Liskov: Una classe D derivata da una classe S
(superclasse)
superclasse) dovrebbe essere progettata in modo da obbedire
alla proprietà
proprietà che gli oggetti di classe D sono correttamente
sostituibili ad oggetti di classe S in tutti i posti dove si attendono
Ossia, un oggetto di classe D dovrebbe
oggetti di classe S. Ossia,
essere a tutti gli effetti un oggetto di classe S, solo un pò piùpiù
specializzato
Tale principio è stato intuitivamente utilizzato nel corso
di POO e su di esso sono state discusse le ben note
proprietà di polimorfismo e dynamic binding
Il principio di Liskov è integrato nel pattern D-by-C
adattato all’inheritance
IS_5 - L. Nigro 62

SCRIVI PER INSERIRE UNA DIDASCALIA.

Quindi si può modificare il contratto nella classe eredi, però deve essere sempre
soddisfatto quello della classe che sta sopra, quindi si deve adattare.

Se io ristringo la pre-condizione della classe erede, vuol dire che i casi ammissibili
sono minori, però il cliente che prima sapeva le pre-condizione della classe padre,
si trova spiazzato. Allora bisogna indebolire la pre-condizione:

D-by-C and Inheritance


Detta S è una superclasse,
superclasse, D una sua classe derivata,
derivata, ciò che si
vuole conseguire è vincolare il progetto di D in modo che
effettivamente gli oggetti di D possano essere correttamente
sostituibili ad oggetti di classe S
Si ricorda che ad una variabile s di classe S si può assegnare un
oggetto di classe D e che dopo questo s ha tipo statico S ma tipo
dinamico D, dunque s è polimorfa e dal polimorfismo nasce anche il
fenomeno del dynamic binding:

S s; …
D d=new D(…
D(…);
s=d;

Invocando su s un metodo m definito in S e ridefinito in D, viene


fatto partire materialmente la versione di m dovuta al dipo dinamico
e dunque il metodo m di D
È lecito aspettarsi che deve esistere una ben precisa relazione tra le
pre/post di m in S e le pre/post di m in D. Infatti,
Infatti, l’utente potrebbe
non sapere il particolare tipo dinamico di s e dunque le sue
aspettative sono legate a quanto stabilito dal contratto della classe
S
IS_5 - L. Nigro 63

SCRIVI PER INSERIRE UNA DIDASCALIA.


Regole D-by-C su una classe derivata


Una classe derivata D è “ben progettata”
progettata”, ossia segue il pattern D-
D-by-
by-C
se:
Il suo invariante è compatibile con quello della superclasse S, più
più
esattamente è vera la congiunzione:
congiunzione: INV_S and INV_D
Per ogni metodo m definito in S e ridefinito in D si ha:

premS implica premD, ossia premD è più


più debole di premS
o coincide con essa
postmD implica postmS, ossia postmD è più
più forte di postmS
o coincide con essa

Che la prem su D debba essere più più debole di premS si comprende subito
pensando che se il cliente garantisce sulla chiamata s.m( s.m(…) il
allora, anche se s ha tipo dinamico D,
soddisfacimento di prem di S allora,
sarà soddisfatta la premD dal momento che quest’
sicuramente sarà quest’ultima è
weaker
Invece,
Invece, una postcondizione piùpiù forte nella sottoclasse si può giustificare
considerando che il servizio evocato da s.m( s.m(…) per lo meno ritorna la
“qualità
qualità” prescritta dalla superclasse.
superclasse. Il tipo dinamico della sottoclasse
può invece fornire un servizio più
più accurato stante la postcondizione
stronger IS_5 - L. Nigro 64

SCRIVI PER INSERIRE UNA DIDASCALIA.

Dunque la pre-condizione si può indebolire nella sottoclasse, mentre post-


condizione si può solo rafforzare. Ciò non vuole dire che le possiamo tenere uguali.

L’invariante invece, la sottoclasse può introdurre una suo invariante, a patto che
sia soddisfatto anche l’invariante della superclasse, non si può né indebolire né
rafforzare.