Eccezione vs set di risultati vuoto quando gli input sono tecnicamente validi, ma insoddisfacenti


108

Sto sviluppando una libreria destinata al rilascio pubblico. Contiene vari metodi per operare su insiemi di oggetti: generare, ispezionare, partizionare e proiettare gli insiemi in nuove forme. Nel caso sia pertinente, è una libreria di classe C # contenente estensioni in stile LINQ su IEnumerable, da rilasciare come pacchetto NuGet.

Ad alcuni dei metodi in questa libreria possono essere forniti parametri di input non soddisfacenti. Ad esempio, nei metodi combinatori, esiste un metodo per generare tutti gli insiemi di n elementi che possono essere costruiti da un insieme di origine di m elementi. Ad esempio, dato il set:

1, 2, 3, 4, 5

e chiedere combinazioni di 2 produrrebbe:

1, 2
1, 3
1, 4
ecc ...
5, 3
5, 4

Ora, ovviamente, è possibile chiedere qualcosa che non può essere fatto, come dargli un set di 3 elementi e quindi chiedere combinazioni di 4 elementi mentre si imposta l'opzione che dice che può usare ogni oggetto solo una volta.

In questo scenario, ogni parametro è individualmente valido:

  • La raccolta di origine non è nulla e contiene elementi
  • La dimensione richiesta delle combinazioni è un numero intero diverso da zero
  • La modalità richiesta (usa ogni elemento una sola volta) è una scelta valida

Tuttavia, lo stato dei parametri quando presi insieme causa problemi.

In questo scenario, ti aspetteresti che il metodo generi un'eccezione (ad es. InvalidOperationException) O restituisca una raccolta vuota? O mi sembra valido:

  • Non è possibile produrre combinazioni di n elementi da un insieme di m elementi in cui n> m se è consentito utilizzare ogni articolo una sola volta, quindi questa operazione può essere considerata impossibile, quindi InvalidOperationException.
  • L'insieme di combinazioni di dimensione n che possono essere prodotte da m articoli quando n> m è un insieme vuoto; nessuna combinazione può essere prodotta.

L'argomento per un set vuoto

La mia prima preoccupazione è che un'eccezione impedisce il concatenamento idiomatico di metodi in stile LINQ quando si ha a che fare con set di dati che potrebbero avere dimensioni sconosciute. In altre parole, potresti voler fare qualcosa del genere:

var result = someInputSet
    .CombinationsOf(4, CombinationsGenerationMode.Distinct)
    .Select(combo => /* do some operation to a combination */)
    .ToList();

Se il set di input ha dimensioni variabili, il comportamento di questo codice è imprevedibile. Se .CombinationsOf()genera un'eccezione quando someInputSetha meno di 4 elementi, questo codice a volte fallirà in fase di esecuzione senza alcuni controlli preliminari. Nell'esempio sopra questo controllo è banale, ma se lo chiami a metà di una catena più lunga di LINQ, questo potrebbe diventare noioso. Se restituisce un set vuoto, resultsarà vuoto, di cui potresti essere perfettamente soddisfatto.

L'argomento per un'eccezione

La mia seconda preoccupazione è che la restituzione di un set vuoto potrebbe nascondere problemi: se stai chiamando questo metodo a metà di una catena di LINQ e restituisce silenziosamente un set vuoto, potresti riscontrare problemi alcuni passaggi in seguito, o ritrovarti con un vuoto set di risultati, e potrebbe non essere ovvio come sia accaduto dato che hai sicuramente avuto qualcosa nel set di input.

Cosa ti aspetteresti e qual è il tuo argomento?


66
Poiché l'insieme vuoto è matematicamente corretto, è probabile che quando lo ottieni sia effettivamente ciò che desideri. Le definizioni e le convenzioni matematiche sono generalmente scelte per coerenza e convenienza, in modo che le cose si risolvano semplicemente con esse.
asmeurer il

5
@asmeurer Sono scelti in modo tale che i teoremi siano coerenti e convenienti. Non sono stati scelti per facilitare la programmazione. (Questo a volte è un vantaggio collaterale, ma a volte rendono anche la programmazione più difficile.)
jpmc26

7
@ jpmc26 "Sono scelti in modo tale che i teoremi siano coerenti e convenienti": assicurarsi che il tuo programma funzioni sempre come previsto equivale essenzialmente a dimostrare un teorema.
Artem

