it-swarm.it

I riferimenti null sono davvero una brutta cosa?

Ho sentito dire che l'inclusione di riferimenti null nei linguaggi di programmazione è "l'errore di miliardi di dollari". Ma perché? Certo, possono causare NullReferenceExceptions, ma che importa? Qualsiasi elemento del linguaggio può essere fonte di errori se utilizzato in modo improprio.

E qual è l'alternativa? Suppongo invece di dire questo:

Customer c = Customer.GetByLastName("Goodman"); // returns null if not found
if (c != null)
{
    Console.WriteLine(c.FirstName + " " + c.LastName + " is awesome!");
}
else { Console.WriteLine("There was no customer named Goodman.  How lame!"); }

Potresti dire questo:

if (Customer.ExistsWithLastName("Goodman"))
{
    Customer c = Customer.GetByLastName("Goodman") // throws error if not found
    Console.WriteLine(c.FirstName + " " + c.LastName + " is awesome!"); 
}
else { Console.WriteLine("There was no customer named Goodman.  How lame!"); }

Ma come va meglio? In entrambi i casi, se si dimentica di verificare l'esistenza del cliente, si ottiene un'eccezione.

Suppongo che una CustomerNotFoundException sia un po 'più facile da eseguire il debug di una NullReferenceException in quanto più descrittiva. È tutto quello che c'è da fare?

165
Tim Goodman

null è male

C'è una presentazione su InfoQ su questo argomento: Riferimenti null: errore da miliardi di dollari di Tony Hoare

Tipo di opzione

L'alternativa alla programmazione funzionale sta usando un Tipo di opzione , che può contenere SOME value o NONE.

Un buon articolo Il modello “Opzione” che discute il tipo di Opzione e ne fornisce un'implementazione per Java.

Ho anche trovato una segnalazione di bug per Java su questo problema: Aggiungi tipi Nice Option a Java per prevenire NullPointerExceptions . funzione richiesta è stato introdotto in Java 8.

93
Jonas

Il problema è che, poiché in teoria qualsiasi oggetto può essere nullo e generare un'eccezione quando si tenta di utilizzarlo, il codice orientato agli oggetti è fondamentalmente una raccolta di bombe inesplose.

Hai ragione sul fatto che una buona gestione degli errori può essere funzionalmente identica alle istruzioni if a controllo nullo. Ma cosa succede quando qualcosa che ti sei convinto non poteva possibilmente essere un null è, infatti, un null? Kerboom. Qualunque cosa accada dopo, sono disposto a scommettere che 1) non sarà grazioso e 2) non ti piacerà.

E non ignorare il valore di "facile debug". Il codice di produzione maturo è una creatura folle e tentacolare; tutto ciò che ti dà più informazioni su cosa è andato storto e dove potrebbe farti risparmiare ore di scavo.

129
BlairHippo

Esistono diversi problemi con l'utilizzo di riferimenti null nel codice.

Innanzitutto, viene generalmente utilizzato per indicare uno stato speciale . Piuttosto che definire una nuova classe o costante per ogni stato mentre le specializzazioni vengono normalmente eseguite, usando un riferimento null si usa un tipo/valore con perdita (massicciamente generalizzata) .

In secondo luogo, il codice di debug diventa più difficile quando appare un riferimento null e tu cerchi di determinare cosa lo ha generato , quale stato è in vigore e la sua causa anche se tu può tracciare il suo percorso di esecuzione a monte.

In terzo luogo, i riferimenti null introducono percorsi di codice aggiuntivi da testare .

In quarto luogo, una volta che i riferimenti null sono utilizzati come stati validi per i parametri e per i valori di ritorno, la programmazione difensiva (per gli stati causati dalla progettazione) richiede ulteriori controlli dei riferimenti null da eseguire in vari punti ... per ogni evenienza.

In quinto luogo, il runtime della lingua sta già eseguendo dei controlli del tipo quando esegue la ricerca del selettore sulla tabella dei metodi di un oggetto. Quindi stai duplicando lo sforzo controllando se il tipo di oggetto è valido/non valido e quindi il runtime controlla il tipo di oggetto valido per invocare il suo metodo.

