El servicio debe lanzar una excepción o regresar cuando no hay elementos especificados para su eliminación

12

Tengo un fragmento de código que se puede representar como:

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

Ahora me pregunto si este es el enfoque correcto o si debo lanzar una excepción. Puedo evitar excepciones, porque regresar sería lo mismo que iterar sobre una colección vacía, lo que significa que no se ejecuta ningún código importante de todos modos, pero por otro lado, posiblemente esté ocultando problemas en algún lugar del código, porque ¿por qué alguien querría llamar? DeleteItemscon nullparámetro? Esto puede indicar que hay un problema en otro lugar del código.

Este es un problema que generalmente tengo con los métodos en los servicios, porque la mayoría de ellos hacen algo y no devuelven un resultado, por lo que si alguien pasa información no válida, el servicio no tiene nada que hacer, por lo que se devuelve.

FCin
fuente
2
Esta es solo mi opinión personal, pero siempre he encontrado que tiene más sentido que un método arroje una excepción cuando hace algo no deseado (excepcional). En su caso, lanzaría una InvalidOperationException si alguien intentara eliminar elementos nulos / 0.
Falgantil
1
@gnat Mi pregunta es específicamente sobre los servicios, debido a cómo se usan y su propósito. Este "duplicado" solo habla de los casos generales y la respuesta principal incluso se contradice en el contexto de mi método exacto.
FCen
2
¿Es posible que lo enumerable esté vacío en lugar de nulo? ¿Manejarías eso de manera diferente?
JAD
13
@Falgantil Puedo ver que null es digno de una excepción, pero creo que es absurdo lanzar una excepción en una lista vacía.
Kevin

Respuestas:

58

Estas son dos preguntas diferentes.

¿Deberías aceptar null? Eso depende de su política general acerca nullde la base del código. En mi opinión, prohibir en nulltodas partes, excepto donde está documentado explícitamente, es una muy buena práctica, pero es una práctica aún mejor cumplir con la convención que su base de código ya tiene.

¿Deberías aceptar la colección vacía? En mi opinión: SÍ, absolutamente. Es mucho más esfuerzo restringir a todas las personas que llaman a colecciones no vacías que hacer lo matemáticamente correcto, incluso si sorprende a algunos desarrolladores que están dudosos con el concepto de cero.

Kilian Foth
fuente
99
En cualquier caso, si vas a lanzar, entonces no hay tanta utilidad en el lanzamiento AhaINoticedYouPassingInNullExceptioncuando el tiempo de ejecución ya te brinda la posibilidad de tirar NullReferenceExceptionpor ti, sin que escribas ningún código. Hay alguna utilidad (por ejemplo, su herramienta de cobertura de código le dirá si tiene o no una prueba de unidad para pasar nula en el primer caso pero no en el último), pero ninguna de las excepciones debe probarse / capturarse en cada llamada al servicio, porque generalmente es un error del programador.
Steve Jessop
2
... solo debería captar de inmediato las excepciones generadas debido a parámetros incompletos, si sabe que lo que está pasando es nulo en algún caso esperado, y desea activamente que la función que está llamando lo valide. Como dice Kilian en la respuesta, su política general podría ser prohibir nulos en todas partes donde no esté explícitamente permitido. Entonces no lo atraparía NullReferenceExceptionni en AhaINoticedYouPassingInNullExceptionninguna parte, excepto en el código de recuperación de desastres de alto nivel. Y ese controlador de excepciones probablemente debería crear un informe de error :-)
Steve Jessop
2
@FCin: el diseño de su interfaz debe coincidir con lo que los usuarios realmente necesitan fuera del servicio. Entonces, si cada persona que llama va a poner try / catch alrededor de esto, entonces este método o algún método de conveniencia junto con él debería verificar e ignorar nulo, porque eso es lo que exige su público. Sin embargo, como dice Kilian, generalmente funciona mejor tratar los nulos de propagación como un error de programación y no tratar de continuar en cada sitio de llamadas. Para el caso, ¿qué pasa si el servicio en el que se llama este método es null: los controladores contienen repetitivo para atrapar y continuar en ese caso también?
Steve Jessop
2
... si es así, parece que la base del código trata constantemente los componentes faltantes como una condición esperada que debe manejar a bajo nivel y continuar. Puede preguntar razonablemente, "¿de quién es la responsabilidad de proporcionar un servicio y un número enumerable para que esta operación pueda continuar?". Si la respuesta es "alguien", entonces su función está verificando las condiciones previas , y el resultado de una condición previa fallida normalmente sería una excepción atrapada en un nivel alto, no en cada llamada. Si las cosas requeridas para la operación son realmente opcionales, entonces su función está manejando un caso de uso esperado .
Steve Jessop
2
Lanzar explícitamente un ArgumentNullExceptiones superior a permitir que el código interior arroje un NullReferenceException: simplemente mirando el mensaje de excepción, es obvio que es un error de entrada en lugar de un error lógico en el cuerpo del método en el sitio de lanzamiento, y garantizar la salida anticipada significa que es más probable que haya dejado otros datos en un estado válido en lugar de mutar parcialmente de un nulo inesperado más adelante.
Miral
9