2
@ jpmc26 Non capisco perché hai citato la programmazione funzionale. Dimostrare la correttezza per i programmi imperativi è del tutto possibile, sempre vantaggioso, e può essere fatto anche in modo informale: basti pensare un po 'e usare il buon senso matematico quando scrivi il tuo programma e passerai meno tempo a testare. Statisticamente provato sul campione di uno ;-)
Artem

5
@DmitryGrigoryev 1/0 come indefinito è molto più matematicamente corretto di 1/0 come infinito.
asmeurer,

Risposte:


145

Restituisce un set vuoto

Mi aspetterei un set vuoto perché:

Ci sono 0 combinazioni di 4 numeri dall'insieme di 3 quando posso usare ogni numero solo una volta


5
Matematicamente, sì, ma è anche molto probabilmente una fonte di errore. Se è previsto questo tipo di input, potrebbe essere meglio richiedere all'utente di rilevare l'eccezione in una libreria per scopi generici.
Casey Kuball,

56
Non sono d'accordo sul fatto che sia una causa "molto probabile" di errore. Supponiamo, ad esempio, di implementare un ingenuo algoritmo "matchmaknig" da un set di input di grandi dimensioni. Potresti chiedere tutte le combinazioni di due elementi, trovare la singola "migliore" corrispondenza tra loro, quindi rimuovere entrambi gli elementi e ricominciare con il nuovo set più piccolo. Alla fine il tuo set sarà vuoto e non ci saranno più coppie da considerare: un'eccezione a questo punto è ostruttiva. Penso che ci siano molti modi per finire in una situazione simile.
amalloy,

11
In realtà non sai se si tratta di un errore agli occhi dell'utente. Il set vuoto è qualcosa che l'utente dovrebbe verificare, se necessario. if (result.Any ()) DoSomething (result.First ()); else DoSomethingElse (); è molto meglio di provare {result.first (). dosomething ();} catch {DoSomethingElse ();}
Guran,

4
@TripeHound Questo è ciò che ha lasciato la decisione alla fine: richiedere allo sviluppatore che utilizza questo metodo di controllare e quindi lanciare, ha un impatto molto più piccolo rispetto a lanciare un'eccezione che non vogliono, in termini di sforzo di sviluppo, prestazioni del programma e semplicità del flusso del programma.
anaximander

6
@Darthfett Prova a confrontare questo con un metodo di estensione LINQ esistente: Where (). Se ho una clausola: Where (x => 1 == 2) Non ottengo un'eccezione, ottengo un set vuoto.
Necoras,

79

In caso di dubbi, chiedi a qualcun altro.

La funzione esempio ha uno molto simile in Python: itertools.combinations. Vediamo come funziona:

>>> import itertools
>>> input = [1, 2, 3, 4, 5]
>>> list(itertools.combinations(input, 2))
[(1, 2), (1, 3), (1, 4), (1, 5), (2, 3), (2, 4), (2, 5), (3, 4), (3, 5), (4, 5)]
>>> list(itertools.combinations(input, 5))
[(1, 2, 3, 4, 5)]
>>> list(itertools.combinations(input, 6))
[]

E mi sento perfettamente bene. Mi aspettavo un risultato che potessi ripetere e ne ho ottenuto uno.

Ma, ovviamente, se dovessi chiedere qualcosa di stupido:

>>> list(itertools.combinations(input, -1))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: r must be non-negative

Quindi direi che se tutti i tuoi parametri vengono convalidati ma il risultato è un set vuoto restituisci un set vuoto, non sei il solo a farlo.


Come detto da @Bakuriu nei commenti, questo è lo stesso anche per una SQLquery simile SELECT <columns> FROM <table> WHERE <conditions>. Finché <columns>, <table>, <conditions>sono ben formati formati e si riferiscono ai nomi esistenti, è possibile costruire un insieme di condizioni che escludono l'un l'altro. La query risultante non produrrebbe semplicemente righe invece di lanciare un InvalidConditionsError.


4
Sottovalutato perché non è idiomatico nello spazio del linguaggio C # / Linq (vedere la risposta di sbecker per come simili problemi fuori limite sono gestiti nella lingua). Se questa fosse una domanda su Python, sarebbe un +1 da parte mia.
James Snell,

32
@JamesSnell Riesco a malapena a vedere come si collega al problema dei limiti. Non stiamo selezionando elementi per indici per riordinarli, stiamo selezionando nelementi in una raccolta per contare il numero di modi in cui possiamo selezionarli. Se ad un certo punto non sono rimasti elementi durante la loro raccolta, allora non esiste (0) un modo per selezionare gli nelementi da detta raccolta.
Mathias Ettinger,

1
Tuttavia, Python non è un buon oracolo per le domande di C #: per esempio C # lo permetterebbe 1.0 / 0, Python non lo farebbe.
Dmitry Grigoryev,

2
@MathiasEttinger Le linee guida di Python su come e quando usare le eccezioni sono molto diverse da quelle di C #, quindi usare il comportamento dei moduli Python come linea guida non è una buona metrica per C #.
Formica P

2
Invece di scegliere Python potremmo semplicemente scegliere SQL. Una query ben formata SELECT su una tabella produce un'eccezione quando il set di risultati è vuoto? (spoiler: no!). La "buona formazione" di una query dipende solo dalla query stessa e da alcuni metadati (ad es. La tabella esiste e presenta questo campo (con questo tipo)?) Una volta che la query è ben formata ed eseguita, ci si aspetta che un (possibilmente vuoto) risultato valido o un'eccezione perché il "server" ha avuto un problema (ad es. server db inattivo o qualcosa del genere).
Bakuriu,

72

In parole povere:

  • Se si verifica un errore, è necessario sollevare un'eccezione. Ciò può comportare il fare le cose a passi invece che in una singola chiamata concatenata per sapere esattamente dove si è verificato l'errore.
  • Se non si verificano errori ma il set risultante è vuoto, non generare un'eccezione, restituire il set vuoto. Un set vuoto è un set valido.

23
+1, 0 è un numero perfettamente valido e davvero estremamente importante. Ci sono così tanti casi in cui una query potrebbe legittimamente restituire zero hit che sollevare un'eccezione sarebbe estremamente fastidioso.
Kilian Foth,

19
buon consiglio, ma la tua risposta è chiara?
Ewan,

54
Non sono ancora più saggio se questa risposta suggerisca che l'OP dovrebbe throwo restituire un set vuoto ...
James Snell,

7
La tua risposta dice che potrei fare entrambe le cose, a seconda che ci sia un errore, ma non dici se considererai lo scenario di esempio come un errore. Nel commento sopra dici che dovrei restituire un set vuoto "se non ci sono problemi", ma ancora una volta, le condizioni insoddisfacenti potrebbero essere considerate un problema, e continui dicendo che non sto sollevando eccezioni quando dovrei. Quindi cosa dovrei fare?
anaximander

3
Ah, capisco, potresti aver frainteso; Non sto concatenando nulla, sto scrivendo un metodo che mi aspetto di essere usato in una catena. Mi chiedo se l'ipotetico futuro sviluppatore che utilizza questo metodo vorrebbe lanciarlo nello scenario sopra.
anaximander,

53

Concordo con la risposta di Ewan ma voglio aggiungere un ragionamento specifico.

Hai a che fare con operazioni matematiche, quindi potrebbe essere un buon consiglio attenersi alle stesse definizioni matematiche. Da un punto di vista matematico il numero di r -set di un n- set (cioè nCr ) è ben definito per tutti r> n> = 0. È zero. Quindi restituire un set vuoto sarebbe il caso previsto dal punto di vista matematico.


9
Questo è un buon punto. Se la libreria era di livello superiore, come scegliere combinazioni di colori per creare una tavolozza, allora ha senso lanciare un errore. Perché sai che una tavolozza senza colori non è una tavolozza. Ma un set senza voci è ancora un set e la matematica lo definisce come uguale a un set vuoto.
Captain Man

1
Molto meglio della risposta di Ewans perché cita la pratica matematica. +1
user949300

24

Trovo un buon modo per determinare se usare un'eccezione, è immaginare le persone coinvolte nella transazione.

Prendendo il recupero del contenuto di un file come esempio:

  1. Per favore, prendimi il contenuto del file, "non esiste.txt"

    un. "Ecco il contenuto: una raccolta vuota di personaggi"

    b. "Ehm, c'è un problema, quel file non esiste. Non so cosa fare!"

  2. Per favore, prendimi il contenuto del file, "esiste ma è vuoto.txt"

    un. "Ecco il contenuto: una raccolta vuota di personaggi"

    b. "Ehm, c'è un problema, non c'è niente in questo file. Non so cosa fare!"

Non c'è dubbio che alcuni non saranno d'accordo, ma per la maggior parte delle persone, "Erm, c'è un problema" ha senso quando il file non esiste e restituisce "una raccolta vuota di caratteri" quando il file è vuoto.

Quindi applicando lo stesso approccio al tuo esempio:

  1. Per favore, dammi tutte le combinazioni di 4 articoli per {1, 2, 3}

    un. Non ce ne sono, ecco un set vuoto.

    b. C'è un problema, non so cosa fare.

Ancora una volta, "C'è un problema" avrebbe senso se, ad esempio, nullvenisse offerto come set di articoli, ma "ecco un set vuoto" sembra una risposta ragionevole alla richiesta di cui sopra.

Se la restituzione di un valore vuoto maschera un problema (ad esempio un file mancante, a null), in genere dovrebbe essere utilizzata un'eccezione (a meno che la lingua scelta non supporti i option/maybetipi, a volte hanno più senso). Altrimenti, restituire un valore vuoto probabilmente semplificherà il costo e rispetta meglio il principio del minimo stupore.


4
Questo consiglio è buono, ma non si applica a questo caso. Un esempio migliore: quanti giorni dopo il 30: gennaio ?: 1 Quanti giorni dopo il 30: febbraio ?: 0 Quanti giorni dopo il 30: gennaio ?: Eccezione Quante ore lavorative il 30 : febbraio: Eccezione
Guran,

9

Poiché è per una libreria di uso generale, il mio istinto sarebbe: Lascia che l'utente finale scelga.

Proprio come abbiamo Parse()e TryParse()disponibile per noi possiamo avere l'opzione di cui utilizziamo a seconda dell'output di cui abbiamo bisogno dalla funzione. Passeresti meno tempo a scrivere e mantenere un wrapper per le funzioni per lanciare l'eccezione piuttosto che discutere sulla scelta di una singola versione della funzione.


3
+1 perché mi piace sempre l'idea della scelta e perché questo modello ha la tendenza a incoraggiare i programmatori a convalidare il proprio input. La semplice esistenza di una funzione TryFoo rende ovvio che alcune combinazioni di input possono causare problemi e se la documentazione spiega quali sono questi potenziali problemi, il programmatore può verificarli e gestire input non validi in modo più pulito di quanto potrebbero semplicemente cogliendo un'eccezione o rispondendo a un set vuoto. Oppure possono essere pigri. Ad ogni modo, la decisione è loro.
Aleppo,

19
No, un set vuoto è la risposta matematicamente corretta. Fare qualcos'altro sta ridefinendo la matematica concordata, che non dovrebbe essere fatta per capriccio. Mentre ci siamo, dovremmo ridefinire la funzione di esponenziale per generare un errore se l'esponente è uguale a zero, perché decidiamo che non ci piace la convenzione che qualsiasi numero elevato al potere "zeroth" sia uguale a 1?
Wildcard il

1
Stavo per rispondere a qualcosa di simile. I metodi di estensione Linq Single()e SingleOrDefault()subito mi sono venuti in mente. Single genera un'eccezione se il risultato è zero o> 1, mentre SingleOrDefault non genererà e restituirà invece default(T). Forse OP potrebbe usare CombinationsOf()e CombinationsOfOrThrow().
RubberDuck,

1
@Wildcard - stai parlando di due risultati diversi con lo stesso input che non è la stessa cosa. L'OP è interessato solo a scegliere a) il risultato oppure b) lanciare un'eccezione che non è un risultato.
James Snell,

4
Singlee SingleOrDefault(o Firste FirstOrDefault) sono una storia completamente diversa, @RubberDuck. Raccogliendo il mio esempio dal mio commento precedente. Va benissimo rispondere a nessuno alla domanda "Cosa esistono anche numeri primi maggiori di due?" , poiché questa è una risposta matematicamente sana e ragionevole. Ad ogni modo, se stai chiedendo "Qual è il primo anche primo primo maggiore di due?" non esiste una risposta naturale. Non puoi semplicemente rispondere alla domanda, perché stai chiedendo un singolo numero. Non è nessuno (non è un singolo numero, ma un set), non 0. Quindi lanciamo un'eccezione.
Paul Kertscher,

4

È necessario convalidare gli argomenti forniti quando viene chiamata la funzione. E di fatto, vuoi sapere come gestire argomenti non validi. Il fatto che più argomenti dipendano l'uno dall'altro, non compensa il fatto che si convalidano gli argomenti.

Vorrei quindi votare per ArgumentException fornendo le informazioni necessarie all'utente per capire cosa è andato storto.

Ad esempio, controlla la public static TSource ElementAt<TSource>(this IEnumerable<TSource>, Int32)funzione in Linq. Che genera un ArgumentOutOfRangeException se l'indice è inferiore a 0 o maggiore o uguale al numero di elementi nell'origine. Pertanto l'indice viene convalidato rispetto all'enumerabile fornito dal chiamante.


3
+1 per citare un esempio di una situazione simile nella lingua.
James Snell,

13
Stranamente, il tuo esempio mi ha fatto pensare e mi ha portato a qualcosa che penso sia un forte contro-esempio. Come ho notato, questo metodo è progettato per essere simile a LINQ, in modo che gli utenti possano concatenarlo insieme ad altri metodi LINQ. Se lo fai new[] { 1, 2, 3 }.Skip(4).ToList();ottieni un set vuoto, il che mi fa pensare che restituire un set vuoto sia forse l'opzione migliore qui.
anaximander

14
Questo non è un problema di semantica del linguaggio, la semantica del dominio del problema fornisce già la risposta corretta, ovvero restituire il set vuoto. Fare qualsiasi altra cosa viola il principio del minimo stupore per chi lavora nel campo del problema combinatorio.
Ukko,

1
Sto iniziando a capire gli argomenti per restituire un set vuoto. Perché avrebbe senso. Almeno per questo esempio particolare.
sbecker

2
@sbecker, per portare il punto a casa: Se la domanda fosse: "Come molti combinazioni ci sono di ..." e poi "zero" sarebbe una risposta del tutto valido. Allo stesso modo, "Quali sono le combinazioni tali che ..." ha l'insieme vuoto come risposta completamente valida. Se la domanda fosse: "Qual è la prima combinazione tale che ...", allora e solo allora un'eccezione sarebbe appropriata, poiché la domanda non è rispondibile. Si veda anche il commento di Paul K .
Wildcard il

3

Dovresti eseguire una delle seguenti operazioni (sebbene continui a gettare costantemente problemi di base come un numero negativo di combinazioni):

  1. Fornire due implementazioni, una che restituisce un set vuoto quando gli input insieme sono privi di senso e uno che genera. Prova a chiamarli CombinationsOfe CombinationsOfWithInputCheck. O qualunque cosa ti piaccia. È possibile invertire ciò in modo che quello di controllo input sia il nome più breve e quello elenco CombinationsOfAllowInconsistentParameters.

  2. Per i metodi Linq, restituisci il vuoto IEnumerablenella premessa esatta che hai delineato. Quindi, aggiungi questi metodi Linq alla tua libreria:

    public static class EnumerableExtensions {
       public static IEnumerable<T> ThrowIfEmpty<T>(this IEnumerable<T> input) {
          return input.IfEmpty<T>(() => {
             throw new InvalidOperationException("An enumerable was unexpectedly empty");
          });
       }
    
       public static IEnumerable<T> IfEmpty<T>(
          this IEnumerable<T> input,
          Action callbackIfEmpty
       ) {
          var enumerator = input.GetEnumerator();
          if (!enumerator.MoveNext()) {
             // Safe because if this throws, we'll never run the return statement below
             callbackIfEmpty();
          }
          return EnumeratePrimedEnumerator(enumerator);
       }
    
       private static IEnumerable<T> EnumeratePrimedEnumerator<T>(
          IEnumerator<T> primedEnumerator
       ) {
          yield return primedEnumerator.Current;
          while (primedEnumerator.MoveNext()) {
             yield return primedEnumerator.Current;
          }
       }
    }

    Infine, usalo così:

    var result = someInputSet
       .CombinationsOf(4, CombinationsGenerationMode.Distinct)
       .ThrowIfEmpty()
       .Select(combo => /* do some operation to a combination */)
       .ToList();

    o in questo modo:

    var result = someInputSet
       .CombinationsOf(4, CombinationsGenerationMode.Distinct)
       .IfEmpty(() => _log.Warning(
          $@"Unexpectedly received no results when creating combinations for {
             nameof(someInputSet)}"
       ))
       .Select(combo => /* do some operation to a combination */)
       .ToList();

    Si noti che il metodo privato essendo diverso da quello pubblico è necessario affinché si verifichi il lancio o il comportamento dell'azione quando viene creata la catena linq anziché qualche tempo dopo quando viene elencata. Lo vuoi lanciare subito.

    Si noti, tuttavia, che ovviamente deve enumerare almeno il primo elemento per determinare se ci sono elementi. Questo è un potenziale svantaggio che penso sia in gran parte mitigato dal fatto che qualsiasi futuro spettatore può facilmente ragionare sul fatto che un ThrowIfEmptymetodo deve enumerare almeno un elemento, quindi non dovrebbe essere sorpreso dal farlo. Ma non lo sai mai. Potresti renderlo più esplicito ThrowIfEmptyByEnumeratingAndReEmittingFirstItem. Ma sembra un gigantesco eccessivo.

Penso che # 2 sia abbastanza, beh, fantastico! Ora il potere sta nel codice chiamante e il prossimo lettore del codice capirà esattamente cosa sta facendo e non dovrà affrontare eccezioni impreviste.


Per favore, spiega cosa intendi. In che modo qualcosa nel mio post "non si comporta come un set" e perché è una cosa negativa?
ErikE,

Stai fondamentalmente facendo lo stesso della mia risposta, invece di racchiudere la query che ti è piaciuta per eseguire una query su If Condition, il risultato non ti cambia
GameDeveloper

Non riesco a vedere come la mia risposta sia "sostanzialmente uguale alla" della tua risposta. Sembrano completamente diversi, per me.
ErikE,

Fondamentalmente stai solo applicando la stessa condizione della "mia cattiva classe". Ma non lo stai dicendo ^^. Sei d'accordo, stai rafforzando l'esistenza di 1 elemento nel risultato della query?
GameDeveloper il

Dovrai parlare più chiaramente per i programmatori evidentemente stupidi che ancora non capiscono di cosa stai parlando. Quale classe è cattiva? Perché è male? Non sto facendo rispettare nulla. Sto permettendo all'UTENTE della classe di decidere il comportamento finale invece del CREATORE della classe. Ciò consente di utilizzare un paradigma di codifica coerente in cui i metodi Linq non generano casualmente a causa di un aspetto computazionale che non è in realtà una violazione delle normali regole come se si chiedessero -1 elementi alla volta.
ErikE

2

Vedo argomenti per entrambi i casi d'uso: un'eccezione è eccezionale se il codice downstream prevede set che contengono dati. D'altra parte, semplicemente un set vuoto è ottimo se questo è previsto.

Penso che dipenda dalle aspettative del chiamante se si tratta di un errore o di un risultato accettabile, quindi trasferirei la scelta al chiamante. Forse introdurre un'opzione?

.CombinationsOf(4, CombinationsGenerationMode.Distinct, Options.AllowEmptySets)


10
Mentre arrivo dove stai andando, cerco di evitare i metodi con molte opzioni passate che modificano il loro comportamento. Non sono ancora soddisfatto al 100% della modalità enum che è già lì; Non mi piace l'idea di un parametro di opzione che modifica il comportamento di eccezione di un metodo. Sento che se un metodo deve generare un'eccezione, deve essere lanciato; se vuoi nascondere quell'eccezione che è la tua decisione come codice chiamante, e non qualcosa che puoi far fare al mio codice con la giusta opzione di input.
anaximander

Se il chiamante prevede un set che non è vuoto, spetta al chiamante gestire un set vuoto o generare un'eccezione. Per quanto riguarda la chiamata, un set vuoto è una risposta perfettamente valida. E puoi avere più di un chiamante, con opinioni diverse sul fatto che i set vuoti vadano bene o meno.
gnasher729,

2

Esistono due approcci per decidere se non esiste una risposta ovvia:

  • Scrivi il codice assumendo prima un'opzione, poi l'altra. Considera quale funzionerebbe meglio nella pratica.

  • Aggiungi un parametro booleano "rigoroso" per indicare se desideri che i parametri siano rigorosamente verificati o meno. Ad esempio, Java SimpleDateFormatha un setLenientmetodo per tentare di analizzare input che non corrispondono completamente al formato. Ovviamente, dovresti decidere qual è l'impostazione predefinita.


2

In base alla tua analisi, restituire l'insieme vuoto sembra chiaramente giusto: l'hai persino identificato come qualcosa che alcuni utenti potrebbero effettivamente desiderare e non sono caduti nella trappola di vietare un certo utilizzo perché non riesci a immaginare che gli utenti non vogliano mai usarlo quel modo.

Se ritieni davvero che alcuni utenti potrebbero voler forzare resi non vuoti, dai loro un modo per chiedere quel comportamento piuttosto che forzarlo su tutti. Ad esempio, potresti:

  • Rendilo un'opzione di configurazione su qualunque oggetto stia eseguendo l'azione per l'utente.
  • Trasformalo in una bandiera che l'utente può facoltativamente passare alla funzione.
  • Fornire un AssertNonemptycontrollo che possono mettere nelle loro catene.
  • Crea due funzioni, una che afferma non vuota e una che non lo fa.

questo non sembra offrire nulla di sostanziale rispetto ai punti formulati e spiegati nelle precedenti 12 risposte
moscerino

1

Dipende molto da ciò che gli utenti si aspettano di ottenere. Per un esempio (in qualche modo non correlato) se il tuo codice esegue la divisione, puoi lanciare un'eccezione o restituire Info NaNquando dividi per zero. Né è giusto o sbagliato, tuttavia:

  • se ritorni Infin una libreria Python, le persone ti assaliranno per nascondere errori
  • se si genera un errore in una libreria Matlab, le persone ti assaliranno per non aver elaborato i dati con valori mancanti

Nel tuo caso, sceglierei la soluzione che sarà meno sorprendente per gli utenti finali. Dato che stai sviluppando una libreria che si occupa di set, un set vuoto sembra qualcosa che i tuoi utenti si aspetterebbero di gestire, quindi restituirlo sembra una cosa sensata da fare. Ma potrei sbagliarmi: hai una comprensione del contesto molto migliore di chiunque altro qui, quindi se ti aspetti che i tuoi utenti facciano affidamento sul fatto che il set non sia sempre vuoto, dovresti lanciare subito un'eccezione.

Le soluzioni che consentono all'utente di scegliere (come l'aggiunta di un parametro "rigoroso") non sono definitive, poiché sostituiscono la domanda originale con una nuova equivalente: "Quale valore di strictdovrebbe essere il valore predefinito?"


2
Ho pensato di utilizzare l'argomento NaN nella mia risposta e condivido la tua opinione. Non possiamo dire altro senza conoscere il dominio dell'OP
GameDeveloper il

0

È una concezione comune (in matematica) che quando selezioni elementi su un set non riesci a trovare alcun elemento e quindi ottieni un set vuoto . Ovviamente devi essere coerente con la matematica se vai in questo modo:

Regole comuni stabilite:

  • Set.Foreach (predicato); // restituisce sempre true per set vuoti
  • Set.Exists (predicato); // restituisce sempre false per set vuoti

La tua domanda è molto sottile:

  • Potrebbe essere che l'input della tua funzione debba rispettare un contratto : in tal caso qualsiasi input non valido dovrebbe sollevare un'eccezione, il gioco è fatto, la funzione non funziona con parametri regolari.

  • Potrebbe essere che l'input della tua funzione debba comportarsi esattamente come un set e quindi dovrebbe essere in grado di restituire un set vuoto.

Ora se fossi in te andrei nel modo "Set", ma con un grande "MA".

Supponi di avere una raccolta che "per ipotesi" dovrebbe avere solo studentesse:

class FemaleClass{

    FemaleStudent GetAnyFemale(){
        var femaleSet= mySet.Select( x=> x.IsFemale());
        if(femaleSet.IsEmpty())
            throw new Exception("No female students");
        else
            return femaleSet.Any();
    }
}

Ora la tua collezione non è più un "insieme puro", perché hai un contratto e quindi dovresti far rispettare il tuo contratto con un'eccezione.