Perché non usare modello NullObject per sfruttare il controllo del runtime per farlo invocare NOP metodi specifici per quello stato (conforme all'interfaccia dello stato normale) eliminando al contempo tutti i controlli extra per riferimenti null in tutta la tua base di codice?

comporta più lavoro creando una classe NullObject per ogni interfaccia con cui si desidera rappresentare uno stato speciale. Ma almeno la specializzazione è isolata per ogni stato speciale, piuttosto che per il codice in cui lo stato potrebbe essere presente. IOW, il numero di test è ridotto perché hai meno percorsi di esecuzione alternativi nei tuoi metodi.

50
Huperniketes

I nulli non sono poi così male, a meno che non te li aspetti. dovresti devi specificare esplicitamente nel codice che ti aspetti null, che è un problema di progettazione del linguaggio. Considera questo:

Customer? c = Customer.GetByLastName("Goodman");
// note the question mark -- 'c' is either a Customer or null
if (c != null)
{
    // c is not null, therefore its type in this block is
    // now 'Customer' instead of 'Customer?'
    Console.WriteLine(c.FirstName + " " + c.LastName + " is awesome!");
}
else { Console.WriteLine("There was no customer named Goodman.  How lame!"); }

Se si tenta di invocare metodi su un Customer?, dovresti ricevere un errore di compilazione. Il motivo per cui più lingue non lo fanno (IMO) è che non prevedono che il tipo di una variabile cambi a seconda dell'ambito in cui si trova. Se la lingua è in grado di gestirlo, il problema può essere risolto interamente all'interno del sistema di tipo.

C'è anche il modo funzionale di gestire questo problema, usando i tipi di opzioni e Maybe, ma non ne ho familiarità. Preferisco così perché il codice corretto dovrebbe teoricamente richiedere solo un carattere aggiunto per compilare correttamente.

Ci sono molte risposte eccellenti che coprono gli sfortunati sintomi di null, quindi vorrei presentare un argomento alternativo: Null è un difetto nel sistema dei tipi.

Lo scopo di un sistema di tipi è quello di garantire che i diversi componenti di un programma "si incastrino" correttamente; un programma ben scritto non può "off the Rails" in un comportamento indefinito.

Prendi in considerazione un ipotetico dialetto di Java, o qualunque sia il tuo linguaggio preferito tipicamente, in cui puoi assegnare la stringa "Hello, world!" A qualsiasi variabile di qualsiasi tipo:

Foo foo1 = new Foo();  // Legal
Foo foo2 = "Hello, world!"; // Also legal
Foo foo3 = "Bonjour!"; // Not legal - only "Hello, world!" is allowed

E puoi controllare le variabili in questo modo:

if (foo1 != "Hello, world!") {
    bar(foo1);
} else {
    baz();
}

Non c'è nulla di impossibile in questo - qualcuno potrebbe progettare un linguaggio del genere se lo volesse. Il valore speciale non deve essere "Hello, world!": Potrebbe essere stato il numero 42, la Tupla (1, 4, 9) O, per esempio, null. Ma perché dovresti farlo? Una variabile di tipo Foo dovrebbe contenere solo Foos - questo è l'intero punto del sistema di tipi! null non è un Foo non più di "Hello, world!". Peggio ancora, null non è un valore di qualsiasi tipo e non c'è niente che tu possa fare con esso!

Il programmatore non può mai essere sicuro che una variabile contenga effettivamente un Foo, e nemmeno il programma; per evitare comportamenti indefiniti, deve controllare le variabili per "Hello, world!" prima di usarle come Foos. Nota che eseguire il controllo delle stringhe nello snippet precedente non propaga il fatto che foo1 sia in realtà un Foo - bar probabilmente avrà anche un proprio controllo, solo per sicurezza.

Confrontalo con l'uso di un tipo Maybe/Option con pattern matching:

case maybeFoo of
 |  Just foo => bar(foo)
 |  Nothing => baz()

All'interno della clausola Just foo, Sia tu che il programma siete sicuri che la nostra variabile Maybe Foo Contenga davvero un valore Foo - che le informazioni siano propagate lungo la catena di chiamate e bar non ha bisogno di fare alcun controllo. Poiché Maybe Foo È un tipo distinto da Foo, sei costretto a gestire la possibilità che possa contenere Nothing, quindi non puoi mai essere nascosto da un NullPointerException. Puoi ragionare sul tuo programma molto più facilmente e il compilatore può omettere controlli null sapendo che tutte le variabili di tipo Foo contengono davvero Foos. Tutti vincono.

20
Doval

Un puntatore null è uno strumento

non un nemico. Dovresti solo usarli nel modo giusto.

Pensa solo un minuto al tempo impiegato per trovare e correggere un tipico bug puntatore non valido, rispetto alla localizzazione e correzione di un bug puntatore nullo. È facile controllare un puntatore contro null. È un PITA per verificare se un puntatore non nullo punta a dati validi.

Se non sei ancora convinto, aggiungi una buona dose di multithreading al tuo scenario, quindi ripensaci.

Morale della storia: non gettare i bambini con l'acqua. E non dare la colpa agli strumenti per l'incidente. L'uomo che ha inventato il numero zero molto tempo fa era abbastanza intelligente, da quel giorno non si poteva nominare il "nulla". Il puntatore null non è così lontano da quello.


EDIT: Anche se il modello NullObject sembra essere una soluzione migliore rispetto ai riferimenti che possono essere nulli, introduce problemi da solo:

  • Un riferimento che contiene un NullObject dovrebbe (secondo la teoria) non fare nulla quando viene chiamato un metodo. Pertanto, errori sottili possono essere introdotti a causa di riferimenti non assegnati in modo errato, che ora sono garantiti come non nulli (yehaa!) Ma eseguono un'azione indesiderata: nulla. Con un NPE è quasi ovvio dove si trova il problema. Con un NullObject che si comporta in qualche modo (ma è sbagliato), introduciamo il rischio che errori vengano rilevati (troppo) in ritardo. Questo non è per caso simile a un problema di puntatore non valido e ha conseguenze simili: il riferimento punta a qualcosa, che assomiglia a dati validi, ma non lo è, introduce errori di dati e può essere difficile da rintracciare. Ad essere onesti, in questi casi preferirei senza battere ciglio un NPE che fallisce immediatamente, ora, su un errore logico/di dati che improvvisamente mi colpisce in seguito lungo la strada. Ricordi lo studio IBM sul costo degli errori in funzione della fase in cui vengono rilevati?

  • L'idea di non fare nulla quando viene chiamato un metodo su NullObject non vale, quando viene chiamato un getter di proprietà o una funzione o quando il metodo restituisce valori tramite out. Supponiamo che il valore restituito sia int e decidiamo che "non fare nulla" significa "ritorno 0". Ma come possiamo essere sicuri, che 0 è il "niente" giusto per essere (non) restituito? Dopotutto, il NullObject non dovrebbe fare nulla, ma quando viene chiesto un valore, deve reagire in qualche modo. Fallire non è un'opzione: usiamo NullObject per prevenire l'NPE e sicuramente non lo scambieremo con un'altra eccezione (non implementata, dividere per zero, ...), vero? Quindi, come restituire correttamente nulla quando si deve restituire qualcosa?

Non posso fare a meno, ma quando qualcuno cerca di applicare il modello NullObject a tutti i problemi, sembra più come cercare di riparare un errore facendo un altro. È senza dubbio una buona e utile soluzione in alcuni casi, ma sicuramente non è il proiettile magico per tutti i casi.

15
JensG

(Lanciando il mio cappello sul ring per una vecchia domanda;))

