Scopo e valutazione del progetto

La sottomissione di un progetto di qualità sufficientemente buona è un requisito necessario per il superamento del corso (assieme alle firme di frequenza e al superamento dei compitini).

Il progetto ha lo scopo di valutare la capacità degli studenti nel progettare e realizzare un programma Java, semplice ma di dimensioni maggiori di quelli fatti durante le esercitazioni del corso.

Di norma il progetto viene assegnato a gruppi di due persone. Solo in casi eccezionali (es. studenti lavoratori) è possibile chiedere di svilupparlo individualmente.

La valutazione del progetto sarà basata su:

  • testing del programma al calcolatore,
  • lettura del codice da parte del docente o di un assistente,
  • discussione dell'elaborato, durante la quale gli studenti potranno essere invitati a fare delle piccole modifiche al progetto.

Il docente ha la facoltà di chiedere agli studenti di rivedere il progetto e risottometterlo, nel caso il progetto non rispetti le specifiche date, oppure la qualità del progetto renda impossibile una valutazione ragionevole.

Valutazione: Ai fini della votazione finale, la valutazione del progetto produce di norma un incremento nell'intervallo [-3, +3] rispetto alla media dei voti dei due compitini (eccezioni sono possibili a discrezione del docente). Per gli studenti con media maggiore o uguale di 28, un incremento che superi il 30 non matura automaticamente la lode, che rimane a discrezione del docente.




Modalità di richiesta del progetto

Di norma il progetto viene assegnato a gruppi di due persone, e solo in casi eccezionali può essere chiesto da un singolo studente.

Il progetto può essere richiesto anche da studenti che non abbiano superato con successo il primo o il secondo compitino (o le corrispondenti prove di recupero). La discussione del progetto sarà possibile solo dopo aver superato entrambi i compitini.

Il progetto deve essere richiesto per email al docente del corso di appartenenza, a partire dal 3 giugno 2008 e non oltre il 28 luglio 2008. Il mail deve avere come oggetto [LIP-07] Richiesta progetto fine corso, e deve contenere nome, cognome e numero di matricola dei/l richiedenti/e.

Al momento dell'assegnazione del progetto, il docente controllerà che i richiedenti abbiano assolto agli eventuali obblighi di frequenza, e fisserà la data di consegna, che di norma sarà dopo un mese (escluso il mese di agosto: ad esempio, un progetto assegnato il 15 di luglio avrà come scadenza il 15 di settembre).

Dopo la consegna il docente correggerà il progetto, e fisserà con gli studenti che lo hanno sottomesso un appuntamento per la discussione.




Modalità di consegna del progetto

La realizzazione del progetto deve produrre, oltre ai sorgenti Java:

  • La documentazione HTML delle classi del progetto, prodotte con javadoc.

  • Una relazione in formato PDF, scritta in buon italiano e ben strutturata, che descriva in modo comprensibile le scelte progettuali e la struttura finale del progetto. In particolare, spiegare le scelte fatte in corrispondenza di eventuali ambiguità nella specifica del progetto, o nel caso di aspetti solo parzialmente specificati.
    Chiamare il file relativo Relazione.pdf per facilitarne l'individuazione.

Il progetto deve essere sottomesso sia in copia elettronica (tramite email con oggetto [LIP-07] Consegna progetto) che in copia cartacea entro la data fissata dal docente al momento della assegnazione.