Valor nulo

Como @KilianFoth ya dijo, cumpla con su política general. Si se trata nullcomo una "taquigrafía" para una lista vacía, hágalo de esa manera.

Si no tiene una política coherente sobre nullvalores, le recomendaría la siguiente:

nulldebe reservarse para representar situaciones que no se pueden expresar con el tipo "normal", por ejemplo, usar nullpara representar "No sé". Y esa es una buena opción, ya que todos los que intenten usar este valor descuidadamente obtendrán una excepción, que es lo correcto.

Usar nullcomo una abreviatura para una lista vacía no califica de esa manera, ya que ya hay una representación perfecta, siendo una lista con cero elementos. Y es una mala elección técnica, ya que obliga a cada parte de su código que trata con listas a verificar la taquigrafía válida null.

Lista vacía

Para un DeleteItems()método, pasar una lista vacía efectivamente significa no hacer nada. Permitiría eso como un argumento, no arrojando una excepción, solo regresando rápidamente.

Por supuesto, la persona que llama podría verificar primero cero elementos y omitir la DeleteItems()llamada en ese caso. Si hablamos de una API web, por razones de eficiencia, la persona que llama debería hacerlo para evitar el tráfico innecesario y las latencias de ida y vuelta. Pero no creo que su API deba hacer cumplir eso.

Ralf Kleberhoff
fuente
1

Lanza una excepción y maneja nulos en el código de llamada.

Como regla de diseño, trate de evitar valores nulos como parámetros. Reducirá NullPointerExceptions en general, ya que los nulos realmente serán una excepción.

Además de eso, mira el resto de tu código. Si este es un patrón común en su proyecto, manténgase constante.

serprime
fuente
Estoy en medio de "configurar" cómo se estructuran mis servicios, por lo que es posible que se requiera una refactorización para generar entradas no válidas, pero no mucho. Pero estoy de acuerdo con su punto de que los nulos se convertirán en una excepción una vez que comience a arrojar en lugar de ocultarlos.
FCen
0

En términos generales, las excepciones de lanzamiento deben reservarse para situaciones excepcionales, es decir, si el código no tiene un curso de acción razonable para tomar en el contexto actual.

Puede aplicar ese proceso de pensamiento a esta situación. Dado que esta es una clase pública con un método público, tiene una API pública y, en teoría, no tiene control sobre lo que se le pasa.

Su código no tiene contexto sobre lo que lo llama (como no debería), y no hay nada que pueda hacer razonablemente con un valor nulo. Esto sin duda sería un candidato para un ArgumentNullException.