Il problema specifico con null è che interrompe la tipizzazione statica.

Se ho un Thing t, Il compilatore può garantire che posso chiamare t.doSomething(). Bene, UNLESS t è nullo in fase di esecuzione. Ora tutte le scommesse sono spente. Il compilatore ha detto che era OK, ma ho scoperto molto più tardi che t NON in effetti doSomething(). Quindi, anziché essere in grado di fidarmi del compilatore per rilevare errori di tipo, devo attendere fino al momento dell'esecuzione per rilevarli. Potrei anche usare Python!

Quindi, in un certo senso, null introduce la tipizzazione dinamica in un sistema tipizzato staticamente, con risultati attesi.

La differenza tra una divisione per zero o log di negativo, ecc. È che quando i = 0, è ancora un int. Il compilatore può ancora garantire il suo tipo. Il problema è che la logica applica erroneamente quel valore in un modo che non è consentito dalla logica ... ma se la logica lo fa, è praticamente la definizione di un bug.

(Il compilatore può rilevare alcuni di questi problemi, a proposito. Come espressioni di segnalazione come i = 1 / 0 Al momento della compilazione. Ma non puoi davvero aspettarsi che il compilatore segua una funzione e assicurarsi che i parametri siano tutti coerenti con la logica della funzione)

Il problema pratico è che fai molto lavoro extra e aggiungi controlli null per proteggerti in fase di esecuzione, ma cosa succede se ne dimentichi uno? Il compilatore ti impedisce di assegnare:

String s = new Integer (1234);

Quindi perché dovrebbe consentire l'assegnazione a un valore (null) che interromperà i de-riferimenti a s?

Mescolando "nessun valore" con riferimenti "digitati" nel codice, stai caricando ulteriormente i programmatori. E quando si verificano NullPointerExceptions, rintracciarle può richiedere ancora più tempo. Invece di fare affidamento sulla digitazione statica per dire "questo è un riferimento a qualcosa di previsto", stai lasciando che la lingua dica "questo potrebbe anche essere un riferimento a qualcosa di previsto. "

14
Rob

E qual è l'alternativa?

Tipi opzionali e corrispondenza dei motivi. Dato che non conosco C #, ecco un pezzo di codice in un linguaggio immaginario chiamato Scala # :-)

Customer.GetByLastName("Goodman")    // returns Option[Customer]
match
{
    case Some(customer) =>
    Console.WriteLine(customer.FirstName + " " + customer.LastName + " is awesome!");

    case None =>
    Console.WriteLine("There was no customer named Goodman.  How lame!");
}
8
fredoverflow

I riferimenti null sono un errore perché consentono il codice non sensitivo:

foo = null
foo.bar()

Esistono alternative se si utilizza il sistema dei tipi:

Maybe<Foo> foo = null
foo.bar() // error{Maybe<Foo> does not have any bar method}

L'idea generale è quella di mettere la variabile in una scatola, e l'unica cosa che puoi fare è decomprimerla, preferibilmente arruolando l'aiuto del compilatore come proposto per Eiffel.

Haskell ce l'ha da zero (Maybe), in C++ puoi sfruttare boost::optional<T> ma puoi comunque ottenere un comportamento indefinito ...

8
Matthieu M.

Il problema con i null è che i linguaggi che li consentono praticamente ti costringono a programmare difensivamente contro di esso. Ci vuole molto sforzo (molto più che cercare di usare if-block difensivi) per assicurarsi che

  1. gli oggetti che ti aspetti not essere null non sono mai mai null, e
  2. che i tuoi meccanismi difensivi affrontano effettivamente tutti i potenziali NPE in modo efficace.

Quindi, in effetti, i null finiscono per essere una cosa costosa da avere.

6
luis.espinal

Il problema fino a che punto il tuo linguaggio di programmazione tenta di dimostrare la correttezza del tuo programma prima che venga eseguito. In una lingua tipicamente statica dimostri di avere i tipi corretti. Passando al valore predefinito di riferimenti non annullabili (con riferimenti annullabili facoltativi) è possibile eliminare molti dei casi in cui viene passato null e non dovrebbe esserlo. La domanda è se lo sforzo extra nella gestione di riferimenti non annullabili valga il vantaggio in termini di correttezza del programma.

