Se stai usando la parola chiave "statica" senza la parola chiave "finale", questo dovrebbe essere un segnale per considerare attentamente il tuo design. Anche la presenza di un 'finale' non è un passaggio libero, poiché un oggetto finale statico mutabile può essere altrettanto pericoloso.
Stimerei da qualche parte circa l'85% delle volte che vedo un 'statico' senza un 'finale', è SBAGLIATO. Spesso, troverò strani accorgimenti per mascherare o nascondere questi problemi.
Si prega di non creare mutabili statici. Soprattutto collezioni. In generale, le raccolte dovrebbero essere inizializzate quando viene inizializzato il loro oggetto contenitore e dovrebbero essere progettate in modo da essere ripristinate o dimenticate quando il loro oggetto contenitore viene dimenticato.
L'uso della statica può creare bug molto sottili che causeranno agli ingegneri giorni di sofferenza. Lo so, perché ho entrambi creato e cacciato questi bug.
Se desideri maggiori dettagli, continua a leggere ...
Perché non usare Statics?
Ci sono molti problemi con la statica, tra cui la scrittura e l'esecuzione di test, così come i bug sottili che non sono immediatamente evidenti.
Il codice che si basa su oggetti statici non può essere facilmente testato dall'unità e la statica non può essere facilmente derisa (di solito).
Se si utilizza la statica, non è possibile scambiare l'implementazione della classe per testare componenti di livello superiore. Ad esempio, immagina un CustomerDAO statico che restituisce gli oggetti Customer caricati dal database. Ora ho un filtro cliente di classe, che deve accedere ad alcuni oggetti cliente. Se CustomerDAO è statico, non posso scrivere un test per CustomerFilter senza prima inizializzare il mio database e popolare informazioni utili.
E la popolazione e l'inizializzazione del database richiedono molto tempo. E nella mia esperienza, il tuo framework di inizializzazione del DB cambierà nel tempo, il che significa che i dati si trasformeranno e i test potrebbero interrompersi. IE, immagina che il Cliente 1 fosse un VIP, ma il framework di inizializzazione del DB è cambiato e ora il Cliente 1 non è più VIP, ma il tuo test è stato codificato per caricare il Cliente 1 ...
Un approccio migliore consiste nell'istanziare un CustomerDAO e trasferirlo nel filtro cliente quando viene costruito. (Un approccio ancora migliore sarebbe utilizzare Spring o un altro framework di Inversion of Control.
Una volta fatto, puoi deridere o stubare rapidamente un DAO alternativo nel tuo Test filtro cliente, permettendoti di avere un maggiore controllo sul test,
Senza il DAO statico, il test sarà più veloce (nessuna inizializzazione db) e più affidabile (perché non fallirà quando cambia il codice di inizializzazione db). Ad esempio, in questo caso assicurarsi che il Cliente 1 sia e sarà sempre un VIP, per quanto riguarda il test.
Esecuzione di test
Le statistiche causano un vero problema quando si eseguono insieme suite di unit test (ad esempio, con il server di integrazione continua). Immagina una mappa statica di oggetti Socket di rete che rimane aperta da un test all'altro. Il primo test potrebbe aprire un socket sulla porta 8080, ma hai dimenticato di cancellare la mappa quando il test viene abbattuto. Ora quando viene avviato un secondo test, è probabile che si blocchi durante il tentativo di creare un nuovo socket per la porta 8080, poiché la porta è ancora occupata. Immagina anche che i riferimenti Socket nella tua raccolta statica non vengano rimossi e (ad eccezione di WeakHashMap) non siano mai idonei per la raccolta dei rifiuti, causando una perdita di memoria.
Questo è un esempio troppo generalizzato, ma in sistemi di grandi dimensioni, questo problema si verifica TUTTO IL TEMPO. Le persone non pensano ai test unitari che avviano e interrompono ripetutamente il loro software nella stessa JVM, ma è un buon test di progettazione del tuo software e se hai aspirazioni verso l'alta disponibilità, è qualcosa di cui devi essere consapevole.
Questi problemi sorgono spesso con oggetti framework, ad esempio accesso ai livelli DB, memorizzazione nella cache, messaggistica e livelli di registrazione. Se stai usando Java EE o alcuni dei migliori framework di razza, probabilmente gestiscono molto di questo per te, ma se come me hai a che fare con un sistema legacy, potresti avere molti framework personalizzati per accedere a questi livelli.
Se la configurazione del sistema che si applica a questi componenti del framework cambia tra i test unitari e il framework unit test non smantella e ricostruisce i componenti, tali cambiamenti non possono avere effetto e quando un test si basa su tali cambiamenti, falliranno .
Anche i componenti non framework sono soggetti a questo problema. Immagina una mappa statica chiamata OpenOrders. Scrivi un test che crea alcuni ordini aperti e verifica che siano tutti nello stato giusto, quindi il test termina. Un altro sviluppatore scrive un secondo test che inserisce gli ordini necessari nella mappa OpenOrders, quindi afferma che il numero di ordini è accurato. Eseguiti singolarmente, entrambi i test passerebbero entrambi, ma quando eseguiti insieme in una suite, falliranno.
Peggio ancora, il fallimento potrebbe essere basato sull'ordine in cui i test sono stati eseguiti.
In questo caso, evitando la statica, si evita il rischio di persistenza dei dati nelle istanze di test, garantendo una migliore affidabilità del test.
Bug sottili
Se si lavora in un ambiente ad alta disponibilità o in qualsiasi punto in cui i thread possono essere avviati e arrestati, la stessa preoccupazione menzionata sopra con le suite di unit test può essere applicata anche quando il codice è in esecuzione in produzione.
Quando si ha a che fare con i thread, anziché utilizzare un oggetto statico per archiviare i dati, è meglio usare un oggetto inizializzato durante la fase di avvio del thread. In questo modo, ogni volta che viene avviato il thread, viene creata una nuova istanza dell'oggetto (con una configurazione potenzialmente nuova) e si evitano che i dati da un'istanza del thread passino all'istanza successiva.
Quando un thread muore, un oggetto statico non viene ripristinato o non viene raccolto. Immagina di avere un thread chiamato "EmailCustomers" e quando si avvia popola una raccolta String statica con un elenco di indirizzi e-mail, quindi inizia a inviare e-mail a ciascuno degli indirizzi. Supponiamo che il thread sia interrotto o annullato in qualche modo, quindi il framework ad alta disponibilità riavvia il thread. Quindi quando il thread si avvia, ricarica l'elenco dei clienti. Ma poiché la raccolta è statica, potrebbe conservare l'elenco di indirizzi e-mail della raccolta precedente. Ora alcuni clienti potrebbero ricevere e-mail duplicate.
A parte: finale statico
L'uso di "static static" è effettivamente l'equivalente Java di un C #define, anche se ci sono differenze di implementazione tecnica. AC / C ++ #define viene sostituito dal codice dal pre-processore, prima della compilazione. Un "finale statico" Java finirà con la memoria residente nello stack. In questo modo, è più simile a una variabile "const statica" in C ++ che a un #define.
Sommario
Spero che questo aiuti a spiegare alcune ragioni di base per cui la statica è problematica. Se si utilizza un moderno framework Java come Java EE o Spring, ecc., È possibile che non si verifichino molte di queste situazioni, ma se si lavora con un ampio corpus di codice legacy, possono diventare molto più frequenti.