Il servizio dovrebbe generare eccezioni o restituire quando non sono stati specificati elementi per l'eliminazione


12

Ho un pezzo di codice che può essere rappresentato come:

public class ItemService {

    public void DeleteItems(IEnumerable<Item> items)
    {
        // Save us from possible NullReferenceException below.
        if(items == null)
            return;

        foreach(var item in items)
        {
            // For the purpose of this example, lets say I have to iterate over them.
            // Go to database and delete them.
        }
    }
}

Ora mi chiedo se questo è l'approccio giusto o devo gettare un'eccezione. Posso evitare l'eccezione, perché il ritorno sarebbe lo stesso di un'iterazione su una raccolta vuota, il che significa che nessun codice importante viene eseguito comunque, ma d'altra parte probabilmente nascondo problemi da qualche parte nel codice, perché perché qualcuno dovrebbe voler chiamare DeleteItemscon nullparametro? Ciò può indicare che c'è un problema da qualche altra parte nel codice.

Questo è un problema che di solito ho con i metodi nei servizi, perché la maggior parte di essi fa qualcosa e non restituisce un risultato, quindi se qualcuno passa informazioni non valide non c'è nulla da fare per il servizio, quindi ritorna.


2
Questa è solo la mia opinione personale, ma ho sempre trovato sensato che un metodo dovrebbe generare un'eccezione quando fa qualcosa di non intenzionale (eccezionale). Nel tuo caso, lancerei un InvalidOperationException se qualcuno provasse a eliminare gli elementi null / 0.
Falgantil,


1
@gnat La mia domanda riguarda in particolare i servizi, a causa del modo in cui vengono utilizzati e del loro scopo. Questo "duplicato" parla solo dei casi generali e la risposta principale si contraddice anche nel contesto del mio metodo esatto.
FCin

2
È possibile che l'enumerabile sia vuoto anziché null? La gestiresti diversamente?
JAD

13
@Falgantil Vedo null essere degno di un'eccezione, ma penso che sia assurdo lanciare un'eccezione in un elenco vuoto.
Kevin,

Risposte:


58

Queste sono due domande diverse.

Dovresti accettare null? Dipende dalla vostra politica generale circa nullnella base di codice. A mio avviso, vietare nullovunque, tranne dove esplicitamente documentato, è un'ottima pratica, ma è ancora meglio attenersi alla convenzione che già ha la base di codici.

Dovresti accettare la raccolta vuota? Secondo me: SÌ, assolutamente. È molto più difficile limitare tutti i chiamanti a raccolte non vuote che fare la cosa matematicamente giusta - anche se sorprende alcuni sviluppatori che sono incerti sul concetto di zero.


9
In ogni caso, se hai intenzione di lanciare, allora non c'è molta utilità nel lanciare AhaINoticedYouPassingInNullExceptionquando il runtime ti fornisce già la possibilità di lanciare NullReferenceExceptionper te, senza che tu scriva alcun codice. C'è qualche utilità (ad es. Il tuo strumento di copertura del codice ti dirà se hai un unit test per passare in null nel primo caso ma non nel secondo), ma nessuna eccezione dovrebbe essere provata / catturata ad ogni chiamata al servizio, perché di solito è un errore del programmatore.
Steve Jessop,

2
... dovresti rilevare immediatamente le eccezioni sollevate a causa di parametri imprecisi, se sai che ciò che stai passando è nullo in alcuni casi previsti e desideri attivamente che la funzione che stai chiamando lo convalidi per te. Come dice Kilian nella risposta, la tua politica generale potrebbe essere quella di vietare il nulla ovunque non sia esplicitamente consentito. Quindi non verrai catturato NullReferenceExceptiono da AhaINoticedYouPassingInNullExceptionnessuna parte se non con un codice di ripristino di emergenza di alto livello. E quel gestore delle eccezioni dovrebbe probabilmente creare una segnalazione di bug :-)
Steve Jessop,

2
@FCin: il design dell'interfaccia dovrebbe corrispondere a ciò di cui gli utenti hanno effettivamente bisogno al di fuori del servizio. Quindi, se ogni chiamante proverà a provare / aggirare questo, allora questo metodo o qualche metodo di praticità a fianco dovrebbe verificare e ignorare null, perché questo è ciò che il tuo pubblico richiede. Tuttavia, come dice Kilian, di solito funziona meglio nel trattare i null di propagazione come un errore di programmazione e non provare a continuare su ogni sito di chiamata. Del resto, cosa succede se il servizio su cui viene chiamato questo metodo è null: i controller contengono un boilerplate per catturare e continuare anche in quel caso?
Steve Jessop,

