come gestire il valore null

Come gestire il valore null nel codice? Ci possono essere molteplici risposte a questa domanda, a partire dalla considerazione del contesto a cui ci riferiamo. 

Vediamo di dare una cornice alla filosofia di sviluppo di mutabilità e immutabilità, facendo due chiacchiere con il nostro Davide e identifichiamo bene l’argomento di oggi. 

In che contesto siamo, innanzi tutto?

Il contesto è lo sviluppo generale di applicazioni di qualsiasi tipo, anche applicazioni embedded molto a basso livello, a contatto con l’hardware. Si tratta di  una tematica che è nata agli albori della programmazione con il linguaggio C e di conseguenza si estende in ogni campo dello sviluppo. 

Mutabilità, immutabilità e valore null. Qual è il denominatore comune? 

Sono due tematiche distinte: quando parliamo di  mutabilità / immutabilità parliamo di  una filosofia di sviluppo, orientata a ridurre in modo consistente il numero di bug nel codice delle applicazioni, organizzando il codice in un certo modo, mentre il valore null si riferisce a un valore critico che può rompere le applicazioni se non gestito in modo attento, ma è più una cosa pratica di tutti i giorni (una daily nuisance) rispetto a un paradigma di progettazione software da seguire.

Sappiamo bene che il valore null non significa che un campo è vuoto, ma che proprio non ha valore in memoria, sono due cose distinte.

Spazio vuoto, spazio bianco. Cosa significano in memoria?

Posso avere il campo nome che ha valore “Luigi” oppure “” 

‘Testo vuoto’ (appunto: “”) nella maggior parte dei casi significa che l’utente non ha proprio interagito con l’input di test dell’applicazione e semplicemente è andato avanti con il form) oppure ” ” – spazio bianco, l’utente ha premuto solo la barra spaziatrice senza digitare nulla. 

In questi casi il campo ha comunque un valore, per quanto poco significativo. Nella memoria del dispositivo comparirà che il campo nome è uguale a uno di quei valori.

Il valore null significa che quel campo non ha acquisito alcun valore ed in memoria compare proprio la dicitura “null” oppure “not referenced” (non referenziato). Ciò vuol dire che in memoria è stato creato un piccolo recipiente che dovrebbe contenere del testo, ma che attualmente non gli è ancora stato assegnato un valore dall’applicazione e pertanto il sistema operativo gli assegna il valore particolare null come valore di default (si tratta proprio di una convenzione risalente al secolo scorso per gestire questi casi eccezionali).

Quando è possibile, come va gestito?

Il modo migliore per gestirlo è prevedere sempre dei valori di default alle nostre variabili che indicano assenza di valore, come per esempio una lista vuota (indicata con []) oppure un oggetto vuoto (indicato come {}) oppure una stringa vuota (indicata come “”) anziché usare il null.

Non sempre questo è possibile e alle volte occorre saperlo gestire. In questi casi occorre affidarsi in primis a linguaggi moderni che ne permettano una gestione elegante, prevedendo la differenza fra tipo di dato nullabile (nullable) e non nullabile (not nullable).

Un esempio?

Per esempio se utilizzo il tipo Int (intero, un campo che può contenere un qualsiasi numero intero) significa che non potrò mai avere numeri uguali a null. Mentre se utilizzo il tipo Int? significa che, oltre a tutti i numeri interi, ammetto consapevolmente anche il valore null e potrei avere un campo intero uguale a null.

Avere già questa possibilità di distinguere in modo specifico cosa può essere null aiuta a differenziare le varie casistiche e aiuta lo sviluppatore e il suo ambiente di sviluppo a riconoscere gli errori in modo automatico – e non a posteriori – oltre che costringerlo a pensare all’eventuale valore null. Purtroppo ci sono linguaggi di programmazione che non permettono questa differenza e sta al programmatore prestare attenzione ai tipi di dato da utilizzare, con conseguenze pericolose se la scoperta di un valore null avviene in produzione e non durante lo sviluppo (molto spesso i valori null sono i responsabili dei crash delle applicazioni).

I linguaggi di programmazione che permettono questa gestione più accurata ricadono nella categoria dei linguaggi null-safety (sicurezza dal valore null).

Andando ancora più nello specifico, il modo più semplice e antico insegnato per gestire il valore null nel codice è semplicemente controllare che un campo non sia null prima del suo utilizzo.

If (nome != null) {

    // Qui posso usare la variabile nome senza preoccupazioni perché ha sicuramente un valore.

} else {

    // Qui NON posso usare la variabile nome perché non ha valore ed il compilatore lancerebbe subito un errore.

    // Devo pensare a come gestirla, per esempio assegnando un valore di default.

    nome = “”;

    // Oppure mostrando un errore all’utente.

    print(“Attenzione: la variabile Nome non ha valore!”);

}

Ok, questi sono i pro. E i contro?

Questo metodo è semplice, intuitivo e immediato ma ha dei “contro” da tenere presenti: solo per gestire il valore null, richiede del codice di gestione if else. Cosa succederebbe se invece di controllare solo nome, dovessimo controllare altri 39 campi? Come fare?

Null coalescing operator. Dicci di più

I linguaggi moderni mettono a disposizione il Null coalescing operator in grado di condensare in un’unica riga il controllo if else.

nome = nome ?? “”;

Questa assegnazione si traduce in “assegna al campo nome il suo valore se e solo se questo non è null, altrimenti assegna a nome una stringa vuota. Il vantaggio è un codice molto più lineare, breve e conciso, che favorisce la comprensione immediata del significato e permette allo sviluppatore di concentrarsi su parti del codice più complicate e non sulla gestione del valore null.