Mòdul 4   

Pràctica 4: Depuració
Tornar presentació tema
    Pràctica 1 Pràctica 2 Pràctica 3 Pràctica 4 Pràctica 5  
     
  En aquesta pràctica treballarem sobre un projecte relativament complex, potser un xic més que els que hem fet fins ara. Això ens ajudarà a aprendre coses noves de Java especialment algunes tècniques de depuració dels nostres projectes, que facin possible el seguiment i correcció senzilla dels inevitables errors de programació.  
     
 

Ens vigilen!

 
     
 

Imagineu que rebem l'encàrrec d'escriure el programari per a un sistema de control de personal adaptat a les necessitats d'un institut. L'encàrrec implica que:

  • Hem de fer tarjetes per a tots els estudiants que fitxen a l'entrar i al sortir de l'institut
  • Hem d'organitzar l'accés al servidor de l ainformació perquè els pares puguin supervisar, en línia, els moviments dels seus fills.

Per a resoldre aquest problema amb senzillesa, necessitem pensar acuradament en quines peces necessitem.

Aquest és el diagrama que suggerim:

  • Una clase Moviment amb la informació necessària per a cada entrada o sortida d'un estudiant.
  • Una classe que enregistra la llista de moviments (Servidor),
  • Una tarjeta per als estudiants (EstudiantClient) i
  • Una tarjeta pels pares (PareClient).
 
     
   
     
Obre el i crea un nou projecte amb el nom presencia. Ves afegint les classes que es detallen més avall. Revisa el codi de cadascuna per a verificar la feina que fan.  
     
  A) Moviment    
     
 

Aquesta classe Moviment volem que reculli l'estructura informativa de les entrades i sortides d'alumnes.

Què necessitem saber d'un moviment?

  • qui el fa
  • quan es fa
  • si es tracta d'una entrada o d'una sortida.

Aquesta n'és una traducció en Java:

 
     
  /**
* Estructura d'un moviment.
* Conté informació sobre qui fa el moviment, en quina data
* i si és entrada (entrada=true) o sortida (entrada=false)
*
* @author angel solans
* @version 15-04-2004
*/

public class Moviment {
   
 // Codi d'estudiant
    private String estudiant;
   
 // Hora en que es produeix el moviment
    private java.util.Date hora;
    
// És una entrada o una sortida?
    private boolean entrada;

     /**
     * Al construir el moviment, assignar l'hora actual i
     * assignar el moviment a una entrada.
     */

    public Moviment() {
        estudiant="";
        hora=new java.util.Date();
        entrada=true;
    }

    
/**
     * Set del camp estudiant
     */

    public void setEstudiant(String estudiant) {
        this.estudiant=estudiant;
    }

    
/**
     * Set el camp entrada
    * */

    public void setEntrada(boolean entrada) {
        this.entrada=entrada;
    }

   
 /**
     * Get del camp estudiant
     */

    public String getEstudiant() {
        return this.estudiant;
    }

    
/**
    * Get del camp entrada
    */

    public boolean isEntrada() {
        return this.entrada;
    }

    
/**
     * Get del camp Hora
     */

    public java.util.Date getHora() {
        return this.hora;
    }

}

 
     
 

Aquest tipus de classes, que descriuen la forma d'un objecte sense fer res més i, per tant, contenen només constructor/s, camps privats i mètodes accessors als camps (getters i setters), reben el nom de POJO (Plain Old Java Object). Moltes eines de Java treballen amb pojos. És bo, doncs, que sempre intentis descriure els objectes que fas en forma de pojo.

Observa que definim tres camps: estudiant, hora i entrada.

  • El primer és una cadena amb el codi d'estudiant. A la vida real, aquest camp coincidiria amb la clau primària de la taula d'estudiants de la base de dades.
  • El segon és un objecte de la classe java.util.Date, un contenidor de les dates i hores en què es produeixen les entrades i sortides de l'edifici.
  • Finalment, un camp lògic que es posa a cert quan un moviment és una entrada i a fals quan és una sortida.