2
... se è così, allora sembra che la base di codice stia costantemente trattando i componenti mancanti come una condizione prevista che dovresti gestire a basso livello e continuare. Si può ragionevolmente chiedere, "di chi è la responsabilità di fornire un servizio e un elenco numeroso affinché questa operazione possa andare avanti?". Se la risposta è "qualcuno", la tua funzione sta verificando le condizioni preliminari e il risultato di una condizione preliminare fallita normalmente sarebbe un'eccezione rilevata ad alto livello, non ad ogni chiamata. Se le cose richieste per l'operazione sono veramente opzionali, la tua funzione sta gestendo un caso d'uso previsto .
Steve Jessop,

2
Lanciare esplicitamente un ArgumentNullExceptionè superiore a consentire al codice interno di lanciare un NullReferenceException- puramente guardando il messaggio di eccezione è ovvio che si tratta di un errore di input piuttosto che di un errore logico nel corpo del metodo nel sito di lancio, e garantire l'uscita anticipata significa che è più probabile che tu abbia lasciato altri dati in uno stato valido piuttosto che parzialmente mutati da un null imprevisto in seguito.
Miral,

9

Valore nullo

Come già detto da @KilianFoth, attenersi alla politica generale. Se si tratta nulldi una "scorciatoia" per un elenco vuoto, farlo in questo modo.

Se non disponi di una politica coerente in materia di nullvalori, ti consigliamo di seguire la seguente:

nulldovrebbe essere riservato per rappresentare situazioni che non possono essere espresse dal tipo "normale", ad esempio usando nullper rappresentare "Non lo so". E questa è una buona scelta, poiché tutti coloro che cercano di usare questo valore con noncuranza otterranno un'eccezione, che è la cosa giusta.

L'uso nullcome scorciatoia per un elenco vuoto non si qualifica in questo modo, poiché esiste già una rappresentazione perfetta, essendo un elenco con zero elementi. Ed è una scelta tecnicamente sbagliata, poiché obbliga ogni parte del codice a gestire elenchi per verificare la stenografia valida null.

Elenco vuoto

Per un DeleteItems()metodo, passare un elenco vuoto significa effettivamente non fare nulla. Lo permetterei come argomento, non gettando un'eccezione, ma solo tornando rapidamente.

Naturalmente, il chiamante potrebbe prima verificare la presenza di zero elementi e saltare la DeleteItems()chiamata in quel caso. Se stiamo parlando di un'API Web, per motivi di efficienza il chiamante dovrebbe effettivamente farlo per evitare il traffico non necessario e le latenze di andata e ritorno. Ma non credo che la tua API dovrebbe applicarlo.


1

Generare un'eccezione e gestire i valori null nel codice chiamante.

Come regola di progettazione, cerca di evitare null come valori di parametro. Ridurrà NullPointerExceptions in generale, poiché i null saranno davvero un'eccezione.

Oltre a ciò, guarda il resto del tuo codice. Se questo è un modello comune nel tuo progetto, resta coerente.


Sono nel bel mezzo di "modellare" come sono strutturati i miei servizi, quindi un po 'di refactoring potrebbe essere richiesto per fornire input non validi, ma non molto. Ma sono d'accordo con il tuo punto sul fatto che i null diventeranno un'eccezione una volta che inizierò a lanciare invece di nasconderli.
FCin

0

In generale, il lancio di eccezioni dovrebbe essere riservato a situazioni eccezionali, vale a dire se il codice non ha una linea di condotta ragionevole da intraprendere nel contesto attuale.

Puoi applicare quel processo mentale a questa situazione. Dato che questa è una classe pubblica con un metodo pubblico, hai un'API pubblica e in teoria nessun controllo su ciò che le viene passato.

Il tuo codice non ha alcun contesto su ciò che lo chiama (come non dovrebbe) e non c'è nulla che tu possa ragionevolmente fare con un valore nullo. Questo sarebbe certamente un candidato per un ArgumentNullException.


