Vantaggio di generare eccezioni per i parametri null

1

Il codice base con cui lavoro ha un certo schema prevalente nei tutti metodi pubblici, che vanno così:

public void UpdateUser(User userArg)
{
    Framework.NullCheck(userArg);

    var user = userDb.Get(userArg.Id);

    Framework.NullCheck(user);

    user = userArg;

    userDb.Update(user);
}

Il metodo NullCheck è generico e controllerà se l'oggetto è uguale a null . Se lo fa, viene lanciato un NullReferenceException con un messaggio generico.

Non ho mai pensato di chiedere ai miei colleghi quali sono i vantaggi di farlo.

Considerando che questo codice è in esecuzione all'interno di un'applicazione in cui tutti gli input dell'utente sono disinfettati (compresi i controlli null ) sia lato client che lato server non appena ricevuto, e sono memorizzati in un database con i vincoli corretti su null, fare un controllo come questo in ogni metodo sembra eccessivo. Per non parlare aggiunge solo un po 'più di tempo in più durante la creazione di un nuovo metodo, che tutto sommato.

    
posta Frayt 02.10.2016 - 17:14
fonte

3 risposte

4

Non penso che la domanda collegata abbia una risposta che affronta il tuo caso specifico quindi farò inserisci un'altra risposta qui.

Lo snippet di codice che hai mostrato contiene due chiamate a Framework.NullCheck con uno scopo molto diverso.

Parametri

Il primo convalida un parametro. Dobbiamo testarlo? La prima domanda qui dovrebbe essere ciò che dice il contratto della funzione UpdateUser . Non hai mostrato la sua documentazione quindi non lo so.

Interfacce con contratti estesi

Se la documentazione dice

If userArg is null, a NullPointerException is thrown and the function has no effect.

allora sì, devi assolutamente controllarlo. Diciamo che una tale interfaccia ha un ampio contratto . Bene, in questo caso, il controllo è effettivamente ridondante perché se userArg == null allora la prima riga di codice

var user = userDb.Get(userArg.Id);

farà esattamente ciò che il contratto pubblicizza perché la JVM valuterà per prima cosa i parametri della funzione e la valutazione di userArg.Id è garantita per innescare un NullPointerException senza effetti collaterali. Potresti ancora obiettare che il controllo esplicito è più pulito perché rende più ovvio e se modifichi l'implementazione, è meno probabile che tu interrompa accidentalmente il tuo contratto.

Interfacce con contratti ristretti

Se, d'altra parte, la documentazione dice

userArg must not be null.

o

If userArg is null, the behavior is undefined.

o semplicemente non dice nulla 1 , quindi chiama UpdateUser con null come userArg è una violazione del contratto e la tua implementazione può fare letteralmente qualsiasi cosa. "Tutto" ovviamente include throw in NullPointerException senza avere effetti collaterali. E farlo potrebbe essere una buona idea in termini di programmazione difensiva. Tuttavia, non vorrei ottimizzare per il caso di errore. Supponiamo che la maggior parte delle volte la tua funzione venga chiamata con argomenti validi. (Se questo non è vero, c'è qualcos'altro di cui preoccuparsi di più.) Nel raro caso in cui è chiamato fuori contratto, prima si fa un po 'di lavoro e poi fallire va bene. Soprattutto se "qualche lavoro" non ha effetti collaterali visibili.

Valori di ritorno

Il secondo controllo nel codice è molto diverso. Qui, stai verificando che userDb.Get(userArg.Id); non return null . Questo è strano.

O la documentazione di Get dice che non return null , quindi puoi fare affidamento su di esso e non devi controllare, o dice che potrebbe return null sotto alcune circostanze 2 , quindi semplicemente throw in NullPointerException probabilmente non è la reazione giusta.

Conclusione

Vorrei mantenere quei controlli per i parametri in ogni funzione che ha un contratto ampio, anche se potrebbe non essere strettamente necessario, solo perché rende il codice più ovvio.

Nelle funzioni con un contratto ristretto, utilizzerei null con parsimonia quando l'overhead delle prestazioni sembra accettabile e la funzione farebbe cose potenzialmente dannose (effetti collaterali) prima di fallire comunque. Probabilmente preferirei usare le dichiarazioni assert in questo caso, comunque. Ciò rende più chiaro che stai effettuando un controllo difensivo che verrà attivato solo in caso di violazione del contratto.

Mi sbarazzerei di tutti i controlli per i valori di return .

1 Se non si dice nulla su un parametro implica che non deve essere null potrebbe essere aperto al dibattito. Poiché null è raramente un valore valido, preferisco dichiarare esplicitamente dove è consentito e altrimenti rimanere in silenzio. Però, dovresti consultare le convenzioni del tuo progetto su questo.

2 Ad esempio, potrebbe utilizzare return value null per indicare che la voce richiesta non è stata trovata nel database. Questa potrebbe non essere la migliore scelta di design, ma tale codice esiste nella realtà.

    
risposta data 02.10.2016 - 17:57
fonte
0

Il buon vecchio detto dice "Crash precoce, crash spesso" .

Rende molto più facile il debug per ottenere un feedback immediato sui problemi. Se il codice base non è pieno di documentazione ben documentato, tali controlli renderanno immediatamente chiaro al lettore cosa ci si aspetta.

Si potrebbe argomentare se le asserzioni non sarebbero una scelta migliore - ma la risposta potrebbe essere "dipende".

Tuttavia, il controllo dei valori di ritorno di solito mi sembra sbagliato. In genere la funzione chiamata dovrebbe già aver generato un'eccezione. Ma questo dipende molto dal contesto: potrebbe essere perfettamente corretto per una funzione di ricerca utente restituire null per qualsiasi nome utente non esistente, ma in quello specifico scenario si dovrebbe garantire un utente valido e la non esistenza indica un errore ...

    
risposta data 02.10.2016 - 21:10
fonte
0

Il problema non è che controlli esplicitamente qualcosa per null.

Il problema che hai è:

The NullCheck method is generic and will check if the object equals null. If it does, a NullReferenceException is thrown with a generic message.

IFF che controlli esplicitamente, getti ArgumentNullException per il parametro con il nome del metodo e il parametro.

Nel caso del secondo NullCheck , dal momento che hai scritto

To clarify: Yes, null will be returned from userDb.Get if there is no matching entry in the database.

lanciare un NullRefExc qui è davvero orribile - per lo meno lanciare un InvalidOperationException che indica che ID non è stato trovato nel DB e che il passaggio di ID non validi è un errore per questo metodo specifico.

    
risposta data 11.10.2016 - 10:47
fonte

Leggi altre domande sui tag