MokaByte Numero  35  - Novembre 99
 
Il design by contract
di 
Andrea Giovannini
 


In un articolo precedente [1] abbiamo esaminato in dettaglio l'utilizzo delle interfacce in Java e i loro vantaggi. Avevamo anche osservato una limitazione di fondo nella definizione delle interfacce: non è infatti possibile specificare condizioni sul comportamento dei metodi se non attraverso la documentazione. Una possibile alternativa potrebbe essere rappresentata dai metodi formali di specifica ma il loro costo è spesso eccessivo

Il Design by Contract (DBC nel seguito dell'articolo) rappresenta un'interessante soluzione; si tratta di una metodologia che consiste, ad una prima approssimazione, nel considerare un'interfaccia come un contratto e nel definire i termini del contratto, ovvero
  • le condizioni che il client deve rispettare per poter chiamare un metodo (precondizioni);
  • le condizioni che il metodo si impegna a rispettare, purché le precondizioni siano soddisfatte (postcondizioni);
  • le condizioni che devono essere sempre valide (invarianti).
Nel seguito dell'articolo vedremo di approfondire il DBC quindi vedremo come sviluppare un supporto minimale per le asserzioni in Java.
 
 
 

Il DBC in dettaglio
La programmazione OO permette di sviluppare software  flessibile, riutilizzabile ed estensibile ma molto spesso questo non basta per avere software veramente affidabile. Poter riutilizzare codice esistente ha molti vantaggi purché quel codice faccia veramente ciò che noi ci aspettiamo. Ciò che è necessario per fare un salto di qualità nello sviluppo del codice è una metodologia che permetta di specificare in modo rigoroso il comportamento di un oggetto.
Questo è proprio quello che permettono di fare i metodi di specifica formale ma il tempo richiesto per il loro utilizzo è eccessivo e quindi si tratta di una soluzione non conveniente. Il DBC è stato sviluppato da B. Meyer e integrato nel linguaggio Eiffel [2] come possibile soluzione a questi problemi.
Come accennato nell'introduzione il DBC si basa sul concetto di contratto software: i contraenti sono un oggetto O e i client che lo utilizzano. Ognuno di essi ha degli obblighi

  • quando un client invoca un metodo m() dell'oggetto O deve garantire che lo stato dell'oggetto sia consistente prima dell'esecuzione del metodo (precondizioni);
  • l'oggetto da parte sua deve garantire che al termine dell'esecuzione del metodo il client veda soddisfatte le proprie richieste (postcondizioni);
  • inoltre vi sono delle condizioni che devono sempre essere rispettate durante l'esecuzione del metodo e durante tutto il ciclo di vita dell'oggetto (invarianti);
Se una delle condizioni non è verificata allora il contratto non è più valido e l'esecuzione deve essere interrotta con un'eccezione, o comunque occorre segnalare il problema in un qualche modo ad esempio utilizzando un file di log. Le condizioni specificate sono espresse in generale con i classici operatori relazionali (=, <, >, ...). Strumenti come iContract [7] supportano invece una sintassi più potente che comprende ad esempio i quantificatori (forall, exists) e che rappresenta un sottoinsieme di OCL, l'Object Constraint Language di UML [9].
Vediamo ora come si integra il DBC con l'ereditarietà, un aspetto molto importante per l'utilizzo di questa metodologia con la programmazione a oggetti. Se una classe derivata ridefinisce un metodo della classe base allora
  • le precondizioni del metodo nella classe derivata dovranno essere le stesse della classe base oppure più deboli;
  • le postcondizioni dovranno essere le stesse della classe base oppure le stesse.
In questo modo è possibile garantire che un client possa utilizzare la classe derivata in luogo di quella base. Il contratto della classe derivata è cioè più "rigido" di quella della classe base.
Un uso interessante del DBC è il supporto alla documentazione. Molto spesso infatti la documentazione è ambigua, poco precisa oppure del tutto mancante. Inserire nella documentazione di un metodo le sue pre e postcondizioni ne aumenta di gran lunga la qualità e aiuta ad utilizzare e comprendere il metodo in modo più sicuro ed efficace.
 
 
 

Usare il DBC in Java
Iniziamo subito con una nota dolente: Java non supporta il DBC e simularlo è abbastanza complicato. Occorre infatti inserire manualmente il controllo su precondizioni e postcondizioni nel codice dei metodi, prestando attenzione ad esempio a tutte le istruzioni di ritorno presenti. Se all'interno di un metodo troviamo cioè più istruzioni di return occorre verificare prima di ognuna di esse le postcondizioni. Inoltre la gestione dell'ereditarietà delle condizioni complica ulteriormente le cose. Dovranno infatti essere ancora i programmatori a verificare la consistenza dei controlli sui metodi ridefiniti nelle classi derivate. L'ultimo macigno è la mancanza di compilazione condizionale in Java, che è molto importante per eliminare le asserzioni dal codice rilasciato.
Una curiosità: uno dei primi documenti di specifica del linguaggio prevedeva il supporto al DBC, ma Gosling non lo ha poi inserito per problemi di scadenze [8].
Nonostante i problemi descritti prima vediamo ora come sviluppare un semplice tool per usare le asserzioni in Java. Non arriveremo a supportare appieno il DBC ma avremo comunque uno strumento utile per cominciare a ragionare in termini di contratto software a livello di singolo metodo, rinunciando però al supporto per l'ereditarietà. Come osservato poi in [3] l'uso delle semplici asserzioni "è uno dei cardini della programmazione professionale".
Il seguente listato mostra l'implementazione completa del tool:

public class Contract {
    public static void require(boolean condition, String message) {

if ( ! condition) { 
throw new RuntimeException("Precondition violated: " + message);
        }
    }

public static void ensure(boolean condition, String message) {
 if ( ! condition) {
   throw new RuntimeException("Postcondition violated: " + 
    message);
  }
}

public static void invariant(boolean condition, String message) {

 if ( ! condition) {
  throw new RuntimeException("Invariant violated: " +
    message);
  }
 }
}

Per chi volesse approfondire l'uso del DBC in Java vi sono diverse librerie, anche commerciali, che offrono tale supporto anche utilizzando l'ereditarietà. Alcuni esempi si possono trovare in [8].
 

 

Esempio
Vediamo ora di mettere in pratica i concetti del Design by Contract e di testare la nostra classe Contract con un esempio. Supponiamo di voler realizzare una classe che rappresenta i dati di una persona. Questa classe avrà attributi che rappresentano dati come nome, data di nascita e professione. Vi sarà quindi una serie di metodi per modificare ed accedere a questi attributi, con alcuni vincoli di integrità come ad esempio il fatto che il nome debba essere una stringa di lunghezza superiore a 0. Segue ora il listato della classe Person

import java.util.Date;

public class Person {
        private String name;
        private Date birthdate;
        private String job;

        public Person() {
            name = "";
            birthdate = new Date();
            job = "";
        }

        public void setName(String name) {
            Contract.require(name.length() > 0, "invalid name");
            this.name = name;
        }

        public String getName() {
            Contract.ensure(name.length() > 0, "invalid name");
            return name;
        }

        // ...
}

Ho tralasciato il codice degli altri metodi poiché è analogo. Possiamo vedere come il metodo setName() richieda nella precondizione che il parametro stringa debba avere una lunghezza maggiore di zero. Un controllo più sofisticato potrebbe verificare che la stringa contenga almeno uno spazio ed essere quindi della forma "cognome nome". Il metodo getName() verifica la stessa condizione sul risultato, usando quindi una postcondizione. Vediamo un possibile codice client che usa la classe precedente
    ...
   Person p1 = new Person(), 
  p2 = new Person(), 
  p3 = new Person(); 
  String name;

  p1.setName("Giovannini Andrea");    // OK 
  name = p1.getName();    // OK 
  p2.setName("");    // precondizione violata 
  name = p3.getName();    // postcondizione violata 
     ...

E' importante osservare che i problemi del codice precedente non si sarebbero verificati durante la compilazione. L'esempio presentato è molto semplice ma è facile immaginare la portata dei vantaggi del DBC in programmi più complessi con molte righe di codice. Un altro esempio di utilizzo del DBC in Java può essere trovato in [6].
 

 

Conclusioni
In questo articolo abbiamo introdotto una metodologia di progettazione e sviluppo poco conosciuta ma molto potente che permette di aumentare la qualità del software. Questo è fondamentale se si pensa alla programmazione per componenti che devono essere riutilizzati magari su vasta scala.
Il Design by Contract può essere applicato in linea di principio ad ogni linguaggio a oggetti e in questo articolo abbiamo visto come poter utilizzarlo in Java. Il tool qui presentato non vuole essere un completo supporto al DBC ma un semplice punto d'inizio.
Un aspetto che non abbiamo approfondito è l'utilizzo del Design by Contract in un ambiente multi-threaded. Ad esempio le precondizioni non rappresentano più vincoli sul chiamante perchè altri thread potrebbero intervenire per modificare lo stato dell'oggetto chiamato. La soluzione proposta sempre da Meyer consiste nel far diventare le precondizioni delle condizioni di sincronizzazione. Ulteriori informazioni possono essere trovate in [5].
 

 

Riferimenti

  1. Andrea Giovannini, "Sviluppare con le interfacce in Java", MokaByte N. 34, Ottobre 1999
  2. Il sito di Eiffel, http://www.eiffel.com
  3. Carlo Pescio, "Debugging: tecniche e tool", Computer Programming N. 43, Gennaio 1996
  4. Carlo Pescio, "Oggetti ed Interfacce", Computer Programming N. 63, Novembre 1997
  5. Carlo Pescio, "Oggetti e Thread", Computer Programming N. 65, Gennaio 1998
  6. Jim Weirich, "Design by Contract for Java", http://w3.one.net/~jweirich/java/javadbc/java-dbc.html
  7. Il sito di iContract,  http://www.reliable-systems.com/tools/iContract/iContract.htm
  8. Una raccolta di link su DBC e Java, http://www.elj.com/eiffel/feature/dbc/java/ge/
  9. Il sito di Rational, la società che ha sviluppato UML, http://www.rational.com

  
 

MokaByte rivista web su Java

MokaByte ricerca nuovi collaboratori
Chi volesse mettersi in contatto con noi può farlo scrivendo a mokainfo@mokabyte.it