puntatori nulli contro il modello oggetto nullo

25

Attribuzione: è nata da una relativa domanda P.SE

Il mio background è in C / C ++, ma ho lavorato molto in Java e sto attualmente codificando C #. A causa del mio background in C, controllare i puntatori passati e restituiti è di seconda mano, ma riconosco che distorce il mio punto di vista.

Recentemente ho visto la menzione del Pattern di oggetto Null in cui l'idea è che un oggetto sia sempre restituito. Il caso normale restituisce l'oggetto popolato previsto e il caso di errore restituisce un oggetto vuoto invece di un puntatore nullo. La premessa è che la funzione chiamante avrà sempre una sorta di oggetto per accedere e quindi evitare violazioni della memoria ad accesso nullo.

  • Quindi quali sono i pro / contro di un controllo nullo rispetto all'utilizzo del modello di oggetto nullo?

Riesco a vedere codice chiamante più pulito con il NOP, ma posso anche vedere dove creerebbe errori nascosti che altrimenti non vengono sollevati. Preferirei che la mia applicazione venisse a mancare (anche se è un'eccezione) durante lo sviluppo piuttosto che far scappare l'errore in silenzio.

  • Non è possibile che il modello oggetto nullo abbia problemi simili in quanto non esegue un controllo Null?

Molti degli oggetti con cui ho lavorato contengono oggetti o contenitori propri. Sembra che dovrei avere un caso speciale per garantire che tutti i contenitori dell'oggetto principale avessero oggetti vuoti. Sembra che questo potrebbe diventare brutto con più livelli di nidificazione.

    
posta GlenH7 08.06.2012 - 20:10
fonte

5 risposte

22

Non si userebbe un Pattern di oggetto Nullo in luoghi dove viene restituito null (o Null Object) perché c'è stato un errore catastrofico. In quei posti continuerei a restituire null. In alcuni casi, se non è possibile eseguire il ripristino, è possibile che si verifichi un arresto anomalo perché almeno il crash dump indicherà esattamente dove si è verificato il problema. In questi casi, quando aggiungi la tua gestione degli errori, stai ancora per uccidere il processo (di nuovo, ho detto per i casi in cui non c'è recupero), ma la tua gestione degli errori maschererà le informazioni molto importanti che sarebbero state fornite da un crash dump. / p>

Il pattern a oggetti nulli è più per i luoghi in cui è possibile adottare un comportamento predefinito in un caso in cui l'oggetto non viene trovato. Ad esempio, considera quanto segue:

User* pUser = GetUser( "Bob" );

if( pUser )
{
    pUser->SetAddress( "123 Fake St." );
}

Se usi NOP, dovresti scrivere:

GetUser( "Bob" )->SetAddress( "123 Fake St." );

Si noti che il comportamento di questo codice è "se Bob esiste, voglio aggiornare il suo indirizzo". Ovviamente se la tua applicazione richiede che Bob sia presente, non vuoi riuscire silenziosamente. Ma ci sono casi in cui questo tipo di comportamento sarebbe appropriato. E in questi casi, NOP non produce un codice molto più pulito e conciso?

In luoghi in cui davvero non puoi vivere senza Bob, vorrei che GetUser () generasse un'eccezione dell'applicazione (cioè non una violazione di accesso o qualcosa di simile) che sarebbe gestita a un livello più alto e avrebbe segnalato un errore generale di funzionamento. In questo caso, non è necessario NOP, ma non è inoltre necessario verificare esplicitamente NULL. IMO, quei controlli per NULL, rendono solo il codice più grande e tolgono alla leggibilità. Verifica che NULL sia ancora la scelta di design giusta per alcune interfacce, ma non così tante come alcune persone tendono a pensare.

    
risposta data 08.06.2012 - 20:52
fonte
7

So what are the pros / cons of a null check versus using the Null Object Pattern?

Pro

  • Un controllo nullo è migliore poiché risolve più casi. Non tutti gli oggetti hanno un comportamento predefinito o no-op.
  • Un controllo Null è più solido. Anche gli oggetti con impostazioni predefinite sane vengono utilizzati in luoghi in cui l'impostazione predefinita non è valida. Il codice dovrebbe fallire vicino alla causa principale se sta per fallire. Il codice dovrebbe fallire, ovviamente, se fallirà.