La informació del constructor és important: per defecte un moviment el definim com a entrada i, en crear l'objecte, fem l'assignació de l'hora actual. Així no ens cal patir per a gestionar l'hora en què es produeix el moviment: serà la mateixa hora de la creació de l'objecte Moviment. Com que aquesta hora ja no s'ha de canviar més, ja no hem escrit un mètode "setter" -setHora()- per aquest camp. Fixa't, en canvi, que sí que disposem d'un mètode getHora().

 
     
  B) Servidor    
     
 

Seguidament, creem una classe que s'encarrega d'enregistrar les entrades i sortides d'alumnes i de facilitar la informació als pares. És el cor del programa, la classe Servidor.

Afegeix-la al projecte presencia.

 
     
  /**
* Gestiona el registre d'accessos a l'institut i
* en dóna la informació als pares
*
* @author Angel Solans
* @version 15-04-04
*/

public class Servidor {
    
//Llista d'accessos del dia actual
    private static java.util.Vector registre =
        new java.util.Vector();

    /**
     * Només el servidor per defecte
     */

    public Servidor() {
    }

   
 /**
     * Entrada d'un estudiant a l'institut.
     * S'enregistra l'estudiant que ha entrat i l'hora
     *
     * @param estudiant codi de l'estudiant;
     */

    public void entra(String estudiant) {
        Moviment moviment = new Moviment();
        moviment.setEstudiant(estudiant);
        registre.add(moviment);
    }

    
/**
     * Sortida d'un estudiant a l'institut.
     * S'enregistra l'estudiant que ha sortit i l'hora
     *
     * @param estudiant codi de l'estudiant;
     */

    public void surt(String estudiant) {
        Moviment moviment = new Moviment();
        moviment.setEntrada(false);
        moviment.setEstudiant(estudiant);
        registre.add(moviment);
    }

    
/**
     * Eina de supervisió dels pares.
     * El servidor informa el pare dels moviments del seu fill
     * @param estudiant Codi de l'estudiant
     */

    public void supervisa(String estudiant) {
        java.text.SimpleDateFormat df =
            new java.text.SimpleDateFormat
                ("dd-MM-yyyy hh:mm aaa");
        System.out.println
            ("---------------------------------------");
        System.out.println("Activitat del vostre fill/a");
        System.out.println
            ("---------------------------------------");

        java.util.Iterator iterador = registre.iterator();
        while (iterador.hasNext()) {
             Moviment moviment = (Moviment)iterador.next();
             if (moviment.getEstudiant().equals(estudiant)) {
                String cadena = "Sortida a les ";
                if (moviment.isEntrada()) cadena =
                               "Entrada a les ";
               System.out.println(cadena+
                 df.format(moviment.getHora()));
            }
        }
    }

    
/**
     * Retorna el número de moviments de la llista
     * @return moviments del dia
     */

    public int getMoviments() {
        return registre.size();
    }

    
/**
     * Esborra les dades del vector
     */

    public void neteja() {
         registre.clear();
    }

    /**
     *Llista totes les entrades d'alumnes
     */

    public void getEntrades() {
        java.text.SimpleDateFormat df =
            new java.text.SimpleDateFormat
                        ("dd-MM-yyyy hh:mm aaa");
        System.out.println("Registre d'entrades");
        System.out.println("-------------------");

        if (registre!=null)
                for int (n=0;n<=registre.size();n++) {
            if ( ((Moviment)registre.get(n)).isEntrada()) {
                System.out.println(
                ((Moviment)registre.get(n)).getEstudiant()
                );
                 System.out.println(df.format(
                ((Moviment)registre.get(n)).getHora())
                );
            }
        }
    }


}

 
     
 