Alcune linee guida per un buon progetto

  • [Requisiti] I requisiti del progetto da sviluppare sono descritti nel documento consegnato dal docente al momento dell'assegnazione del progetto. Si raccomanda di leggere con grande attenzione tutte le istruzioni.

  • [Progettazione] La relazione finale deve documentare in maniera puntuale la fase di progettazione (cioè individuazione delle classi, delle loro relazioni, dei metodi eccetera). In particolare, devono essere discusse le alternative considerate e le motivazioni per la scelta effettuata.

  • [Strutturazione] Ci deve essere una chiara separazione tra le classi del progetto che realizzano la logica dell'applicazione, e quelle che forniscono accesso al sistema, consentendone l'uso tramite menu oppure tramite opportuni metodi. Questa strutturazione deve essere ben descritta nella relazione.

  • [Formattazione e Documentazione] Il codice deve essere ben formattato, e riccamente commentato usando lo stile dei commenti javadoc.
    Si ricorda che la documentazione HTML del progetto, che è richiesta, viene estratta automaticamente da tali commenti usando il comando
    Project -> Generate javadoc
    oppure
    File -> Export -> Java -> Javadoc
    di Eclipse. Si veda Documentazione del codice in javadoc.

  • [Input di dati] Quando il programma chiede un dato in input all'utente, deve spiegare chiaramente il tipo di dato atteso e gli eventuali vincoli (ad esempio, "un intero maggiore di 0"). Il programma non deve mai terminare a causa di un'eccezione se il dato fornito dall'utente non è del tipo corretto.
    Per l'input da tastiera si può utilizzare la classe Input.java, una sua modifica, o qualunque altra soluzione equivalente.

  • [GUI (Graphical User Interface)] Il programma dovrà fornire all'utente la possibilità di accedere in modalità testuale utilizzando dei menu, come meglio descritto nelle istruzioni specifiche del progetto assegnato. Se lo si desidera, si può fornire, in aggiunta, una GUI. In questo caso al momento dell'avvio il programma deve consentire all'utente di scegliere se usare la GUI o l'interazione testuale. In ogni caso il codice dei menu o della GUI deve essere ben separato da quello che realizza le specifiche del progetto.

  • [Varie]
    • Il codice deve essere per quanto possibile privo di ridondanza e ripetizioni.

    • Le variabili di istanza di ogni classe devono essere private, a meno che non vi siano validi motivi per renderle più visibili (ad esempio protected se devono essere visibili in una sottoclasse).

    • Determinate il tipo di una variabile o di un parametro formale di un metodo in modo che sia concettualmente adeguato. Ad esempio, in un programma dove è definita una classe Persona, se una variabile viene usata per far riferimento ad una persona, non dichiararla di tipo String (memorizzando il nome della persona), ma di tipo Persona.

    • Usare, quando possibile, le interfacce invece di classi concrete che le implementano. Ad esempio, se una variabile conterrà una sequenza di elementi, dichiararla di tipo List e non Vector o ArrayList.

  • [Meta-Regola] Attenersi scrupolosamente alle istruzioni. Per ogni eventuale aspetto del progetto non specificato in modo esplicito nel testo, una qualunque soluzione è accettata, purché ragionevole e documentata nella relazione.



Esempio di progetto

Quello che segue è un esempio (semplificato) di quello che potrebbe essere un tipico testo di progetto.


Progetto ELABORAZIONE DI SEQUENZE DI VALORI NUMERICI

Scrivere un programma per la manipolazione e l'elaborazione di sequenze di valori numerici, che possono essere interi o valori in virgola mobile. Il programma deve consentire all'utente di generare una sequenza di valori numerici casuali, di salvare una sequenza di numeri in un file, di caricare una sequenza leggendola da un file, di stampare una sequenza, di calcolare la media e la varianza di una sequenza, e, nel caso di sequenze di interi, di calcolare la frequenza con cui ogni elemento compare nella sequenza.

