Devo accettare collezioni vuote nei miei metodi che iterano su di loro?

39

Ho un metodo in cui tutta la logica viene eseguita all'interno di un ciclo foreach che esegue un'iterazione sul parametro del metodo:

public IEnumerable<TransformedNode> TransformNodes(IEnumerable<Node> nodes)
{
    foreach(var node in nodes)
    {
        // yadda yadda yadda
        yield return transformedNode;
    }
}

In questo caso, l'invio di una raccolta vuota risulta in una raccolta vuota, ma mi chiedo se ciò non sia saggio.

La mia logica è che se qualcuno sta chiamando questo metodo, allora intendono passare i dati e passeranno una raccolta vuota al mio metodo solo in circostanze errate.

Devo rilevare questo comportamento e generare un'eccezione, oppure è consigliabile restituire la raccolta vuota?

    
posta Nick Udell 26.11.2014 - 13:29
fonte

9 risposte

174

I metodi di utilità non dovrebbero gettare su collezioni vuote. I tuoi clienti API ti odieranno per questo.

Una raccolta può essere vuota; una "collezione-che-non-deve-essere-vuota" è concettualmente una cosa molto più difficile con cui lavorare.

Trasformare una collezione vuota ha un risultato ovvio: la collezione vuota. (Potresti anche salvare un po 'di rifiuti restituendo il parametro stesso.)

Ci sono molte circostanze in cui un modulo mantiene liste di cose che possono o non possono essere già riempite con qualcosa. Dover controllare la presenza di vuoto prima che ogni chiamata a transform sia fastidiosa e ha il potenziale per trasformare un semplice ed elegante algoritmo in un brutto pasticcio.

I metodi di utilità dovrebbero sempre cercare di essere liberali nei loro input e conservativi nei loro output.

Per tutti questi motivi, per l'amor di Dio, gestisci correttamente la raccolta vuota. Niente è più esasperante di un modulo helper che pensa conosce ciò che vuoi meglio di te.

    
risposta data 26.11.2014 - 13:35
fonte
26

Vedo due domande importanti che determinano la risposta a questo:

  1. La tua funzione può restituire qualcosa di significativo e logico quando viene passata una raccolta vuota (incluso null )?
  2. Qual è lo stile generale di programmazione in questa applicazione / biblioteca / squadra? (In particolare, come FP sei?)

1. Ritorno significativo

In sostanza, se puoi restituire qualcosa di significativo, allora non fare un'eccezione. Lascia che il chiamante si occupi del risultato. Quindi se la tua funzione ...

  • Conta il numero di elementi nella raccolta, restituisce 0. È stato facile.
  • Cerca elementi che soddisfano determinati criteri, restituisce una raccolta vuota. Per favore non gettare nulla. Il chiamante può avere una vasta collezione di collezioni, alcune delle quali sono vuote, altre no. Il chiamante desidera qualsiasi elemento corrispondente in qualsiasi raccolta. Le eccezioni rendono più difficile la vita del chiamante.
  • Cerca il criterio più grande / più piccolo / più adatto alla lista. Ops. A seconda della domanda di stile, potresti generare un'eccezione qui o potresti restituire null . Odio null (molto una persona FP), ma è probabilmente più significativo qui e ti consente di riservare eccezioni per errori imprevisti all'interno del tuo codice. Se il chiamante non verifica nulla, verrà comunque emessa un'eccezione abbastanza chiara. Ma lascia questo a loro.
  • Chiede l'ennesimo elemento nella raccolta o il primo / ultimo n elementi. Questo è il caso migliore per un'eccezione ancora e quella che meno probabilmente causerà confusione e difficoltà per il chiamante. Un caso può ancora essere fatto per null se tu e il tuo team siete abituati a verificarlo, per tutti i motivi indicati nel punto precedente, ma questo è il caso più valido per lanciare un DudeYou Conosci YouShouldCheckTheSizeFirst exception. Se il tuo stile è più funzionale, allora null o leggi la mia Style answer.

In generale, il mio bias di FP mi dice che "restituisce qualcosa di significativo" e null può avere un significato valido in questo caso.

2. Stile

Il tuo codice generale (o il codice del progetto o il codice del team) favorisce uno stile funzionale? Se no, saranno previste eccezioni e gestite. In caso affermativo, prendi in considerazione la possibilità di restituire un tipo di opzione . Con un tipo di opzione, puoi restituire una risposta significativa o Nessuno / Niente . Nel mio terzo esempio di cui sopra, Niente sarebbe una buona risposta in stile FP. Il fatto che la funzione restituisca un tipo di opzione segnali chiaramente al chiamante rispetto a una risposta significativa potrebbe non essere possibile e che il chiamante dovrebbe essere pronto a gestirlo. Sento che dà al chiamante più opzioni (se perdonerai il gioco di parole).

F # è dove tutti i fantastici bambini .Net fanno questo genere di cose ma C # fa supporto questo stile.

tl; dr

Mantieni eccezioni per errori imprevisti nel tuo percorso di codice, non interamente prevedibili (e legali) da quelli di qualcun altro.

    
risposta data 26.11.2014 - 15:35
fonte
18

Come sempre, dipende.

importa che la collezione è vuota?
La maggior parte del codice di gestione della raccolta probabilmente direbbe "no"; una raccolta può avere qualsiasi numero di elementi in essa contenuti, incluso zero.

Ora, se hai una sorta di collezione in cui è "non valido" per non avere elementi in esso, allora questo è un nuovo requisito e devi decidere cosa fare al riguardo.

Prendi in prestito alcune logiche di testing dal mondo Database: prova per gli articoli zero , un e due . Che si rivolge ai casi più importanti (svuotare le condizioni interne o cartesiane mal formate).

    
risposta data 26.11.2014 - 13:39
fonte
11

Per una buona progettazione, accetta quanto più possibile le variazioni nei tuoi input. Le eccezioni dovrebbero solo essere generate quando (viene presentato un input inaccettabile o si verificano errori imprevisti durante l'elaborazione) E il programma non può continuare in modo prevedibile come risultato.

In questo caso, ci si dovrebbe aspettare che venga presentata una raccolta vuota e che il tuo codice debba gestirlo (cosa che già fa). Sarebbe una violazione di tutto ciò che è buono se il tuo codice ha lanciato un'eccezione qui. Questo sarebbe simile a moltiplicare 0 per 0 in matematica. È ridondante, ma assolutamente necessario perché funzioni come fa.

Ora, sull'argomento di raccolta nullo. In questo caso, una raccolta nulla è un errore di programmazione: il programmatore ha dimenticato di assegnare una variabile. Questo è un caso in cui può essere generata un'eccezione, perché non è possibile elaborarla in modo significativo in un output, e il tentativo di farlo introdurrebbe un comportamento inaspettato. Questo sarebbe simile alla divisione per zero in matematica - è completamente privo di significato.

    
risposta data 26.11.2014 - 15:49
fonte
10

La soluzione giusta è molto più difficile da vedere quando si osserva la propria funzione da soli. Considera la tua funzione come una parte del un problema più grande . Una possibile soluzione a quell'esempio è simile a questa (in Scala):

input.split("\D")
.filterNot (_.isEmpty)
.map (_.toInt)
.filter (x => x >= 1000 && x <= 9999)

In primo luogo, dividi la stringa in alto per non cifre, filtra le stringhe vuote, converti le stringhe in numeri interi, quindi filtra per mantenere solo i numeri a quattro cifre. La tua funzione potrebbe essere map (_.toInt) nella pipeline.

Questo codice è abbastanza semplice perché ogni fase della pipeline gestisce solo una stringa vuota o una raccolta vuota. Se all'inizio metti una stringa vuota, alla fine otterrai una lista vuota. Non devi fermarti e controllare null o un'eccezione dopo ogni chiamata.

Naturalmente, presumendo che una lista di output vuota non abbia più di un significato. Se hai bisogno di distinguere tra un output vuoto causato da input vuoto e uno causato dalla trasformazione stessa, ciò cambia completamente le cose.

    
risposta data 26.11.2014 - 21:07
fonte
2

Questa domanda riguarda in realtà le eccezioni. Se la si guarda in questo modo e si ignora la raccolta vuota come dettaglio di implementazione, la risposta è semplice:

1) Un metodo dovrebbe generare un'eccezione quando non può procedere: o non è in grado di eseguire l'operazione designata, o restituire il valore appropriato.

2) Un metodo dovrebbe rilevare un'eccezione quando è in grado di procedere nonostante l'errore.