Observa els detalls importants d'aquest codi:

  • Novament camps static.
    Recordes què passa quan a un camp d'una classe li posem l'etiqueta static? Vol dir que el valor d'aquest camp serà compartit per tots els objectes o instàncies de la classe. En el nostre cas ens interessa utilitzar diferents objectes de la classe Servidor (els alumnes en crearan instàncies a l'entrar i sortir de l'institut i els pares quan supervisin l'activitat dels seus fills) però volem que només existeixi una única llista de moviments d'alumnes. La solució és posar el camp de la llista com a static i, a partir d'aquí, només hi haurà una llista per a tothom.

  • Vectors
    Observa que la llista és un vector. Perquè no hem posat una matriu? Com que no sabem el número d'entrades que tindrà la llista no podem utilitzar classes de llista amb la longitud fixa. En aquest mòdul has estudiat altres classes agrupadores que podríem utilitzar en aquest programa. Pensa quines són!
    El vector l'anirem omplint amb objectes de la classe Moviment. Cada cop que un alumne entri o surti de l'institut es crea un objecte Moviment que s'afegirà a la llista única de moviments.

  • La classe Servidor sap anotar entrades i sortides a la llista i fer un resum dels moviments d'un alumne. Els dos primers mètodes els utilitzen les targetes dels alumnes i el darrer les targetes dels pares.

  • Observa com es fa una entrada a l'institut. En primer lloc, creem un moviment. Gràcies al constructor de la classe Moviment, l 'objecte pren la data i hora actuals i s'assigna a la categoria d'entrades a l'institut. Només ens cal assignar el moviment a l'estudiant (moviment.setEstudiant(estudiant)) i afegir-lo a la llista (registre.add(moviment)).
 
     
      /**
     * Entrada d'un estudiant a l'institut.
     * S'enregistra l'estudiant que ha entrat i l'hora
     *
     * @param estudiant codi de l'estudiant;
     */

    public void entra(String estudiant) {
        Moviment moviment = new Moviment();
        moviment.setEstudiant(estudiant);
        registre.add(moviment);
    }
 
     
 
  •  Les sortides són similars, només cal afegir aquesta línia
 
     
           moviment.setEntrada(false);  
     
 

per a indicar que el moviment és una sortida i no una entrada.

 
 
 
 
  • Date i SimpleDateFormat

    Java té diverses formes de representar el temps. Pots utilitzar la classe més genèrica java.util.Date, assignar dates a bases de dades amb java.sql.Date o utilitzar un potent calendari amb GregorianCalendar, entre altres opcions.

    La classe java.util.Date és, però, la classe "tot terreny" de representació del temps. Té molta precisió, arriba als milisegons, i pot representar qualsevol data de la història. La manera més simple d'utilitzar-la és l'emprada en les classes Moviment i Servidor: es crea una instància de la classe amb el constructor per defecte new java.util.Date() i l'objecte s'actualitza a la data i hora actuals.
    La classe Date desa la informació en forma de número (del tipus long, un enter molt llarg) i, per tant, es pot operar matemàticament amb objectes Date com si fossin números. Esctrictament, una Date és el número de milisegons que han passat des del dia 1 de gener de 1970 a les 0 hores GTM fins al moment en qüestió.

    Ara bé, la visualització i representació de dates demana una elasticitat que els nombres no tenen. D'una data ens pot interessar veure'n el dia i l'hora, només el dia, el mes o l 'any, o l'hora en format de 24 hores o en format de 12 hores, amb precisió de segons o de minuts, etc. Les possibilitats són enormes.
    Per a facilitar aquesta tasca de representació existeixen els formatadors, que són unes classes que tenen la funció específica de traduir una data a una cadena de text aplicant-hi el format que desitgem. Observa aquest fragment:

        java.text.SimpleDateFormat df =
                new java.text.SimpleDateFormat
                            ("dd-MM-yyyy hh:mm aaa");

        System.out.println
                (cadena+df.format(moviment.getHora()));


    df és un formatador de dates del tipus SimpleDateFormat.
    • El constructor ens permet triar quin és el format de sortida de la data. En aquest cas hem decidit una sortida del tipus "01-04-2004 12:30 PM", que es correspon a la plantilla "dd-MM-yyyy hh:mm aaa" en el constructor de la classe SimpleDateFormat.
    • Cridant el mètode format amb una Date com a paràmetre, df ens retorna la cadena adequada: df.format(moviment.getHora()).


    Pots estudiar formats alternatius a partir de la documentació de la classe SimpleDateFormat.
    Els formatadors et permetran també fer el pas invers, construir una Date a partir d'una String. Per fer-ho procedirem així:

       String sdate="01-04-2004 12:30";
       java.util.Date ddate=null;
       java.text.SimpleDateFormat df=
           new java.text.SimpleDateFormat("dd-MM-yyyy hh:mm");
       try {
           ddate=df.parse(sdate);
       }catch(ParseException e){}

 
     

