Excepción vs conjunto de resultados vacío cuando las entradas son técnicamente válidas, pero insatisfactorias

108

Estoy desarrollando una biblioteca destinada al lanzamiento público. Contiene varios métodos para operar en conjuntos de objetos: generar, inspeccionar, particionar y proyectar los conjuntos en nuevas formas. En caso de que sea relevante, es una biblioteca de clase C # que contiene extensiones de estilo LINQ IEnumerable, que se lanzará como un paquete NuGet.

Algunos de los métodos en esta biblioteca pueden recibir parámetros de entrada insatisfactorios. Por ejemplo, en los métodos combinatorios, hay un método para generar todos los conjuntos de n elementos que se pueden construir a partir de un conjunto fuente de m elementos. Por ejemplo, dado el conjunto:

1, 2, 3, 4, 5

y pedir combinaciones de 2 produciría:

1, 2
1, 3
1, 4,
etc ...
5, 3
5, 4

Ahora, obviamente es posible pedir algo que no se puede hacer, como darle un conjunto de 3 elementos y luego pedir combinaciones de 4 elementos mientras configura la opción que dice que solo puede usar cada elemento una vez.

En este escenario, cada parámetro es válido individualmente :

  • La colección de origen no es nula y contiene elementos.
  • El tamaño solicitado de las combinaciones es un entero positivo distinto de cero
  • El modo solicitado (use cada elemento solo una vez) es una opción válida

Sin embargo, el estado de los parámetros cuando se toman juntos causa problemas.

En este escenario, ¿esperaría que el método arroje una excepción (por ejemplo InvalidOperationException) o devuelva una colección vacía? Cualquiera de los dos me parece válido:

  • No puede producir combinaciones de n elementos a partir de un conjunto de m elementos donde n> m si solo se le permite usar cada elemento una vez, por lo tanto, esta operación puede considerarse imposible InvalidOperationException.
  • El conjunto de combinaciones de tamaño n que se pueden producir a partir de m elementos cuando n> m es un conjunto vacío; No se pueden producir combinaciones.

El argumento para un conjunto vacío

Mi primera preocupación es que una excepción evita el encadenamiento idiomático de métodos al estilo LINQ cuando se trata de conjuntos de datos que pueden tener un tamaño desconocido. En otras palabras, es posible que desee hacer algo como esto:

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

Si su conjunto de entrada es de tamaño variable, el comportamiento de este código es impredecible. Si .CombinationsOf()arroja una excepción cuando someInputSettiene menos de 4 elementos, este código a veces fallará en tiempo de ejecución sin alguna verificación previa. En el ejemplo anterior, esta comprobación es trivial, pero si la está llamando a la mitad de una cadena más larga de LINQ, esto puede ser tedioso. Si devuelve un conjunto vacío, entonces resultestará vacío, con lo que puede estar perfectamente satisfecho.

El argumento para una excepción

Mi segunda preocupación es que devolver un conjunto vacío puede ocultar problemas: si está llamando a este método a la mitad de una cadena de LINQ y devuelve silenciosamente un conjunto vacío, entonces puede tener problemas algunos pasos más tarde o encontrarse con un vacío conjunto de resultados, y puede no ser obvio cómo sucedió eso dado que definitivamente tenía algo en el conjunto de entrada.

¿Qué esperarías y cuál es tu argumento?

anaximandro
fuente
66
Dado que el conjunto vacío es matemáticamente correcto, lo más probable es que cuando lo consigas realmente sea lo que deseas. Las definiciones y convenciones matemáticas generalmente se eligen por su consistencia y conveniencia para que las cosas simplemente funcionen con ellas.
asmeurer
55
@asmeurer Se eligen para que los teoremas sean consistentes y convenientes. No se eligen para facilitar la programación. (Eso a veces es un beneficio secundario, pero a veces también dificultan la programación).
jpmc26
77
@ jpmc26 "Se eligen de modo que los teoremas sean consistentes y convenientes"; asegurarse de que su programa siempre funcione como se espera es esencialmente equivalente a probar un teorema.
artem
2
@ jpmc26 No entiendo por qué mencionó la programación funcional. Probar la corrección de los programas imperativos es bastante posible, siempre ventajoso, y también se puede hacer de manera informal: solo piense un poco y use el sentido matemático común cuando escriba su programa, y ​​pasará menos tiempo probando. Estadísticamente probado en la muestra de uno ;-)
artem
55
@DmitryGrigoryev 1/0 como indefinido es mucho más matemáticamente correcto que 1/0 como infinito.
asmeurer