Quindi, il tuo metodo di supporto non dovrebbe essere "utile" e generare un'eccezione a meno che it non sia in grado di eseguire il lavoro con una raccolta vuota. Lascia che il chiamante determini se i risultati possono essere gestiti.

Se restituisce una raccolta vuota o nulla, è un po 'più difficile ma non molto: le collezioni nullable dovrebbero essere evitate se possibile. Lo scopo di una raccolta nullable è di indicare (come in SQL) che non si hanno le informazioni - una collezione di bambini, ad esempio, potrebbe essere nullo se non si sa se qualcuno ne ha, ma non si sappi che non lo fanno. Ma se per qualche motivo è importante, probabilmente valga la pena di aggiungere una variabile extra per tenerne traccia.

    
risposta data 27.11.2014 - 20:21
fonte
1

Il metodo è denominato TransformNodes . In caso di una raccolta vuota come input, tornare a una collezione vuota è naturale e intuitivo, e rende prefetto il senso matematico.

Se il metodo è stato chiamato Max e progettato per restituire l'elemento massimo, sarebbe naturale lanciare NoSuchElementException su una raccolta vuota, poiché il massimo di nulla non ha senso matematico.

Se il metodo è stato chiamato JoinSqlColumnNames e progettato per restituire una stringa in cui gli elementi sono uniti da una virgola da utilizzare nelle query SQL, allora avrebbe senso gettare IllegalArgumentException su una raccolta vuota, poiché il chiamante alla fine ottenere un errore SQL in ogni caso se ha usato la stringa in una query SQL direttamente senza ulteriori verifiche, e invece di cercare una stringa vuota restituita avrebbe invece dovuto verificare la raccolta vuota.

    
risposta data 30.11.2014 - 12:30
fonte
0