Modifica el mètode supervisa perquè ens retorni la data en els formats següents:

09 mayo 2004 12:08 PM
Domingo, 9 mayo 2004 12:08:56

 
     
  C) Les targetes de pares i estudiants    
     
 

La targeta dels estudiants només serveix per a fitxar a l'entrar i al sortir de l'institut. Afegeix la classe al projecte:

 
  /**
* Targeta de l'estudiant
*
* @author Angel Solans
* @version 15-04-2004
*/

public class EstudiantClient {
    private String estudiant;

    /**
     * Mètode constructor per a objectes de la classe
     * EstudiantClient
     * @param el codi d'estudiant
     */

    public EstudiantClient(String estudiant) {
        this.estudiant = estudiant;
    }

    /**
     * Entrem a l'institut
     */

    public void entra () {
        Servidor servidor = new Servidor();
        servidor.entra(estudiant);

        java.text.SimpleDateFormat df =
            new java.text.SimpleDateFormat
                ("dd-MM-yyyy hh:mm aaa");
            System.out.println("Has entrat a les "+
                df.format(new java.util.Date()));
    }


    /**
     * Sortim de l'institut
     */

    public void surt () {
        Servidor servidor = new Servidor();
        servidor.surt(estudiant);
        java.text.SimpleDateFormat df =
            new java.text.SimpleDateFormat
                ("dd-MM-yyyy hh:mm aaa");
            System.out.println("Has sortit a les "+
                df.format(new java.util.Date()));
    }

}

 
     
  Les targetes dels pares només executes el mètode supervisa(), que crida el mètode supervisa() del servidor. Afegeix la classe al projecte:  
     
  /**
* Targeta per als pares
*
* @author Angel Solans
* @version 15-04-2004
*/

public class PareClient {

    // Codi d'estudiant
    private String estudiant;

    /**
     * Mètode constructor per a objectes
     * de la classe PareClient
     */

    public PareClient(String estudiant) {
        this.estudiant = estudiant;
    }

    /**
     * Obté la llista d'entrades i sortides del fill
     *
     */

    public void supervisa () {
        Servidor servidor = new Servidor();
        servidor.supervisa(estudiant);
    }

}

 
     
 
  • Posa en marxa l'aplicació.
  • Crea dos estudiants i dónal's de clau "1" i "2" respectivament.
  • Crea també el pare de l'estudiant "1" obrint una instància de la classe PareClient amb un "1" com a paràmetre.
  • Fes fitxar els alumnes executant diferents vegades els mètodes entra() i surt().
  • Finalment, posa't en el paper de pare i executa el mètode supervisa(). Obtindràs una sortida similar a aquesta:
 
     
  El Depurador  
     
 

El codi del projecte de control de presència és senzill, molt simple si el comparem a un projecte de la vida real. És fàcil de depurar: si comets un error, segurament trobaràs l'origen amb facilitat.

Però això no sempre és així.

Pràcticament tots els entorns de programació incorporen eines de depuració per a facilitar la correcció i control de les aplicacions. Alguns depuradors són molt sofisticats, altres força senzills. incorpora un depurador elemental, molt fàcil d'utilitzar. Malgrat la pobra aparença, et resultarà més que suficient per a treballar amb la major part de programes que puguis escriure.

La feina més habitual del depurador consisteix en posar punts de ruptura en el programa.

  • Si hem marcat un punt de ruptura en un fragment de codi i iniciem la depuració, el programa s'executa fins arribar a la línia marcada.
  • Un cop aquí, s'atura l'execució del programa i el depurador ens dóna el valor actual dels camps i les variables de la classe.
  • A partir d'aquest moment, el programador controla l'execució del programa; podem aturar l'aplicació, deixar-la córrer fins al final o fer petits salts dins del codi per anar observant com canvia l'estat de la classe i localitzar així els problemes.
  • Els depuradors més potents permeten establir condicions per aturar-se o no en una línia o ens deixen canviar els valors de les variables sense reiniciar el programa.