Contro

  • Le impostazioni predefinite sane di solito danno come risultato un codice più pulito.
  • Le impostazioni predefinite sane di solito provocano meno errori catastrofici se riescono a entrare in libertà.

Quest'ultimo "pro" è il principale elemento di differenziazione (nella mia esperienza) su quando ciascuno dovrebbe essere applicato. "L'errore dovrebbe essere rumoroso?". In alcuni casi, vuoi che il fallimento sia duro e immediato; se qualche scenario che non dovrebbe mai accadere in qualche modo lo fa. Se una risorsa vitale non è stata trovata ... ecc. In alcuni casi, si sta bene con un predefinito corretto poiché non è proprio un errore : ottenere un valore da un dizionario, ma la chiave è manca per esempio.

Come qualsiasi altra decisione sul design, ci sono aspetti positivi e negativi a seconda delle tue esigenze.

    
risposta data 08.06.2012 - 22:00
fonte
5

Non sono una buona fonte di informazioni obiettive, ma soggettivamente:

  • Non voglio che il mio sistema muoia, mai; per me è un segno di un sistema mal progettato o incompleto; il sistema completo dovrebbe gestire tutti i possibili casi e non entrare mai in uno stato imprevisto; per ottenere questo ho bisogno di essere esplicito nel modellare tutti i flussi e le situazioni; l'utilizzo di null non è in alcun modo esplicito e raggruppa molti casi diversi in un unico caso; Preferisco l'oggetto NonExistingUser a null, preferisco NaN a null, ... un tale approccio mi consente di essere esplicito riguardo a quelle situazioni e la loro gestione in tutti i dettagli che voglio, mentre null mi lascia con solo un'opzione nulla; in tale ambiente come Java qualsiasi oggetto può essere nullo quindi perché preferiresti nascondere in quel pool generico di casi anche il tuo caso specifico?
  • le implementazioni nulle hanno un comportamento drasticamente diverso rispetto all'oggetto e sono per lo più incollate all'insieme di tutti gli oggetti (perché? perché? perché?); per me una tale drastica differenza sembra un risultato del design dei null come indicazione dell'errore, nel qual caso il sistema molto probabilmente morirà (sono supplicato che così tante persone preferiscano effettivamente che il loro sistema muoia nei post sopra) a meno che non venga gestito esplicitamente; per me questo è un approccio imperfetto nella sua radice - permette al sistema di morire di default a meno che tu non ti abbia esplicitamente preso cura di questo, che non è molto sicuro, giusto? ma in ogni caso rende impossibile scrivere un codice che consideri il valore null e object nello stesso modo - funzioneranno solo pochi operatori di base incorporati (assign, equal, param, ecc.), tutti gli altri codici falliranno e sarà necessario due percorsi completamente diversi;
  • utilizzando l'oggetto Null si adatta meglio - puoi inserire tutte le informazioni e la struttura necessarie; NonExistingUser è un ottimo esempio: può contenere un'email dell'utente che hai provato a ricevere e suggerire di creare un nuovo utente in base a tali informazioni, mentre con la soluzione null dovrei pensare a come mantenere l'email a cui le persone hanno tentato di accedere vicino alla gestione dei risultati nulli;

In ambiente come Java o C # non ti darà il vantaggio principale di explicitness - la sicurezza della soluzione è completa, perché puoi ancora ricevere null al posto di qualsiasi oggetto nel sistema e Java non ha mezzi (eccetto il custom annotazioni per Fagioli, ecc.) per proteggerti da questo, tranne se espliciti. Ma nel sistema libero da implementazioni nulle, l'approssimarsi della soluzione del problema in tal modo (con oggetti che rappresentano casi eccezionali) offre tutti i vantaggi menzionati. Quindi prova a leggere qualcosa di diverso dalle lingue tradizionali e ti troverai a cambiare i tuoi modi di codifica.

In generale Null / Error objects / return types è considerato una strategia di gestione degli errori molto solida e abbastanza matematicamente solida, mentre le eccezioni che gestiscono la strategia di Java o C vanno come un solo passo sopra la strategia "die ASAP", quindi in pratica lascia tutto l'onere di instabilità e imprevedibilità del sistema su sviluppatore o manutentore.

