Problemi con il costruttore


Malgrado il titolo, non sto ristrutturando il bagno.
E no, non parlerò dei problemi della famiglia Ligresti.
Semmai parlerò di programmazione, stavolta in Java. Il post è un pochino tecnico, cercherò di renderlo semplice, ma se cominciate ad annoiarvi siete autorizzati a saltare alla conclusioni.

L’errore

Qualche tempo fa mi si presenta uno studente con un programma Java che gli andava in crash. Cercherò qui di farvi capire qual’è il problema con un piccolo esempio esplicativo (giusto per non farvi vedere la tonnellata di codice del programma originale, che non serve a niente). Ecco il mio programmino:

class Base {
    public Base() {
        System.out.println("Base constructor");
        function();
    }
    public void function() {
        System.out.println("Base.function()");
    }
}

class Derived extends Base {
    public Derived(String n) {
        super();
        System.out.println("Derived constructor");
    }
    public void function() {
        System.out.println("Derived.function()");
    }
}

public class ConstructorDemo {
    public static void main(String args[]) {
        Derived obj = new Derived();
        System.out.println("Fine del programma!");
    }
}

Se volete provarlo, salvate il file con nome “ConstructorDemo”, compilate con “javac ConstructorDemo.java”, ed eseguite con “java ConstructorDemo”. Otterrere il seguente output:


Base constructor
Derived.function()
Derived constructor
Fine del programma!

Notate l’ordine delle chiamate: quando cerchiamo di creare un oggetto di tipo “Derived”, per prima cosa viene chiamato il costruttore della classe Derived, che a sua volta per prima cosa chiama il costruttore della classe Base (con super()), il quale stampa a video la prima scritta.
Questo costruttore della classe Base chiama la funzione “function()” che è ridefinita nella classe Derived: quindi, entra in gioco il polimorfismo dei linguaggi Object Oriented, per cui tra le due funzioni che si chiamano “function()” quella chiamata è la seconda che stampa la seconda scritta. Infine, si ritorna nel costruttore Derived(), che stampa a video la terza scritta, e nel main che stampa l’ultima scritta.
Spero mi abbiate seguito fin qui.
Ebbene, questo programma è bacato. Cioè, se lo eseguite funziona, niente di male. Ma chi ha scritto la classe Base è una testa d’uovo e ha fatto un errore di progetto della classe. Infatti, un costruttore non dovrebbe mai chiamare una funzione che potrebbe essere in seguito essere ridefinita da una classe derivata! Perché l’oggetto nel suo complesso non è stato ancora costruito del tutto, e in particolare ci sono parti ancora non inizializzate. Per metter in evidenza il problema farò due piccole modifiche alla classe Derived. Ecco la nuova versione:

class Base {
    public Base() {
        System.out.println("Base constructor");
        function();
    }

    public void function() {
        System.out.println("Base.function()");
    }
}

class Derived extends Base {
    private String name; 
    public Derived(String n) {
        super();
        name = n;
        System.out.println("Derived constructor");
    }
    public void function() {
        System.out.println("Derived.function() - " + name.toUpperCase());
    }
}

public class ConstructorDemo {
    public static void main(String args[]) {
        Derived obj = new Derived("Giuseppe");
        System.out.println("Fine del programma!");
    }
}

Adesso il costruttore della classe Derived dichiara una variabile privata di tipo String, e ha un parametro sempre di tipo String. La variabile name viene inizializzata nel costruttore per essere uguale al parametro n. Inoltre, la funzione “function()” adesso, oltre a stampare la scritta “Derived.function()”, stampa anche il contenuto della stringa name convertito in maiuscole. Ed ecco l’output del programma:

Base constructor
Exception in thread "main" java.lang.NullPointerException
at Derived.function(ConstructorDemo.java:22)
at Base.(ConstructorDemo.java:6)
at Derived.(ConstructorDemo.java:17)
at ConstructorDemo.main(ConstructorDemo.java:28)

Penso sia abbastanza chiaro cosa sia successo: il costruttore Base() chiama la funzione “function()” che usa la variabile name prima che tale variabile venga inizializzata nel costruttore Derived, e quindi bum.
L’errore non è in chi ha scritto la classe Derived(), ma in chi ha scritto la classe Base() chiamando la “function()” nel costruttore. Naturalmente in questo caso è sempre il sottoscritto ad aver scritto entrambe le classi, ma in generale non è detto.