Quando usi le tue funzioni "set" in modo puro, non dovresti generare eccezioni in caso di set vuoto, ma se hai raccolte che non sono più "set puri", dovresti lanciare eccezioni dove appropriato .

Dovresti sempre fare ciò che sembra più naturale e coerente: per me un insieme dovrebbe aderire a regole stabilite, mentre le cose che non sono serie dovrebbero avere le loro regole ben pensate.

Nel tuo caso sembra una buona idea fare:

List SomeMethod( Set someInputSet){
    var result = someInputSet
        .CombinationsOf(4, CombinationsGenerationMode.Distinct)
        .Select(combo => /* do some operation to a combination */)
        .ToList();

    // the only information here is that set is empty => there are no combinations

    // BEWARE! if 0 here it may be invalid input, but also a empty set
    if(result.Count == 0)  //Add: "&&someInputSet.NotEmpty()"

    // we go a step further, our API require combinations, so
    // this method cannot satisfy the API request, then we throw.
         throw new Exception("you requsted impossible combinations");

    return result;
}

Ma non è davvero una buona idea, ora abbiamo uno stato non valido che può verificarsi in fase di esecuzione in momenti casuali, tuttavia è implicito nel problema, quindi non possiamo rimuoverlo, sicuro che possiamo spostare l'eccezione all'interno di un metodo di utilità (è esattamente lo stesso codice, spostato in luoghi diversi), ma è sbagliato e in pratica la cosa migliore che puoi fare è attenersi alle normali regole stabilite .

Infatti l'aggiunta di nuova complessità solo per dimostrare che è possibile scrivere metodi di query linq non sembra utile per il tuo problema , sono abbastanza sicuro che se OP ci può dire di più sul suo dominio, probabilmente potremmo trovare il punto in cui l'eccezione è davvero necessaria (se affatto, è possibile che il problema non richieda alcuna eccezione).


Il problema è che stai usando oggetti definiti matematicamente per risolvere un problema, ma il problema stesso potrebbe non richiedere un oggetto definito matematicamente come risposta (infatti qui restituendo un elenco). Tutto dipende dal livello più alto del problema, indipendentemente da come lo risolvi, che si chiama incapsulamento / nascondere le informazioni.
GameDeveloper

Il motivo per cui il tuo "SomeMethod" non dovrebbe più comportarsi come un set è che stai chiedendo un'informazione "fuori dagli insiemi", in particolare guarda la parte commentata "&& someInputSet.NotEmpty ()"
GameDeveloper

1
NO! Non dovresti gettare un'eccezione nella tua classe femminile. Dovresti usare il modello Builder che impone che non puoi nemmeno OTTENERE un'istanza del FemaleClassmeno che non abbia solo femmine (non è pubblicamente creabile, solo la classe Builder può distribuire un'istanza), e possibilmente ha almeno una femmina in essa . Quindi il tuo codice ovunque nel posto che assume FemaleClasscome argomento non deve gestire le eccezioni perché l'oggetto è nello stato sbagliato.
ErikE

Stai assumendo che la classe sia così semplice da poter applicare le sue precondizioni semplicemente dal costruttore, in realtà. La tua argomentazione è difettosa. Se ti proibisci di non avere più femmine, allora o non puoi avere un metodo per rimuovere gli studenti dalla classe o il tuo metodo deve generare un'eccezione quando rimane 1 studente. Hai appena spostato il mio problema altrove. Il punto è che ci sarà sempre qualche stato di condizione / precondizione che non può essere mantenuto "in base alla progettazione" e che l'utente si romperà: ecco perché esistono le eccezioni! Questa è roba piuttosto teorica, spero che il downvote non sia tuo.
GameDeveloper il

Il downvote non è mio, ma se lo fosse, ne sarei orgoglioso . Ho votato con attenzione ma con convinzione. Se la regola della tua classe è che DEVE avere un oggetto al suo interno e tutti gli articoli devono soddisfare una condizione specifica, quindi consentire alla classe di trovarsi in uno stato incoerente è una cattiva idea. Spostare il problema altrove è esattamente la mia intenzione! Voglio che il problema si verifichi in fase di costruzione, non in fase di utilizzo. Il punto di usare una classe Builder invece di lanciare un costruttore è che puoi creare uno stato molto complesso in molti pezzi e parti attraverso incongruenze.
ErikE,
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.