Ara posarem el depurador en marxa. Abans, però, observa el mètode següent que hem inclòs a la classe Servidor:

 
     
 

    /**
     *Llista totes les entrades d'alumnes
     */

    public void getEntrades() {
        java.text.SimpleDateFormat df =
            new java.text.SimpleDateFormat
                        ("dd-MM-yyyy hh:mm aaa");
        System.out.println("Registre d'entrades");
        System.out.println("-------------------");

        if (registre!=null)
                for int n=0;n<=registre.size();n++) {
            if ( ((Moviment)registre.get(n)).isEntrada()) {
                System.out.println(
                ((Moviment)registre.get(n)).getEstudiant()
                );
                 System.out.println(df.format(
                ((Moviment)registre.get(n)).getHora())
                );
            }
        }
    }

 
     

Crea algunes targetes d'estudiant i fes alguns moviments, després crea una instància de la classe Servidor i executa el mètode getEntrades(). Obtindràs un error en temps d'execució, un ArrayIndexOutOfBoundsException. Tot i que estem convençuts que has trobat l'error en el codi d'un cop d'ull, imagina que és la darrera hora de la teva jornada laboral i estàs una mica espès: pots recórrer al depurador per a localitzar l'error.

Obre la classe Servidor, activa l'editor i ves al mètode getEntrades(). Faràs que el flux normal del programa s'aturi en una línia determinada per a repassar els valors de les variables i executar el programa línia a línia fins a trobar l'error. Com que d'entrada no tens massa idea d'on cau l'error, posaràs el punt de ruptura al començament del mètode. Busca la línia

        java.text.SimpleDateFormat df =
            new java.text.SimpleDateFormat
                        ("dd-MM-yyyy hh:mm aaa");

en el mètode getEntrades(). És la primera línia. Observa l'editor; a l'esquerra tens una petita columna. Fes un clic sobre la columna a l'alçada de la línia indicada. Si has encertat amb el ratolí, t'apareixerà un senyal d'stop a l'esquerra de la línia: acabes de triar un punt de ruptura pel programa.

Ara tanca l'editor i torna a la interfície principal del . Tria l'opció de menú Veure | Mostrar Depurador (o fes Ctrl + D). T'ha de sortir una finestra com aquesta:

 
     
 
 
     
 

És la finestra de depuració. Treballarem amb ella.

Torna a la finestra principal del i posa el projecte a treballar si encara no ho has fet: crea alguns estudiants i fes-los entrar a l'institut. Crea una instància de la classe Servidor. Executa el mètode getEntrades().

Immediatament ens salta la finestra de depuració a primer pla:

 
     
   
     
 

Ara ja pots manipular el depurador.

Observa que disposes de diferents caixes on es llisten, separadament

  • Les variables estàtiques o camps static.
  • Els camps o variables d'instància
  • Les variables locals.

En el moment que obrim el depurador, només és actiu un camp, la llista de moviments. És el vector que apareix a la caixa de variables estàtiques. Fes un doble clic sobre el seu nom: s'obrirà l'inspector d'objectes i podràs estudiar l'estat del vector.

Observa que el programa està aturat. Està esperant les teves ordres perquè ara tens el control de flux de l'aplicació. Clica sobre el botó Pas i el programa saltarà una línia. Si a la línia següent hi ha una crida a un mètode, picant sobre el botó Pas Endins el depurador entrarà a l'interior del mètode cridat i també podràs depurar-lo. Si vols que el programa continui amb normalitat, pica sobre el botó Continuar.

Per a localitzar l'error en el mètode, fes correr el programa pas a pas. Aniràs recorrent línies fins arribar al bucle d'impressió de dades. Observa que, caminant pas a pas, pots extreure totes les dades del vector fins que, arribant al darrer element, el bucle intenta localitzar un moviment més enllà dels límits del vector: aquest és l'error.

    


sinó


    
  

 
     
      if (registre!=null) for (int n=0;n<=registre.size();n++) {  
     
  Has de modificar el codi, no ha de ser  
     
       n<=registre.size()  
     
  sinó  
     
       n<registre.size()  
     
  de manera que la línia ha de dir:  
     
  if (registre!=null) for (int n=0;n<registre.size();n++)  
    &nbs