"se il codice non ha azioni ragionevoli da intraprendere nel contesto attuale". Ma il mio pensiero è che ha un ragionevole corso di azione, che è di restituire il vuoto. Fa esattamente la stessa cosa quando si passa la raccolta vuota, quindi se consento la raccolta vuota, perché non lo consentirei null.
FCin

1
@FCin A causa di una raccolta vuota, sai che è vuota. Con nullte non hai collezione. Se il codice chiamante dovrebbe / dovrebbe fornire una raccolta al metodo, c'è qualcosa che non va, quindi dovresti lanciare. L'unico motivo per trattare un null allo stesso modo di una raccolta vuota è se ci si potrebbe ragionevolmente aspettare che il codice chiamante produca raccolte null. Come notato da altre risposte, la maggior parte delle volte, essere qualcosa nullè un'anomalia. Se le trattate come raccolte vuote, potenzialmente ignorate un problema altrove nel codice.
JAD

@FCin: inoltre, se si nullvuol dire vuoto, questo è specifico del contesto per questo metodo. Da qualche altra parte nella stessa base di codice, potresti avere un parametro IEnumerableo IContainerche fa una sorta di filtro, nullpotrebbe significare "nessun filtro" (consentire tutto) mentre un filtro vuoto significa non consentire nulla. E in un altro posto, nullpotrebbe significare esplicitamente "Non lo so". Quindi, dato che nullnon significa sempre la stessa cosa ovunque, è una buona idea non inventare alcun significato per esso a meno che quel significato sia necessario o almeno utile a qualcuno.
Steve Jessop,

0

Questa domanda sembra non riguardare le eccezioni, ma nullsull'essere un argomento valido.

Quindi, prima di tutto devi decidere se nullè un valore consentito per te l'argomento di quel metodo. In tal caso, non è necessaria un'eccezione. In caso contrario, è necessaria un'eccezione.

Se si desidera consentire nullo meno, è discutibile, come dimostrato da un sacco di successi di Google su questo. Ciò significa che non otterrai una risposta chiara, e dipende in qualche modo dall'opinione e dalla tradizione nel luogo in cui lavori.

Un'altra area di contesa è se una funzione di libreria debba essere veramente severa su tali cose, o il più indulgente possibile, purché non stia cercando di "correggere" parametri errati. Confronta questo con il mondo dei protocolli di rete, del trasferimento di posta, ecc. (Che sono contratti di interfaccia, proprio come i metodi di programmazione). Lì, di solito, la politica è che il mittente dovrebbe conformarsi il più rigorosamente possibile al protocollo, mentre il destinatario dovrebbe fare di tutto per essere in grado di lavorare con qualsiasi cosa accada.

Quindi devi decidere: è davvero il lavoro di un metodo di libreria applicare una politica piuttosto ampia come la nullgestione? Soprattutto se la tua biblioteca viene utilizzata anche da altre persone (che potrebbero avere politiche diverse).

Probabilmente errerei sul lato del consentire i nullvalori, definire e documentare la loro semantica (cioè, null = empty arrayin questo caso), e non gettare un'eccezione, a meno che il 99% del tuo altro codice simile (codice di stile "libreria") non faccia diversamente 'il giro.


1
"È davvero il lavoro di un metodo di libreria applicare una politica piuttosto ampia come la gestione nulla?" - in quella linea di pensiero si trovano asserzioni e modalità di debug, che ti consentono di aiutare a far rispettare una politica, piuttosto che applicarla effettivamente, se è quello che vuoi fare. Quindi, se hai intenzione di ingoiare nullil principio di essere liberale in ciò che accetti, allora fare il login o anche lanciare sarebbe utile per le persone la cui intenzione è di essere rigorose in ciò che passano, ma che hanno incasinato sia il loro codice che le loro unità testano nella misura in cui passano nullcomunque.
Steve Jessop,

@SteveJessop, non so davvero se stai discutendo contro lo spirito della mia risposta, o se stai continuando sulla sua linea. Detto questo, non sto suggerendo semplicemente di ingoiare null, ma di dichiararlo apertamente nel contratto del metodo che è accettabile (o no, a seconda di qualsiasi soluzione si presenti l'OP), e quindi la domanda se gettare un'eccezione (o no) si risolve da solo.
AnoE,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.