Proprio l'altro giorno commentavo insieme al mio amico J0rd4n0 un link da lui twittato riguardante dieci semplici cose da ricordare quando si scrive codice. La discussione era particolarmente filosofica, e mentre l'articolo da lui postato considerava quei punti "assolutamente necessari", io ero più propenso a dire che si stesse parlando di passaggi da valutare caso per caso. Inizierò quindi un esame punto per punto di questa filosofica discussione.
Quote 1: Evita di creare oggetti non necessari e usa sempre la 'Lazy Initialization'.
Ovvero: crea gli oggetti solo quando vengono richiesti la prima volta. Questo è uno dei punti dove il fatto che l'Informatica sia una scienza filosofica diventa evidente. Principalmente è il consiglio è giusto. In linea di massima, è inutile instanziare oggetti che poi non verranno utilizzati, o che subiranno un accesso solo dopo molte righe di codice. A meno che, e qui sta il "ma", nella nostra implementazione la velocità non sia un fattore determinante, al di sopra dei requisiti hardware e dei tempi di avvio. In quel momento, diventa d'obbligo che tutte le variabili il cui accesso diventa fondamentale sin dall'inizio del programma vengano inizializzate immediatamente per una questione di velocità e stabilità: velocità perchè andremmo a sfruttare i tempi lenti di inizializzazione del software a primo avvio. All'utente non interessa se l'eseguibile parte in 8 secondi o in 8,5. L'avvio di un software è sempre traumatico, e possiamo "mascherare" la nostra inizializzazione all'interno dei tempi di avvio. In più, le variabili fondamentali inizializzate all'inizio ci permettono di verificare immediatamente la disponibilità della memoria sulla macchina, e di avere una buona certezza di non incombere in eccezioni di tipo NullReference.
Altro punto contro questo consiglio: l'inizializzazione Lazy frammenta pesantemente il codice, ovvero potremmo trovare delle inizializzazioni in ogni punto della nostra classe, se non del nostro programma. La manutenibilità diventa ridotta all'osso. Io sono un vecchio estimatore delle dichiarazioni di variabili in testa del corpo, come si faceva sempre in C. In aggiunta, le dichiarazioni di variabili locali della classe sempre in testa al corpo dichiarativo. Possibilmente ordinate per tipo di dati. Perchè è importante farlo? Perchè nel caso ci fossero problemi nel trovare una variabile utilizzata in un metodo, sapremmo immediatamente dove cercare. Un esempio?
using System; using System.Diagnostics; public class Foo { #region Fields private String text1 = null; private String text2 = null; private object syncLock = null; private object notNeeded = null; #endregion #region .ctor public Foo() { this.syncLock = new Object(); } #endregion #region Methods public void FooMethod() { String text3 = null; bool ready; text3 = "FooTextAppend"; this.text1 = "Test 1"; this.text2 = this.text1 + text3; ready = true; lock (this.syncLock) { if (ready) Debugger.Log(0, Debugger.DefaultCategory, this.text2); } } #endregion }
Le variabili sono perfettamente ordinate. Le regions permettono di collassare il codice non necessario e la memoria non è utilizzata finché non richiesta. Il mio consiglio quindi diventa:
#1: Evita di creare oggetti secondari non necessari. Dichiara sempre le variabili locali in testa al metodo, le variabili di classe in testa al corpo della classe e inizializza gli oggetti solo quando necessario.
Per chi usa C#: usare sempre le #region per organizzare correttamente le dichiarazioni.
Si discuteva come secondo punto importante della limitazione della visibilità di variabili locali. Questo era quanto affermava il suo articolo:
Quote 5: Cerca sempre di limitare la visibilità di una variabile Locale.
tecnicamente è una affermazione giusta. E' il metodo utilizzato che è sbagliato. Secondo l'autore dell'articolo, non limitando la visibilità della variabile locale si può incorrere a bug generati dal copia incolla. E' vero, a prescindere che sono contro il copia incolla selvaggio. Ma come soluzione viene proposto "dichiara sempre la variabile dove serve". E si ritorna al mio consiglio #1. Dichiarando sempre la variabile dove serve, si finisce ad avere un inferno di dichiarazioni, decisamente ingestibili in situazioni dove le righe di codice sono molte. Le dichiarazioni delle variabili vanno sempre e comunque in testa al corpo codice. Il che significa, in parole spicciole, in testa al metodo per la visibilità maggiore, in testa ai for per la visibilità minore. Mai in coda e a metà. La ricerca selvaggia di dichiarazioni ha ucciso più di uno sviluppatore e fatto fallire progetti utili all'umanità. Ricordatelo sempre.
using System; using System.Diagnostics; public class Foo { #region Fields private String text1 = null; private String text2 = null; private object syncLock = null; private object notNeeded = null; #endregion #region .ctor public Foo() { this.syncLock = new Object(); } #endregion #region Methods public void FooMethod() { String text3 = null; bool ready; text3 = "FooTextAppend"; this.text1 = "Test 1"; this.text2 = this.text1 + text3; ready = true; lock (this.syncLock) { if (ready) Debugger.Log(0, Debugger.DefaultCategory, this.text2); else { for (int i = 0; i < 500; i++) { String newText = null; int counter = 0; newText = "New text: "; counter = i + 1; Debugger.Log(0, Debugger.DefaultCategory, newText + counter.ToString()); } } } } #endregion }
Il mio consiglio diventa:
#2: Limita il più possibile la visibilità delle variabili locali. Dichiarale sempre in testa al blocco di codice che le andrà ad utilizzare.
Per il resto sono più o meno d'accordo con l'autore dell'articolo, a parte il fatto che secondo me l'uso dell'eccezione generica Exception può e deve essere permesso, proprio per una serie di scenari generici per cui nasce.
Saluto Matteo e potete trovare l'articolo a questo indirizzo.
Nessun commento:
Posta un commento