richzilla
fuente
"si el código no tiene un curso de acción razonable para tomar en el contexto actual". Pero mi pensamiento es que tiene un curso de acción razonable, que es volver vacío. Hace exactamente lo mismo que cuando se pasa una colección vacía, así que si permito la colección vacía, ¿por qué no permitiría null?
FCen
1
@FCin Debido a una colección vacía, sabes que está vacía. Contigo nullno tienes colección. Si se supone / se espera que el código de llamada proporcione una colección al método, hay algo mal, por lo que debe arrojarlo. La única razón para tratar un nulo de la misma manera que una colección vacía es si razonablemente puede esperar que el código de llamada produzca colecciones nulas. Como señalaron otras respuestas, la mayoría de las veces, algo nulles una anomalía. Si los trata igual que las colecciones vacías, posiblemente esté ignorando un problema en otra parte del código.
JAD
@FCin: también, si se nullentiende que está vacío, ese contexto es específico de este método. En otro lugar de la misma base de código, es posible que tenga un parámetro IEnumerableo IContainerque realice algún tipo de filtrado, que nullpodría significar "sin filtro" (permitir todo), mientras que un filtro vacío significa que no permite nada. Y en otro lugar, nullpodría significar explícitamente "No sé". Entonces, dado que nullno siempre significa lo mismo en todas partes, es una buena idea no inventar ningún significado para él a menos que ese significado sea necesario o al menos útil para alguien.
Steve Jessop
0

Esta pregunta parece no ser sobre excepciones, sino sobre nullser un argumento válido.

Entonces, antes que nada, debe decidir si nulles un valor permitido para el argumento de ese método. Si es así, entonces no necesita una excepción. Si no es así, entonces necesita una excepción.

Es deseable si desea permitirlo nullo no, como lo demuestran muchos y muchos éxitos de Google al respecto. Es decir, no obtendrá una respuesta clara, y de alguna manera depende de la opinión y la tradición en el lugar donde está trabajando.

Otra área de disputa es si una función de biblioteca debe ser realmente estricta sobre tales cosas, o lo más indulgente posible, siempre que no esté tratando de "arreglar" parámetros erróneos. Compare esto con el mundo de los protocolos de red, transferencia de correo, etc. (que son contratos de interfaz, al igual que los métodos de programación). Allí, por lo general, la política es que el remitente debe ajustarse lo más estrictamente posible al protocolo, mientras que el receptor debe hacer todo lo posible para poder trabajar con lo que se avecina.

Por lo tanto, debe decidir: ¿es realmente el trabajo de un método de biblioteca imponer políticas bastante grandes como el nullmanejo? Especialmente si su biblioteca también es utilizada por otras personas (que podrían tener políticas diferentes).

Probablemente me equivocaría al permitir nullvalores, definir y documentar su semántica (es decir, null = empty arrayen este caso), y no lanzar una excepción, a menos que el 99% de su otro código similar (código de estilo de "biblioteca") lo haga de la otra manera 'redondo.

AnoE
fuente
1
"¿Es realmente el trabajo de un método de biblioteca hacer cumplir una política bastante grande como el manejo nulo?" - siguiendo esa línea de pensamiento se encuentran los modos de afirmación y depuración, que le permiten ayudar a hacer cumplir una política, en lugar de hacerla cumplir, si eso es lo que quiere hacer. Entonces, si vas a tragar nullel principio de ser liberal en lo que aceptas, entonces iniciar sesión o incluso tirar sería útil para las personas cuya intención es ser estrictas en lo que pasan, pero que han desordenado su código y sus pruebas unitarias en la medida en que pasan de nulltodos modos.
Steve Jessop
@SteveJessop, realmente no sé si estás argumentando en contra del espíritu de mi respuesta, o si continúas en su línea. Dicho esto, no estoy sugiriendo simplemente tragar null, sino declarar abiertamente en el contrato del método que es aceptable (o no, dependiendo de la solución que surja), y luego la pregunta de si lanzar una excepción (o no) se resuelve solo.
AnoE