4
Winston Ewert

Il problema non è tanto nullo, è che non è possibile specificare un tipo di riferimento non nullo in molte lingue moderne.

Ad esempio, il tuo codice potrebbe apparire come

public void MakeCake(Egg egg, Flour flour, Milk milk)
{
    if (Egg == null) { throw ... }
    if (flour == null) { throw ... }
    if (milk == null) { throw ... }

    Egg.Crack();
    MixingBowl.Mix(Egg, flour, milk);
    // etc
}

// inside Mixing bowl class
public void Mix(Egg egg, Flour flour, Milk milk)
{
    if (Egg == null) { throw ... }
    if (flour == null) { throw ... }
    if (milk == null) { throw ... }

    //... etc
}

Quando i riferimenti di classe vengono passati in giro, la programmazione difensiva ti incoraggia a controllare nuovamente tutti i parametri per null, anche se li hai controllati per null subito prima, specialmente quando costruisci unità riutilizzabili sotto test. Lo stesso riferimento potrebbe essere facilmente verificato per null 10 volte attraverso la base di codice!

Non sarebbe meglio se si potesse avere un tipo nullable normale quando si ottiene la cosa, si tratta di null allora e lì, quindi passarlo come tipo non nullable a tutte le funzioni e le classi del piccolo aiutante senza controllare null tutto tempo?

Questo è il punto della soluzione Option: non consentire all'utente di attivare gli stati di errore, ma consentire la progettazione di funzioni che implicitamente non possono accettare argomenti nulli. Se l'implementazione "predefinita" dei tipi è nulla o non annullabile è meno importante della flessibilità di avere entrambi gli strumenti disponibili.

http://twistedoakstudios.com/blog/Post330_non-nullable-types-vs-c-fixing-the-billion-dollar-mistake

Questo è anche elencato come la funzione più votata n. 2 per C # -> https://visualstudio.uservoice.com/forums/121579-visual-studio/category/30931-languages-c

4
Michael Parker

Null è male. Tuttavia, la mancanza di un null può essere un male maggiore.

Il problema è che nel mondo reale hai spesso una situazione in cui non hai dati. Il tuo esempio di versione senza null può ancora esplodere: o hai fatto un errore logico e hai dimenticato di controllare Goodman o forse Goodman si è sposato tra quando hai controllato e quando l'hai cercata. (Aiuta a valutare la logica se pensi che Moriarty stia guardando ogni parte del tuo codice e cerchi di farti inciampare.)

Cosa fa la ricerca di Goodman quando non c'è? Senza un null devi restituire una sorta di cliente predefinito - e ora stai vendendo roba a quel valore predefinito.

Fondamentalmente, dipende dal fatto che sia più importante che il codice funzioni indipendentemente da cosa o che funzioni correttamente. Se un comportamento improprio è preferibile a nessun comportamento, non si desidera null. (Per un esempio di come questa potrebbe essere la scelta giusta, prendere in considerazione il primo lancio di Ariane V. Un errore non rilevato/0 ha provocato una brusca virata del razzo e l'autodistruzione ha sparato quando si è rotto a causa di questo. Il valore stava provando a calcolare in realtà non serviva più a nulla nell'istante in cui il booster era acceso - avrebbe fatto orbita nonostante la routine che restituiva immondizia.)

Almeno 99 volte su 100 sceglierei di utilizzare i valori null. Vorrei saltare di gioia per notare la versione di sé di loro, però.

3
Loren Pechtel

Ottimizza per il caso più comune.

Dover controllare sempre null è noioso: vuoi essere in grado di ottenere l'oggetto del cliente e lavorarci sopra.

Nel caso normale, questo dovrebbe funzionare bene - fai lo sguardo, ottieni l'oggetto e lo usi.

Nel caso eccezionale, dove stai (casualmente) cerchi un cliente per nome, non sapendo se quel record/oggetto esiste, avresti bisogno di qualche indicazione che questo non è riuscito. In questa situazione, la risposta è lanciare un'eccezione RecordNotFound (o lasciare che il provider SQL sottostante lo faccia per te).