Chi è stato?


Ma non è stato lo studente a fare l’errore. Infatti, lui aveva appena seguito un corso di programmazione Android, la classe “Base()” faceva parte dell’SDK, e la classe Derived era stata scritta da lui per il progetto del corso. E naturalmente non poteva immaginare che chi aveva scritto l’SDK avesse fatto un errore così madornale. Più precisamente la classe Base era la android.widget.SimpleCursorTreeAdapter. Per fortuna il problema è stato successivamente risolto in una successiva release.
Ma è davvero un problema così grave? Beh, se uno studente fa una cosa così al mio esame di OOSD, io sarei tentato di bocciarlo, o quantomeno abbassargli parecchio il voto. Sì, per me è piuttosto grave.

Conclusioni

Possibile che a Google facciano delle cavolate così? Beh, sì, è possibile, l’errore è sempre dietro l’angolo e non sempre i programmatori delle grosse aziende come Google sono “top of the notch”.
E quindi la prima lezione di questo post è: sempre … cioè no, mai (cit.) chiamare funzioni polimorfiche dal costruttore, a meno che la classe non sia dichiarata final (ovvero non derivabile). Idem in C++ o in ogni altro linguaggio OO.
E la seconda è: non sopravvalutate mai l’abilità dei programmatori degli altri! Meditate gente, meditate …

Posta un commento o usa questo indirizzo per il trackback.

Commenti

  • dikiyvolk  Il 10 gennaio 2012 alle 17:49

    Wow… ciao Juhan dovresti fare il professore 🙂 io vengo volentieri a fare l’alievo!

    Mi è proprio piaciuto il post. Devo dire che non ho fatto caso al codice che ho guardato alla veloce… però direi che l’errore non era grave ma gravissimo.
    Nei costruttori si inizializzano le strutture dati dell’oggetto, almeno così mi avevano insegnato… ai miei tempi.

    Direi che i programmatori di google sono come tutti i programmatori 😀 non è che perché lavorano alla silicon valley sono più bravi di altri. Io che uso il mac con Lion vivo continui dejavu… il sistema installa patch a raffica!!!
    Comunque è un po’ desolante notare che oramai cose come la documentazione vengono ignorate o rese marginali, i test vengono fatti attraverso procedure automatiche e le analisi sono chiacchierate tra colleghi 🙂
    Ogni riferimento ad alcune tendenze/mode è puramente voluto… ma io sono vecchio 😛

  • dikiyvolk  Il 10 gennaio 2012 alle 17:56

    Uffi 2 errori in un solo commento che bravo 🙂 la foto in basso mi ha tratto in inganno… l’articolo l’ha scritto IL PROFESSORE. Chiedo venia 🙂 oltre a questo ho scritto allievo con una l sola… va bene torno a studiare italiano sui ceci…

    • juhan  Il 10 gennaio 2012 alle 19:52

      Vedi quando uno parla troppo poi la gente pensa che sia lui che continua e si dice: “toh! questa volta è sensato”. E invece no! non è farina del mio sacco.
      Però già che ci sono: ti ricordi ai tempi di Apollo il tuo capo che non voleva ammettere del baco che avevamo trovato?

  • Dikiyvolk  Il 10 gennaio 2012 alle 21:50

    Mi ricordo di Apollo, mi ricordo del mio capo… ma qual’era il baco… o stai cercando di convincermi che ho una forma di alzheimer galoppante? 🙂
    Davvero non mi ricordo e sono passati solo 20 anni… 18 per l’esattezza.

    • juhan  Il 11 gennaio 2012 alle 09:16

      Te lo racconto quando ci vediamo.
      Per il pubblico:
      1) tutti possono sbagliare;
      2) intestardirsi a sostenere una posizione insostenibile è –come dire– ecco;
      3) per fortuna ci sono gli aggiornamenti; o, anche meglio
      4) riscrivi quel pezzo di codice, fai prima.

Rispondi

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo di WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione /  Modifica )

Google photo

Stai commentando usando il tuo account Google. Chiudi sessione /  Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione /  Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione /  Modifica )

Connessione a %s...

Questo sito utilizza Akismet per ridurre lo spam. Scopri come vengono elaborati i dati derivati dai commenti.

%d blogger hanno fatto clic su Mi Piace per questo: