Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Un altra funzione per verificare il codice, sono le eccezioni, che si possono gestire
o non gestire, a seconda dei casi.
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
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)
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
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
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.
IS_5 - L. Nigro 19
(sempre sulle slide ci sono degli esempi e il costrutto assert dopo la slide 19)
Stack limitato
Qual è l’invariante di classe (INV) ?
IS_5 - L. Nigro 42
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]
IS_5 - L. Nigro 43
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:
La inner class old è un ricordo, ovvero un “memento” (pattern già visto), dello
stato, quindi quando invochiamo il metodo, creiamo un memento dello stato.
Verifica Postcondizioni
A questo punto, ogni metodo, come prima azione esegue
l’istruzione:
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
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.
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:
S s; …
D d=new D(…
D(…);
s=d;
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
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.