Specifiche
Scopo e significato delle specifiche (1)
Lo scopo di una specifica è di definire il comportamento di un’astrazione.
Gli utenti si baseranno su questo comportamento mentre chi implementa
l’astrazione dovrà preoccuparsi che venga assicurato.
Un’implementazione che fornisce il comportamento descritto si dice che
soddisfa la specifica.
Definiamo significato di una specifica l’insieme di tutti i moduli di
programma che la soddisfano. Lo chiamiamo insieme specificando
della specifica.
Scopo e significato delle specifiche (2)
Esempio.
static int p (int y)
// REQUIRES: y > 0
// EFFECTS: Returns x such that x > y.
Questa specifica è soddisfatta da una procedura p che, quando chiamata con
un argomento maggiore di zero, restituisce un valore piú grande del suo
argomento. Membri dell’insieme specificando includono
static int p (int y) { return y+1; }
static int p (int y) { return y*2; }
static int p (int y) { return y*3; }
Come ogni specifica, questa è soddisfatta da un numero infinito di
programmi.
Attributi delle specifiche
Una specifica è sufficientemente restrittiva se esclude tutte le
implementazioni che sono inaccettabili agli utenti di un’astrazione.
Una specifica è sufficientemente generale se non preclude
implementazioni accettabili.
Una specifica dovrebbe essere chiara cosí che sia facile agli utenti
capirla.
Restrittività (1)
Una buona specifica dovrebbe essere abbastanza restrittiva da escludere
qualunque implementazione che sia inaccettabile agli utenti
dell’astrazione. Questa richiesta è alla base di quasi tutti gli usi di
specifiche.
In generale, discutere se una specifica è sufficientemente restrittiva oppure
no implica discutere gli usi che si fanno dei membri dell’insieme
specificando.
Errori tipici che portano a specifiche restrittive in modo inadeguato: non
mettere le richieste necessarie nella clausola REQUIRES.
Esempio.
Consideriamo tre specifiche per un iteratore elems di un multinsieme (bag)
di interi. Un IntBag is come un IntSet tranne che un elemento può
occorrervi piú volte.
Restrittività (2)
public Iterator elems ( )
// EFFECTS: Returns a generator that produces every element of this
// (as Integers).
public Iterator elems
// EFFECTS: Returns a generator that produces every element of this
// (as Integers).
// REQUIRES: this not be modified while the generator is in use.
public Iterator elems ( )
// EFFECTS: Returns a generator that produces every element of this
// (as Integers), in arbitrary order. Each element is produced exactly
// the number of times it occurs in this
// REQUIRES: this not be modified while the generator is in use.
Restrittività (3)
La prima specifica non tiene conto di quello che può capitare se il bag è
cambiato mentre il generatore restituito da elems è in uso e quindi
consente implementazioni che danno comportamenti completamente
differenti a seconda che il cambiamento del bag influenzi o no il valore
restituito.
Un modo di trattare il problema è di richiedere che il bag non sia cambiato
fintantoché il generatore è in uso, come viene fatto nella seconda
specifica.
Questa specifica può ancora non essere sufficientemente restrittiva perché
non vincola l’ordine in cui gli elementi sono restituiti. Sarebbe meglio o
definire un ordine o includere la richiesta "in arbitrary order". Inoltre la
specifica non chiarisce che cosa si deve fare quando un elemento
compare nel bag piú di una volta e non dice neppure esplicitamente che il
generatore restituisce solo gli elementi che sono nel bag. La terza
specifica corregge queste mancanze.
Restrittività (4)
Altri errori sono dovuti alla non identificazione delle eccezioni da segnalare
e alla mancata specifica di quello che va fatto in situazioni al confine.
Esempio. Consideriamo una procedura indexString that prende stringhe s1 e
s2 e, se s1 è una sottostringa di s2, restituisce l’indice a cui il primo
carattere di s1 occorre in s2 ( indexString ("ab" , "babc") restituisce 1).
Una specifica che contenesse solo questa informazione non sarebbe
abbastanza restrittiva perché non spiega che cosa capita se s1 non è una
sottostringa di s2 o se occorre piú volte in s2 o se s1 oppure s2 sono
vuote. La specifica seguente è abbastanza restrittiva.
public static int indexString (String s1, String s2) throws
NullPointerException, EmptyException
// EFFECTS: If s1 or s2 is null, throws NullPointerException; else if s1
// is the empty string, throws EmptyException; else if s1 occurs as a
// substring in s2, returns the least index at which s1 occurs; else returns -1.
// E.g. indexString("bc", "abcbc") = 1, indexString ("b" , a") = -1
Generalità
Una buona specifica dovrebbe essere abbastanza generale da assicurare che
pochi o nessun programma accettabile sia escluso. Anche se programmi
accettabili sono esclusi non lo dovrebbero essere i piú desiderabili, ossia i
piú efficienti ed eleganti.
Esempio. La specifica seguente
public static float sqrt (float sq, float e)
// REQUIRES: sq >= 0 && e > .001
// EFFECTS: Returns rt such that 0 <= (rt*rt - sq) <= e.
costringe l’implementatore ad algoritmi che trovano approssimazioni piú
grandi o uguali all’effettiva radice quadrata. Il vincolo può portare a una
perdita di efficienza.
Specifiche operazionali e definizionali (1)
Una specifica definizionale elenca esplicitamente proprietà che i membri
dell’insieme specificando devono esibire Un aspecifica operazionale,
invece di descrivere le proprietà dei programmi specificandi dà una ricetta
per costruirli.
Esempio. Prendiamo un programma che cerca un elemento in un array. La
prima specifica spiega come implementare la ricerca, mentre la seconda
descrive soltanto una proprietà che gli input e gli output devono
soddisfare. La specifica definizionale è piú breve e inoltre lascia
all’implementatore la scelta di esaminare l’array in un ordine diverso da
quelli dal primo all’ultimo elemento.
Specifiche operazionali e definizionali (2)
public static int search (int[ ] a, int x) throws NotFoundException,
NullPointerException
// EFFECTS: If a is null throws NullPointerException else examines
// a[0] , a[1] , ….. , in turn and returns the index of the first one
// that is equal to x. Signals NotFoundException if none equals x.
public static int search (int[ ] a, int x) throws NotFoundException,
NullPointerException
// EFFECTS: if a is null throws NullPointerException else returns i
// such that a[i] = x; signals NotFoundException if there is no such i.
Chiarezza (1)
Una buona specifica dovrebbe essere chiara. I fattori che contribuiscono alla
chiarezza sono concisione, ridondanza e struttura.
La presentazione piú concisa può non essere la migliore, ma è un buon punto
di partenza. La ridondanza può essere introdotta se serve a ridurre il
rischio di incomprensione o a cogliere errori.
Esempio.
Consideriamo un programma che prende due insiemi e verifica se uno è un
sottoinsieme dell’altro. La prima specifica è precisa ma può lasciare dei
dubbi. Con subset si intende sottoinsieme o sottoinsieme proprio? La
seconda specifica non lascia dubbi, ma non dice esplicitamente che si
vuole un test della proprietà “essere un sottoinsieme”. La terza specifica è
la piú soddisfacente.
Chiarezza (2)
static boolean subset (IntSet s1, IntSet s2) throws NullPointerException
// EFFECTS: If s1 or s2 is null throws NullPointerException else
// returns true if s1 is a subset of s2 else returns false.
static boolean subset (IntSet s1, Intset s2) throws NullPointerException
// EFFECTS: if s1 or s2 is null throws NullPointerException else
// returns true if every element of s1 is an element of s2 else returns
// false.
static boolean subset (IntSet s1, IntSet s2) throws NullPointerException
// EFFECTS: If s1 or s2 is null throws NullPointerException else returns
// true if s1 is a subset of s2 else returns false, i.e., returns true if every
// element of s1 is an element of s2 else returns false.
Scarica

Meccanismi di astrazione (2)