Respuestas:

145

Devolver un conjunto vacío

Esperaría un conjunto vacío porque:

Hay 0 combinaciones de 4 números del conjunto de 3 cuando solo puedo usar cada número una vez

Ewan
fuente
55
Matemáticamente, sí, pero también es muy probable que sea una fuente de error. Si se espera este tipo de entrada, podría ser mejor requerir que el usuario capture la excepción en una biblioteca de propósito general.
Casey Kuball
56
No estoy de acuerdo con que sea una causa de error "muy probable". Supongamos, por ejemplo, que está implementando un ingenuo algoritmo "matchmaknig" a partir de un conjunto de entrada grande. Puede solicitar todas las combinaciones de dos elementos, encontrar la "mejor" coincidencia entre ellos y luego eliminar ambos elementos y comenzar de nuevo con el nuevo conjunto más pequeño. Eventualmente, su conjunto estará vacío, y no habrá pares que considerar: una excepción en este punto es obstructiva. Creo que hay muchas maneras de terminar en una situación similar.
amalloy
11
En realidad no sabes si esto es un error a los ojos del usuario. El conjunto vacío es algo que el usuario debe verificar, si es necesario. if (result.Any ()) DoSomething (result.First ()); else DoSomethingElse (); es mucho mejor que intentar {result.first (). dosomething ();} catch {DoSomethingElse ();}
Guran
44
@TripeHound Eso es lo que determinó la decisión al final: exigir al desarrollador que use este método para verificar y luego arrojar, tiene un impacto mucho menor que lanzar una excepción que no desean, en términos de esfuerzo de desarrollo, rendimiento del programa y simplicidad del flujo del programa.
anaximander
66
@Darthfett Intente comparar esto con un método de extensión LINQ existente: Where (). Si tengo una cláusula: Donde (x => 1 == 2) no obtengo una excepción, obtengo un conjunto vacío.
Necoras
79

En caso de duda, pregunte a alguien más.

Su función ejemplo tiene una muy similar en Python: itertools.combinations. Vamos a ver cómo funciona:

>>> 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))
[]

Y se siente perfectamente bien para mí. Esperaba un resultado que pudiera repetir y obtuve uno.

Pero, obviamente, si fueras a preguntar algo estúpido:

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

Entonces, diría que si todos sus parámetros se validan pero el resultado es un conjunto vacío, devuelve un conjunto vacío, no es el único que lo hace.


Como dijo @Bakuriu en los comentarios, esto también es lo mismo para una SQLconsulta como SELECT <columns> FROM <table> WHERE <conditions>. Mientras <columns>, <table>, <conditions>así se forman en forma y se refieren a los nombres existentes, se puede construir un conjunto de condiciones que se excluyen mutuamente. La consulta resultante simplemente no generaría filas en lugar de arrojar un InvalidConditionsError.

Mathias Ettinger
fuente
44
Votado en contra porque no es idiomático en el espacio de lenguaje C # / Linq (vea la respuesta de sbecker para ver cómo se manejan los problemas fuera de límites similares en el lenguaje). Si esta fuera una pregunta de Python, sería un +1 de mi parte.
James Snell el
32
@JamesSnell Apenas veo cómo se relaciona con el problema fuera de los límites. No estamos seleccionando elementos por índices para reordenarlos, estamos seleccionando nelementos en una colección para contar la cantidad de formas en que podemos elegirlos. Si en algún momento no quedan elementos al seleccionarlos, entonces no hay (0) forma de seleccionar nelementos de dicha colección.
Mathias Ettinger
1
Aún así, Python no es un buen oráculo para las preguntas de C #: por ejemplo, C # lo permitiría 1.0 / 0, Python no.
Dmitry Grigoryev
2
Las pautas de @MathiasEttinger Python sobre cómo y cuándo usar las excepciones son muy diferentes a las de C #, por lo que usar el comportamiento de los módulos de Python como guía no es una buena métrica para C #.
Ant P
2
En lugar de elegir Python, podríamos elegir SQL. ¿Una consulta bien formada SELECT sobre una tabla produce una excepción cuando el conjunto de resultados está vacío? (spoiler: no!) El "buen formato" de una consulta depende solo de la consulta en sí y de algunos metadatos (por ejemplo, ¿existe la tabla y tiene este campo (con este tipo)?) Una vez que la consulta está bien formada y la ejecuta, espera (posiblemente vacío) resultado válido o una excepción porque el "servidor" tuvo un problema (por ejemplo, el servidor de base de datos se cayó o algo así).
Bakuriu
72

En términos simples:

  • Si hay un error, debe plantear una excepción. Eso puede implicar hacer cosas en pasos en lugar de en una sola llamada encadenada para saber exactamente dónde ocurrió el error.
  • Si no hay ningún error pero el conjunto resultante está vacío, no genere una excepción, devuelva el conjunto vacío. Un conjunto vacío es un conjunto válido.
Tulains Córdova
fuente
23
+1, 0 es un número perfectamente válido y, de hecho, extremadamente importante. Hay tantos casos en los que una consulta podría devolver legítimamente cero aciertos que generar una excepción sería muy molesto.
Kilian Foth
19
buen consejo, pero ¿está clara su respuesta?
Ewan
54
Todavía no soy más sabio en cuanto a si esta respuesta sugiere que el OP debería throwo devolver un conjunto vacío ...
James Snell
77
Su respuesta dice que podría hacerlo, dependiendo de si hay un error, pero no menciona si consideraría que el escenario de ejemplo es un error. En el comentario anterior, usted dice que debería devolver un conjunto vacío "si no hay problemas", pero nuevamente, las condiciones insatisfactorias podrían considerarse un problema, y ​​continúa diciendo que no estoy haciendo excepciones cuando debería. Entonces, ¿qué debo hacer?
anaximander
3
Ah, ya veo, puede que hayas entendido mal; No estoy encadenando nada, estoy escribiendo un método que espero usar en una cadena. Me pregunto si el futuro desarrollador hipotético que usa este método querría que se incluyera en el escenario anterior.
anaximander
53

Estoy de acuerdo con la respuesta de Ewan, pero quiero agregar un razonamiento específico.

Se trata de operaciones matemáticas, por lo que podría ser un buen consejo seguir con las mismas definiciones matemáticas. Desde un punto de vista matemático, el número de conjuntos r de un conjunto n (es decir, nCr ) está bien definido para todos r> n> = 0. Es cero. Por lo tanto, devolver un conjunto vacío sería el caso esperado desde un punto de vista matemático.

sigy
fuente
99
Este es un buen punto. Si la biblioteca tuviera un nivel superior, como elegir combinaciones de colores para hacer una paleta, entonces tiene sentido arrojar un error. Porque sabes que una paleta sin colores no es una paleta. Pero un conjunto sin entradas sigue siendo un conjunto y las matemáticas lo definen como igual a un conjunto vacío.
Capitán Man
1
Mucho mejor que la respuesta de Ewans porque cita la práctica matemática. +1
user949300
24

Encuentro una buena manera de determinar si se usa una excepción, es imaginar a las personas involucradas en la transacción.

Tomando la recuperación del contenido de un archivo como ejemplo:

  1. Por favor, tráeme el contenido del archivo, "no existe.txt"

    a. "Aquí está el contenido: una colección vacía de personajes"

    si. "Erm, hay un problema, ese archivo no existe. ¡No sé qué hacer!"

  2. Por favor, tráeme el contenido del archivo, "existe pero está vacío.

    a. "Aquí está el contenido: una colección vacía de personajes"

    si. "Erm, hay un problema, no hay nada en este archivo. ¡No sé qué hacer!"

Sin duda, algunos estarán en desacuerdo, pero para la mayoría de la gente, "Erm, hay un problema" tiene sentido cuando el archivo no existe y devuelve "una colección vacía de caracteres" cuando el archivo está vacío.

Entonces, aplicando el mismo enfoque a tu ejemplo:

  1. Por favor, dame todas las combinaciones de 4 artículos para {1, 2, 3}

    a. No hay ninguno, aquí hay un conjunto vacío.

    si. Hay un problema, no sé qué hacer.

Una vez más, "Hay un problema" tendría sentido si, por ejemplo, nullse ofrecieran como el conjunto de elementos, pero "aquí hay un conjunto vacío" parece una respuesta sensata a la solicitud anterior.

Si devolver un valor vacío enmascara un problema (por ejemplo, un archivo faltante, a null), generalmente se debe usar una excepción en su lugar (a menos que el idioma elegido admita option/maybetipos, a veces tienen más sentido). De lo contrario, devolver un valor vacío probablemente simplificará el costo y cumplirá mejor con el principio del mínimo asombro.

David Arno
fuente
44
Este consejo es bueno, pero no se aplica a este caso. Un mejor ejemplo: ¿Cuántos días después del 30: th de enero ?: 1 ¿Cuántos días después del 30: th de febrero ?: 0 ¿Cuántos días después del 30: th del madeupuary ?: Excepción ¿Cuántas horas de trabajo en el 30 : th of february: Exception
Guran
9

Como es para una biblioteca de propósito general, mi instinto sería Dejar que el usuario final elija.

Al igual que tenemos Parse()y tenemos TryParse()a nuestra disposición, podemos tener la opción de utilizar según la salida que necesitemos de la función. Pasaría menos tiempo escribiendo y manteniendo un contenedor de funciones para lanzar la excepción que discutiendo sobre la elección de una versión única de la función.

James Snell
fuente
3
+1 porque siempre me gusta la idea de elección y porque este patrón tiende a alentar a los programadores a validar su propia entrada. La mera existencia de una función TryFoo hace obvio que ciertas combinaciones de entrada pueden causar problemas y si la documentación explica cuáles son esos problemas potenciales, el codificador puede verificarlos y manejar entradas inválidas de una manera más limpia de lo que podrían simplemente detectando una excepción o respondiendo a un conjunto vacío. O pueden ser flojos. De cualquier manera, la decisión es de ellos.
aleppke
19
No, un conjunto vacío es la respuesta matemáticamente correcta. Hacer otra cosa es redefinir las matemáticas firmemente acordadas, lo que no debe hacerse por capricho. Mientras estamos en ello, ¿redefiniremos la función de exponenciación para arrojar un error si el exponente es igual a cero, porque decidimos que no nos gusta la convención de que cualquier número elevado a la potencia "cero" es igual a 1?
Comodín el
1
Estaba a punto de responder algo similar. Los métodos de extensión de Linq Single()e SingleOrDefault()inmediatamente surgieron a la mente. Single arroja una excepción si hay cero o> 1 resultado, mientras que SingleOrDefault no arrojará, y en su lugar regresa default(T). Quizás OP podría usar CombinationsOf()y CombinationsOfOrThrow().
RubberDuck
1
@Wildcard: estás hablando de dos resultados diferentes para la misma entrada, que no es lo mismo. El OP solo está interesado en elegir a) el resultado o b) lanzar una excepción que no sea un resultado.
James Snell
44
Singley SingleOrDefault(o Firsty FirstOrDefault) son una historia completamente diferente, @RubberDuck. Retomando mi ejemplo de mi comentario anterior. Está perfectamente bien responder ninguno a la consulta "¿Qué existen incluso primos mayores que dos?" , ya que esta es una respuesta matemáticamente sólida y sensata. De todos modos, si se pregunta "¿Cuál es el primer primo incluso mayor que dos?" No hay una respuesta natural. Simplemente no puede responder la pregunta, porque está pidiendo un solo número. No es ninguno (no es un número único, sino un conjunto), no 0. Por lo tanto, lanzamos una excepción.
Paul Kertscher
4

Debe validar los argumentos proporcionados cuando se llama a su función. Y, de hecho, desea saber cómo manejar argumentos no válidos. El hecho de que varios argumentos dependan unos de otros, no compensa el hecho de que valides los argumentos.

Por lo tanto, votaría por la excepción ArgumentException que proporciona la información necesaria para que el usuario comprenda qué salió mal.

Como ejemplo, verifique la public static TSource ElementAt<TSource>(this IEnumerable<TSource>, Int32)función en Linq. Lo que arroja una ArgumentOutOfRangeException si el índice es menor que 0 o mayor o igual que el número de elementos en la fuente. Por lo tanto, el índice se valida con respecto a los enumerables proporcionados por la persona que llama.

sbecker
fuente
3
+1 por citar un ejemplo de una situación similar en el idioma.
James Snell el
13
Curiosamente, su ejemplo me hizo pensar y me llevó a algo que creo que es un fuerte contraejemplo. Como señalé, este método está diseñado para ser similar a LINQ, de modo que los usuarios puedan encadenarlo junto con otros métodos LINQ. Si lo hace new[] { 1, 2, 3 }.Skip(4).ToList();, obtiene un conjunto vacío, lo que me hace pensar que devolver un conjunto vacío es quizás la mejor opción aquí.
anaximandro el
14
Este no es un problema de semántica del lenguaje, la semántica del dominio del problema ya proporciona la respuesta correcta, es decir, devolver el conjunto vacío. Hacer cualquier otra cosa viola el principio de mínimo asombro para alguien que trabaja en el dominio del problema combinatorio.
Ukko
1
Estoy empezando a entender los argumentos para devolver un conjunto vacío. Por qué tendría sentido. Al menos para este ejemplo particular.
sbecker
2
@sbecker, para traer el punto a casa: si la pregunta fuera "¿ Cuántas combinaciones hay de ...", entonces "cero" sería una respuesta completamente válida. Del mismo modo, "¿Cuáles son las combinaciones tales que ..." tiene el conjunto vacío como una respuesta completamente válida. Si la pregunta fuera: "¿Cuál es la primera combinación tal que ...", entonces y solo entonces sería apropiada una excepción, ya que la pregunta no tiene respuesta. Véase también el comentario de Pablo K .
Comodín el
3

Debe hacer uno de los siguientes (aunque continúe lanzando constantemente problemas básicos como un número negativo de combinaciones):

  1. Proporcione dos implementaciones, una que devuelva un conjunto vacío cuando las entradas juntas no tengan sentido y otra que arroje. Intenta llamarlos CombinationsOfy CombinationsOfWithInputCheck. O lo que quieras. Puede revertir esto para que el que verifique la entrada sea el nombre más corto y el de la lista CombinationsOfAllowInconsistentParameters.

  2. Para los métodos de Linq, devuelva el vacío IEnumerableen la premisa exacta que ha esbozado. Luego, agregue estos métodos de Linq a su biblioteca:

    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;
          }
       }
    }

    Finalmente, use eso así:

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

    o así:

    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();

    Tenga en cuenta que se requiere que el método privado sea diferente del público para que se produzca el comportamiento de lanzamiento o acción cuando se crea la cadena linq en lugar de algún tiempo después cuando se enumera. Usted quiere que tirar de inmediato.

    Sin embargo, tenga en cuenta que, por supuesto, tiene que enumerar al menos el primer elemento para determinar si hay algún elemento. Este es un inconveniente potencial que creo que se ve mitigado principalmente por el hecho de que cualquier futuro espectador puede razonar fácilmente que un ThrowIfEmptymétodo tiene que enumerar al menos un elemento, por lo que no debería sorprenderse al hacerlo. Pero nunca se sabe. Podrías hacer esto más explícito ThrowIfEmptyByEnumeratingAndReEmittingFirstItem. Pero eso parece una exageración gigantesca.

¡Creo que el # 2 es bastante bueno! Ahora el poder está en el código de llamada, y el próximo lector del código comprenderá exactamente lo que está haciendo, y no tendrá que lidiar con excepciones inesperadas.

ErikE
fuente
Por favor explica que quieres decir. ¿Cómo es que algo en mi publicación "no se comporta como un conjunto" y por qué es algo malo?
ErikE
Básicamente estás haciendo lo mismo de mi respuesta, en lugar de ajustar la consulta que pasaste a consultar una condición If, el resultado no cambia uu
GameDeveloper
No puedo ver cómo mi respuesta es "básicamente haciendo lo mismo de" su respuesta. Parecen completamente diferentes, para mí.
ErikE
Básicamente solo estás imponiendo la misma condición de "mi mala clase". Pero no estás diciendo eso ^^. ¿Está de acuerdo con que está imponiendo la existencia de 1 elemento en el resultado de la consulta?
GameDeveloper
Tendrás que hablar más claramente para los programadores evidentemente estúpidos que todavía no entienden de lo que estás hablando. ¿Qué clase es mala? ¿Porque es malo? No estoy haciendo cumplir nada. Estoy permitiendo que el USUARIO de la clase decida el comportamiento final en lugar del CREADOR de la clase. Esto le permite a uno usar un paradigma de codificación consistente donde los métodos de Linq no arrojan al azar debido a un aspecto computacional que no es realmente una violación de las reglas normales, como si solicitara -1 elementos a la vez.
ErikE
2

Puedo ver argumentos para ambos casos de uso: una excepción es excelente si el código descendente espera conjuntos que contienen datos. Por otro lado, simplemente un conjunto vacío es genial si se espera esto.

Creo que depende de las expectativas de la persona que llama si se trata de un error o un resultado aceptable, por lo que transferiría la elección a la persona que llama. Tal vez introducir una opción?

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

Christian Sauer
fuente
10
Mientras llego a donde vas con esto, intento evitar los métodos que tienen muchas opciones que modifican su comportamiento. Todavía no estoy 100% contento con la enumeración de modos que ya existe; Realmente no me gusta la idea de un parámetro de opción que cambie el comportamiento de excepción de un método. Siento que si un método necesita lanzar una excepción, necesita lanzar; si desea ocultar esa excepción, esa es su decisión como código de llamada, y no es algo que pueda hacer que mi código haga con la opción de entrada correcta.
anaximander
Si la persona que llama espera un conjunto que no esté vacío, entonces le corresponde manejar un conjunto vacío o lanzar una excepción. En lo que respecta a la persona que llama, un conjunto vacío es una respuesta perfectamente buena. Y puede tener más de una persona que llama, con diferentes opiniones sobre si los conjuntos vacíos están bien o no.
gnasher729
2

Hay dos enfoques para decidir si no hay una respuesta obvia:

  • Escriba el código asumiendo primero una opción, luego la otra. Considere cuál funcionaría mejor en la práctica.

  • Agregue un parámetro booleano "estricto" para indicar si desea que los parámetros se verifiquen estrictamente o no. Por ejemplo, Java SimpleDateFormattiene un setLenientmétodo para intentar analizar entradas que no coinciden completamente con el formato. Por supuesto, tendría que decidir cuál es el valor predeterminado.

Bromista alegre
fuente
2

Según su propio análisis, devolver el conjunto vacío parece claramente correcto: incluso lo ha identificado como algo que algunos usuarios realmente desean y no ha caído en la trampa de prohibir algún uso porque no puede imaginar que los usuarios quieran usarlo alguna vez. de esa manera.

Si realmente crees que algunos usuarios pueden querer forzar retornos no vacíos, entonces dales una forma de pedir ese comportamiento en lugar de forzarlo a todos. Por ejemplo, podrías:

  • Conviértalo en una opción de configuración en cualquier objeto que esté realizando la acción para el usuario.
  • Conviértalo en una bandera que el usuario pueda pasar opcionalmente a la función.
  • Proporcione un AssertNonemptycheque que puedan poner en sus cadenas.
  • Haga dos funciones, una que afirme que no está vacía y otra que no.

fuente
Esto no parece ofrecer nada sustancial sobre los puntos hechos y explicados en las 12 respuestas anteriores
mosquito el
1

Realmente depende de lo que sus usuarios esperan obtener. Por ejemplo (un poco no relacionado) si su código realiza la división, puede lanzar una excepción o retorno Info NaNcuando divide por cero. Sin embargo, ninguno de los dos es correcto o incorrecto:

  • Si regresas Infa una biblioteca de Python, la gente te atacará por esconder errores
  • Si genera un error en una biblioteca de Matlab, las personas lo atacarán por no procesar los datos con valores faltantes.

En su caso, elegiría la solución que será menos sorprendente para los usuarios finales. Como está desarrollando una biblioteca que se ocupa de conjuntos, un conjunto vacío parece algo con lo que sus usuarios esperarían lidiar, por lo que devolverlo parece algo sensato. Pero puedo estar equivocado: tiene una comprensión mucho mejor del contexto que cualquier otra persona aquí, por lo que si espera que sus usuarios confíen en que el conjunto no siempre esté vacío, debe lanzar una excepción de inmediato.

Las soluciones que permiten al usuario elegir (como agregar un parámetro "estricto") no son definitivas, ya que reemplazan la pregunta original por una nueva equivalente: "¿Qué valor de strictdebería ser el predeterminado?"

Dmitry Grigoryev
fuente
2
Pensé en usar el argumento NaN en mi respuesta y comparto su opinión. No podemos decir más sin conocer el dominio de la OP
GameDeveloper
0

Es una concepción común (en matemáticas) que cuando selecciona elementos sobre un conjunto, no puede encontrar ningún elemento y, por lo tanto, obtiene un conjunto vacío . Por supuesto, debe ser coherente con las matemáticas si sigue este camino:

Reglas comunes establecidas:

  • Set.Foreach (predicado); // siempre devuelve verdadero para conjuntos vacíos
  • Set.Exists (predicado); // siempre devuelve falso para conjuntos vacíos