Ci sono anche monadi che possono essere considerate superiori a questa strategia (anche superiori nella complessità della comprensione all'inizio), ma quelle riguardano più i casi in cui è necessario modellare aspetti esterni del tipo, mentre gli oggetti Null sono aspetti interni. Quindi NonExistingUser è probabilmente meglio modellato come monad_existing.

E infine non penso che ci sia alcuna connessione tra il modello Oggetto Nullo e la gestione silenziosa delle situazioni di errore. Dovrebbe essere un altro modo: dovrebbe costringerti a modellare ogni singolo caso di errore in modo esplicito e gestirlo di conseguenza. Ora, naturalmente, potresti decidere di essere sciatto (per qualsiasi motivo) e saltare la gestione del caso di errore in modo esplicito, ma gestirlo in modo generico - beh quella è stata una tua scelta esplicita.

    
risposta data 10.09.2012 - 16:04
fonte
3

Penso che sia interessante notare che la redditività di un oggetto Null sembra essere leggermente diversa a seconda che la tua lingua sia scritta in modo statico o meno. Ruby è un linguaggio che implementa un oggetto per Null (o Nil nella terminologia della lingua).

Invece di recuperare un puntatore alla memoria non inizializzata, la lingua restituirà l'oggetto Nil . Ciò è facilitato dal fatto che la lingua è tipizzata dinamicamente. Non è garantito che una funzione restituisca sempre int . Può restituire Nil se è ciò che deve fare. Diventa più complicato e difficile da fare in un linguaggio tipizzato staticamente, perché naturalmente si aspetta che null sia applicabile a qualsiasi oggetto che possa essere chiamato per riferimento. Dovresti iniziare a implementare versioni nulle di qualsiasi oggetto che potrebbe essere nullo (o andare alla Scala / Haskell e avere una sorta di wrapper "Forse").

MrLister ha sollevato il fatto che in C ++ non è garantito avere un riferimento / puntatore di inizializzazione quando lo crei per la prima volta. Avendo un oggetto nullo dedicato invece di un puntatore nullo non elaborato, è possibile ottenere alcune funzionalità aggiuntive. Nil di Ruby include una funzione to_s (toString) che risulta in un testo vuoto. Questo è ottimo se non ti dispiace ottenere un ritorno nullo su una stringa e vuoi saltare il rapporto senza crash. Le Open Class consentono inoltre di sovrascrivere manualmente l'output di Nil se necessario.

Certo, tutto questo può essere preso con un pizzico di sale. Credo che in un linguaggio dinamico, l'oggetto nullo può essere una grande comodità se usato correttamente. Sfortunatamente, ottenere il massimo da esso potrebbe richiedere la modifica della sua funzionalità di base, che potrebbe rendere difficile la comprensione e la manutenzione del tuo programma per le persone abituate a lavorare con i suoi moduli più standard.

    
risposta data 10.09.2012 - 16:32
fonte
1

Per alcuni punti di vista aggiuntivi, dai un'occhiata a Objective-C, dove invece di avere un oggetto Null il valore nil kind-di funziona come un oggetto. Qualsiasi messaggio inviato a un oggetto nil non ha alcun effetto e restituisce un valore di 0 / 0.0 / NO (false) / NULL / nil, indipendentemente dal tipo di valore restituito del metodo. Questo è usato come un modello molto pervasivo nella programmazione MacOS X e iOS, e solitamente i metodi saranno progettati in modo che un oggetto nullo che restituisce 0 sia un risultato ragionevole.

Ad esempio, se vuoi verificare se una stringa ha uno o più caratteri, puoi scrivere

aString.length > 0

e ottieni un risultato ragionevole se aString == nil, perché una stringa non esistente non contiene caratteri. Le persone tendono a prendersi un po 'di tempo per abituarsi, ma probabilmente mi ci vorrà un po' per abituarmi al controllo di valori nulli ovunque in altre lingue.

(C'è anche un oggetto singleton della classe NSNull che si comporta in modo abbastanza diverso e non viene usato molto nella pratica).

    
risposta data 02.05.2014 - 18:13
fonte

Leggi altre domande sui tag