Se ti trovi in ​​una situazione in cui non sai se puoi fidarti dei dati che arrivano (il parametro), forse perché sono stati inseriti da un utente, allora puoi anche fornire il "TryGetCustomer (nome, cliente esterno)" modello. Riferimenti incrociati con int.Parse e int. TryParse.

1
JBRWilkinson

Le eccezioni non sono valutate!

Sono lì per un motivo. Se il tuo codice sta funzionando male, c'è un modello geniale, chiamato eccezione, e ti dice che qualcosa non va.

Evitando di usare oggetti null nascondi parte di tali eccezioni. Non sto parlando dell'esempio di OP in cui converte l'eccezione del puntatore null in un'eccezione ben tipizzata, questo potrebbe effettivamente essere una buona cosa poiché aumenta la leggibilità. Tuttavia, quando prendi la soluzione Tipo di opzione come sottolineato da @Jonas, nascondi le eccezioni.

Che nella tua applicazione di produzione, invece dell'eccezione da lanciare quando fai clic sul pulsante per selezionare un'opzione vuota, non succede nulla. Invece dell'eccezione del puntatore null verrebbe generata e probabilmente avremmo un rapporto di quell'eccezione (come in molte applicazioni di produzione abbiamo un tale meccanismo) e di quanto potremmo effettivamente risolverlo.

Rendere il tuo codice a prova di proiettile evitando l'eccezione è una cattiva idea, renderti a prova di proiettile fissando l'eccezione, questo è il percorso che sceglierei.

0
Ilya Gazman

No.

L'incapacità di una lingua di considerare un valore speciale come null è il problema della lingua. Se guardi lingue come Kotlin o Swift , che costringono lo scrittore a gestire esplicitamente la possibilità di un valore nullo ad ogni incontro, quindi non c'è pericolo.

In lingue come quella in questione, la lingua consente a null di essere un valore qualsiasi-ish digitare, ma non fornisce alcun costrutto per riconoscerlo in seguito, il che porta a cose inaspettatamente null (specialmente quando passate da qualche altra parte), e quindi si verificano arresti anomali. Questo è il male: lasciarti usare null senza farti gestire.

0
Ben Leggiero

Nulls sono problematici perché devono essere controllati esplicitamente, ma il compilatore non è in grado di avvisarti che hai dimenticato di controllarli. Solo un'analisi statica che richiede tempo può dirlo. Fortunatamente, ci sono diverse buone alternative.

Porta la variabile fuori dal campo di applicazione. Troppo spesso, null viene usato come segnaposto quando un programmatore dichiara una variabile troppo presto o la mantiene troppo lunga. L'approccio migliore è semplicemente quello di sbarazzarsi della variabile. Non dichiararlo fino a quando non hai un valore valido da inserire. Questa non è una restrizione così difficile come potresti pensare e rende il tuo codice molto più pulito.

Usa il modello di oggetti null. A volte, un valore mancante è uno stato valido per il tuo sistema. Il modello di oggetti null è una buona soluzione qui. Evita la necessità di controlli espliciti costanti, ma consente comunque di rappresentare uno stato null. Non si tratta di "documentare una condizione di errore", come affermano alcune persone, perché in questo caso uno stato null è uno stato semantico valido. Detto questo, non dovresti usare questo modello quando uno stato null non è uno stato semantico valido. Dovresti semplicemente togliere la tua portata dall'ambito di applicazione.

Usa un'opzione Forse /. Prima di tutto, questo consente al compilatore di avvisarti che devi verificare la presenza di un valore mancante, ma fa molto più che sostituire un tipo di controllo esplicito con un altro. Usando Options, puoi concatenare il tuo codice e continuare come se il tuo valore esistesse, senza la necessità di controllare fino all'ultimo minuto assoluto. In Scala, il tuo codice di esempio sarebbe simile a:

val customer = customerDb getByLastName "Goodman"
val awesomeMessage =
  customer map (c => s"${c.firstName} ${c.lastName} is awesome!")
val notFoundMessage = "There was no customer named Goodman.  How lame!"
println(awesomeMessage getOrElse notFoundMessage)

Nella seconda riga, in cui stiamo creando questo fantastico messaggio, non controlliamo esplicitamente se il cliente è stato trovato e la bellezza è che non è necessario . Non è fino all'ultima riga, che potrebbe essere più tardi, o anche in un modulo diverso, in cui ci preoccupiamo esplicitamente di ciò che accade se Option è None. Non solo, se avessimo dimenticato di farlo, non avrebbe controllato il tipo. Anche allora, è fatto in modo molto naturale, senza un'istruzione esplicita if.

Contrastalo con un null, dove digita bene i controlli, ma dove devi fare un controllo esplicito su ogni passaggio o l'intera applicazione esplode in fase di esecuzione, se sei fortunato durante un uso nel caso in cui il test dell'unità si eserciti. Non vale la seccatura.

0
Karl Bielefeldt

Il problema centrale di NULL è che rende il sistema inaffidabile. Nel 1980 Tony Hoare nel saggio dedicato al suo Turing Award scrisse:

E così, il migliore dei miei consigli agli autori e ai progettisti di ADA è stato ignorato. .... Non permettere che questo linguaggio allo stato attuale sia utilizzato in applicazioni in cui l'affidabilità è fondamentale, ad esempio centrali nucleari, missili da crociera, sistemi di allarme rapido, sistemi di difesa antimissile contro i missili. Il prossimo razzo che andrà fuori strada a causa di un errore del linguaggio di programmazione potrebbe non essere un razzo spaziale esplorativo in un innocuo viaggio a Venere: potrebbe essere una testata nucleare che esplode su una delle nostre città. Un linguaggio di programmazione inaffidabile che genera programmi inaffidabili costituisce un rischio molto maggiore per il nostro ambiente e la nostra società rispetto alle automobili non sicure, ai pesticidi tossici o agli incidenti nelle centrali nucleari. Siate vigili per ridurre il rischio, non per aumentarlo.

Il linguaggio ADA è cambiato molto da allora, tuttavia esistono ancora problemi di questo tipo in Java, C # e molte altre lingue popolari.

È dovere dello sviluppatore creare contratti tra un cliente e un fornitore. Ad esempio, in C #, come in Java, è possibile utilizzare Generics per ridurre al minimo l'impatto del riferimento Null creando di sola lettura NullableClass<T> (due opzioni):

class NullableClass<T>
{
     public HasValue {get;}
     public T Value {get;}
}

e poi usalo come

NullableClass<Customer> customer = dbRepository.GetCustomer('Mr. Smith');
if(customer.HasValue){
  // one logic with customer.Value
}else{
  // another logic
} 

oppure usa due opzioni di stile con i metodi di estensione C #:

customer.Do(
      // code with normal behaviour
      ,
      // what to do in case of null
) 

La differenza è significativa Come cliente di un metodo sai cosa aspettarti. Una squadra può avere la regola:

Se una classe non è di tipo NullableClass, la sua istanza non deve essere nulla .

Il team può rafforzare questa idea utilizzando Design by Contract e il controllo statico al momento della compilazione, ad esempio con il presupposto:

function SaveCustomer([NotNullAttribute]Customer customer){
     // there is no need to check whether customer is null 
     // it is a client problem, not this supplier
}

o per una stringa

function GetCustomer([NotNullAndNotEmptyAttribute]String customerName){
     // there is no need to check whether customerName is null or empty 
     // it is a client problem, not this supplier
}

Questo approccio può aumentare drasticamente affidabilità dell'applicazione e qualità del software. Design by Contract è un caso di Hoare logic , che è stato popolato da Bertrand Meyer nel suo famoso libro sulla costruzione di software orientato agli oggetti e il linguaggio Eiffel nel 1988, ma non è utilizzato in modo non valido nella moderna produzione di software.

0
Artru