Tu pregunta es muy sutil:

  • Podría ser que la entrada de su función tenga que respetar un contrato : en ese caso, cualquier entrada no válida debería generar una excepción, eso es todo, la función no funciona bajo parámetros regulares.

  • Podría ser que la entrada de su función tenga que comportarse exactamente como un conjunto y, por lo tanto, debería poder devolver un conjunto vacío.

Ahora, si estuviera dentro de ti, iría al modo "Set", pero con un gran "PERO".

Suponga que tiene una colección que "por hipotesis" debería tener solo alumnas:

class FemaleClass{

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

Ahora su colección ya no es un "conjunto puro", porque tiene un contrato y, por lo tanto, debe hacer cumplir su contrato con una excepción.

Cuando utiliza sus funciones de "conjunto" de una manera pura, no debe lanzar excepciones en el caso de un conjunto vacío, pero si tiene colecciones que ya no son "conjuntos puros", entonces debe lanzar excepciones cuando sea apropiado .

Siempre debe hacer lo que se siente más natural y consistente: para mí, un conjunto debe adherirse a las reglas establecidas, mientras que las cosas que no son conjuntos deben tener sus reglas correctamente pensadas.

En su caso, parece una buena idea hacer:

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;
}

Pero no es realmente una buena idea, ahora tenemos un estado no válido que puede ocurrir en tiempo de ejecución en momentos aleatorios, sin embargo, eso está implícito en el problema, por lo que no podemos eliminarlo, seguro que podemos mover la excepción dentro de algún método de utilidad (es exactamente el mismo código, movido en diferentes lugares), pero eso está mal y básicamente lo mejor que puedes hacer es seguir las reglas establecidas .

De hecho, agregar nueva complejidad solo para mostrar que puede escribir métodos de consultas linq parece no valer para su problema , estoy bastante seguro de que si OP puede decirnos más sobre su dominio, probablemente podríamos encontrar el lugar donde realmente se necesita la excepción (si en absoluto, es posible que el problema no requiera ninguna excepción).

Desarrollador de juegos
fuente
El problema es que está utilizando objetos definidos matemáticamente para resolver un problema, pero el problema en sí mismo puede no requerir un objeto definido matemáticamente como respuesta (de hecho, aquí devuelve una lista). Todo depende del nivel superior del problema, independientemente de cómo lo resuelva, eso se llama encapsulación / ocultación de información.
GameDeveloper
La razón por la cual su "SomeMethod" ya no debería comportarse como un conjunto es que está solicitando una información "fuera de los conjuntos", específicamente mire la parte comentada "&& someInputSet.NotEmpty ()"
GameDeveloper
1
¡NO! No deberías lanzar una excepción en tu clase femenina. Debería usar el patrón Builder que exige que ni siquiera pueda OBTENER una instancia de a FemaleClassmenos que solo tenga hembras (no se puede crear públicamente, solo la clase Builder puede entregar una instancia), y posiblemente tenga al menos una hembra en ella . Entonces, su código en todo el lugar que toma FemaleClasscomo argumento no tiene que manejar excepciones porque el objeto está en el estado incorrecto.
ErikE
Supone que la clase es tan simple que puede imponer sus condiciones previas simplemente por constructor, en realidad. Tu argumento es defectuoso. Si prohíbe ya no tener mujeres, entonces o no puede tener un método para eliminar a los estudiantes de la clase o su método tiene que arrojar una excepción cuando queda 1 estudiante. Acabas de trasladar mi problema a otra parte. El punto es que siempre habrá algún estado de condición previa / función que no se puede mantener "por diseño" y que el usuario se va a romper: ¡por eso existen excepciones! Esto es bastante teórico, espero que el voto negativo no sea suyo.
GameDeveloper
El voto negativo no es mío, pero si lo fuera, estaría orgulloso de ello. Voto abajo con cuidado pero con convicción. Si la regla de su clase es que DEBE tener un elemento, y todos los elementos deben cumplir una condición específica, entonces permitir que la clase esté en un estado inconsistente es una mala idea. ¡Mover el problema a otra parte es exactamente mi intención! Yo quiero el problema que se produzca en el momento de la construcción, no en tiempo de uso. El punto de usar una clase de Constructor en lugar de un lanzamiento de constructor es que puede generar un estado muy complejo en muchas piezas y partes a través de inconsistencias.
ErikE