L'interazione con l'utente, di tipo testuale, avviene tramite più menu di opzioni, comprendenti almeno quelli descritti di seguito. All'avvio del sistema, un menu iniziale offre la possibilità di creare una sequenza di numeri casuali (interi o in virgola mobile), di caricare una sequenza di numeri da un file, il cui nome viene chiesto all'utente, oppure di uscire dal sistema. La sequenza che viene creata o caricata da file diviene la sequenza corrente. Quando la sequenza corrente è stata correttamente creata o caricata, il sistema visualizza un menu che permette di selezionare le operazioni da eseguire su di essa. Se la sequenza è di numeri in virgola mobile, il menu deve consentire di calcolarne la media o la varianza; di visualizzare la sequenza su schermo; di salvare la sequenza in un file, il cui nome viene chiesto all'utente; e di ritornare al menu iniziale per caricare una nuova sequenza. Se la sequenza corrente è di numeri interi, oltre alle operazioni appena elencate il menu deve consentire anche il calcolo delle fequenze.

Altri requisiti:

  • Quando una sequenza di numeri (sia interi che in virgola mobile) viene salvata in un file, si deve scrivere un numero per riga.

  • Quando viene letta una sequenza di numeri da un file, è l'utente che determina se la sequenza è di interi o di numeri in virgola mobile. Inoltre si assume che il file contenga un numero su ogni riga. Le righe che non contengono un numero nel formato atteso vengono semplicemente ignorate, e l'esecuzione deve proseguire.

  • Quando l'utente invoca, nel menu per l'elaborazione della sequenza corrente, il comando per tornare al menu iniziale, nel caso la sequenza corrente non abbia una copia su file il sistema deve segnalare che la sequenza verrebbe persa e chiedere conferma all'utente. Se l'utente non conferma di voler tornare al menu iniziale, deve venire stampato nuovamente il menu corrente. Si osservi che la sequenza corrente non ha una copia su file solo se era stata generata dal sistema e non era mai stata salvata su file.

  • Il calcolo delle frequenze in una sequenza di interi produce una sequenza di coppie, ordinate rispetto alla prima componente, del tipo (x, num occorrenze di x / lunghezza sequenza). Il risultato deve poter essere stampato su schermo oppure salvato su file (con nome scelto dall'utente). In entrambi i casi si deve stampare una coppia per riga.

  • La varianza di una sequenza di numeri x1,...,xn è data dalla seguente formula, dove "mu" è la media della sequenza:
    \sigma^2 =  \frac{1}{n} \sum_{i=1}^n  \left( x_i - \mu \right) ^ 2.

Oltre all'accesso da menu, il progetto deve fornire un'API (Application Programming Interface) che consenta l'uso delle funzionalità realizzate in altri programmi. La API è definita dalla seguente interfaccia Sequence: fornire una classe chiamata SequenceImp che la implementi secondo le specifiche contenute nei commenti dei vari metodi. Si noti che questo richiede che siano definite le classi di eccezioni IllegalSequenceTypeException e NoCurrentSequenceException, che vanno usate dove opportuno nel progetto.

public interface Sequence{

    /** Genera una sequenza casuale di numeri, 
        di lunghezza uguale a length. 
        La sequenza e' di interi se type == true, 
        di double altrimenti. 
        La sequenza generata diventa la sequenza corrente.        
    */
     public void generate(boolean type, int length);


    /** Carica una sequenza di numeri
        (interi se type == true, double se type == false)
        dal file di nome 'filename', 
        e la fa diventare la sequenza corrente. 
        Lancia una FileNotFoundException se il file non esiste: 
        in questo caso la sequenza corrente risulta indefinita.
    */
     public void load(boolean type, String filename) 
         throws java.io.FileNotFoundException;


    /** 
        Salva la sequenza corrente nel file di nome filename. 
        Lancia una NoCurrentSequenceException 
        se la sequenza corrente e' indefinita. 
        Restituisce true se il salvataggio ha avuto successo, 
        false se c'e' stato un errore di I/O.
    */
    public boolean save(String filename) 
        throws NoCurrentSequenceException;


    /** Rende indefinita la sequenza corrente. 
     */
    public void drop();


    /** Restituisce true se la sequenza corrente e' definita,
        false altrimenti.
     */
    public void isDefined();


    /** 
        Restituisce la media della sequenza corrente.
        Lancia una NoCurrentSequenceException 
        se la sequenza corrente e' indefinita.
    */
    public double average() 
        throws NoCurrentSequenceException;


    /** 
        Restituisce la varianza della sequenza corrente. 
        Lancia una NoCurrentSequenceException 
        se la sequenza corrente e' indefinita.
    */
    public double variance() 
        throws NoCurrentSequenceException;


    /** 
        Restituisce la frequenza di occorrenze di n 
        nella sequenza corrente. 
        Lancia una NoCurrentSequenceException
        se la sequenza corrente e' indefinita. 
        Lancia una IllegalSequenceTypeException 
        se la sequenza corrente e' di double.
    */
    public double rate(int n) 
        throws NoCurrentSequenceException, 
               IllegalSequenceTypeException;

}




Struttura del progetto

La fase di progettazione deve portare all'individuazione delle classi rilevanti per l'applicazione. Nel semplice progetto in esame, si potrebbero considerare le classi SequenzaInt (sequenze di interi) e SequenzaDbl (sequenze di double): queste classi potrebbero essere, ad esempio, in relazione di ereditarietà, oppure potrebbero estendere entrambe una superclasse comune, ...

Altre classi che vanno individuate in questa fase riguardano la struttura complessiva del progetto: si ricordi che il progetto deve realizzare un sistema per elaborare sequenze numeriche, un menu di accesso alle funzionalità, e una classe SequenceImp che realizzi la API specificata dall'interfaccia Sequence.

È evidente dalla specifica che il sistema può essere utilizzato con due modalità diverse: da un utente a terminale, oppure da un programma java che usi la API. Si può anche pensare che in futuro il sistema venga utilizzato con altre modalità (ad esempio via rete tramite RMI, oppure attraverso una interfaccia WEB,...). In ogni caso, le funzionalità di "manipolazione di sequenze di numeri" offerte dal sistema, come descritte nel testo del progetto, non dovrebbero cambiare.




Separazione tra logica e accesso

Queste considerazioni portano in modo naturale ad un importante requisito, del tutto generale:

Il progetto deve essere strutturato in modo che ci sia una chiara separazione tra la "logica del progetto" e il modulo (o i moduli) che permettono di accedervi

Questa strutturazione si ottiene utilizzando classi diverse per la logica del progetto e per ognuno dei moduli che ne consentono l'accesso. In generale ognuna di queste parti può essere costituita da più classi.

Ad esempio, una tipica struttura del progetto potrebbe essere la seguente:

In relazione a questa struttura, si seguano con scrupolo i seguenti suggerimenti:

La classe Sistema deve fornire tutti i metodi che realizzano le funzionalità del sistema richiesto. I metodi di Sistema non devono fare assunzioni su chi li invoca, quindi non possono contenere operazioni di lettura da tastiera e di scrittura su schermo. Questi metodi possono solo restituire un valore come risultato, oppure lanciare un'opportuna eccezione.

La classe Menu gestisce i menu per l'interazione con l'utente, che in generale saranno realizzati con dei cicli e degli switch. Non usare la ricorsione per realizzare i menu.
Tipicamente, questa classe stampa un menu su schermo, aspetta la scelta di un comando da parte dell'utente, quindi chiede all'utente eventuali parametri per l'operazione richiesta, invoca il metodo di Sistema che realizza l'operazione, e infine comunica all'utente l'esito dell'operazione (che può essere la stampa del risultato, la segnalazione che l'operazione è terminata correttamente, oppure che è stata lanciata un'eccezione, ...).
Se l'operazione richiesta prevede la stampa di informazioni contenute nel sistema, tipicamente il metodo di Sistema corrispondente restituirà una stringa che sarà stampata su terminale da Menu.

Si ricorda di gestire in modo corretto i casi limite nei metodi che scrivete. Ad esempio, se un metodo elabora una sequenza di valori, considerare sempre il caso di sequenza vuota.