Facciamo un passo indietro e usiamo un esempio diverso che calcola la media aritmetica di una matrice di valori.

Se l'array di input è vuoto (o nullo), è possibile soddisfare ragionevolmente la richiesta del chiamante? No. Quali sono le tue opzioni? Bene, potresti:

  • presente / restituisce / genera un errore. usando la convenzione del tuo codebase per quella classe di errore.
  • documenta che verrà restituito un valore come zero
  • documenta che verrà restituito un valore non valido designato (ad es. NaN)
  • documenta che verrà restituito un valore magico (ad esempio un valore minimo o massimo per il tipo o qualche valore indicativo).
  • dichiara che il risultato non è specificato
  • dichiara che l'azione non è definita
  • ecc.

Dico di dare loro l'errore se ti hanno dato un input non valido e la richiesta non può essere completata. Intendo un errore grave dal primo giorno in modo che comprendano i requisiti del tuo programma. Dopo tutto, la tua funzione non è in grado di rispondere. Se l'operazione potrebbe fallire (ad esempio copia un file), la tua API dovrebbe dare loro un errore con cui possono gestire.

In questo modo è possibile definire in che modo la libreria gestisce richieste e richieste non valide che potrebbero non riuscire.

È molto importante che il tuo codice sia coerente nel modo in cui gestisce queste classi di errori.

La prossima categoria è decidere in che modo la tua biblioteca gestisce richieste senza senso. Tornando a un esempio simile al tuo, utilizziamo una funzione che determina se esiste un file in un percorso: bool FileExistsAtPath(String) . Se il client passa una stringa vuota, come gestisci questo scenario? Che ne dici di un array vuoto o nullo passato a void SaveDocuments(Array<Document>) ? Decidi per la tua biblioteca / codice base e essere coerente . Mi capita di considerare questi casi di errori e proibisco ai clienti di fare richieste senza senso segnalandoli come errori (attraverso un'asserzione). Alcune persone resisteranno strongmente a quell'idea / azione. Trovo molto utile questo rilevamento degli errori. È molto utile per individuare i problemi nei programmi, con una buona localizzazione del programma offensivo. I programmi sono molto più chiari e corretti (considera l'evoluzione della tua base di codice) e non bruciano cicli all'interno di funzioni che non fanno nulla. Il codice è più piccolo / pulito in questo modo e i controlli vengono generalmente inviati alle posizioni in cui è possibile che il problema venga introdotto.

    
risposta data 26.11.2014 - 16:43
fonte
-3

Come regola generale, una funzione standard dovrebbe essere in grado di accettare la più ampia lista di input e dare feedback su di essa, ci sono molti esempi in cui i programmatori usano le funzioni in modi che il progettista non aveva pianificato, con questo in mente io crediamo che la funzione dovrebbe essere in grado di accettare non solo le collezioni vuote ma una vasta gamma di tipi di input e restituire con garbo feedback, che sia anche un oggetto errore da qualsiasi operazione è stata eseguita sull'input ...

    
risposta data 27.11.2014 - 12:28
fonte

Leggi altre domande sui tag