La semana pasada, tuvimos una acalorada discusión sobre el manejo de nulos en la capa de servicio de nuestra aplicación. La pregunta está en el contexto .NET, pero será la misma en Java y en muchas otras tecnologías.
La pregunta era: ¿siempre debe verificar si hay nulos y hacer que su código funcione sin importar qué, o dejar que una excepción brote cuando se recibe un nulo inesperadamente?
Por un lado, verificar si es nulo donde no lo espera (es decir, no tener una interfaz de usuario para manejarlo) es, en mi opinión, lo mismo que escribir un bloque de prueba con captura vacía. Solo estás ocultando un error. El error puede ser que algo ha cambiado en el código y nulo es ahora un valor esperado, o hay algún otro error y se pasa la identificación incorrecta al método.
Por otro lado, verificar nulos puede ser un buen hábito en general. Además, si hay una comprobación, la aplicación puede seguir funcionando, y solo una pequeña parte de la funcionalidad no tendrá ningún efecto. Luego, el cliente puede informar un pequeño error como "no se puede eliminar el comentario" en lugar de un error mucho más grave como "no se puede abrir la página X".
¿Qué práctica sigues y cuáles son tus argumentos a favor o en contra de cualquier enfoque?
Actualizar:
Quiero agregar algunos detalles sobre nuestro caso particular. Estábamos recuperando algunos objetos de la base de datos e hicimos algún procesamiento en ellos (digamos, construir una colección). El desarrollador que escribió el código no anticipó que el objeto podría ser nulo, por lo que no incluyó ninguna verificación, y cuando se cargó la página hubo un error y no se cargó toda la página.
Obviamente, en este caso debería haber habido un cheque. Luego nos pusimos en una discusión sobre si cada objeto que se procesa debe verificarse, incluso si no se espera que falte, y si el procesamiento final debe abortarse en silencio.
El beneficio hipotético sería que la página continuará funcionando. Piense en los resultados de una búsqueda en Stack Exchange en diferentes grupos (usuarios, comentarios, preguntas). El método podría verificar nulo y anular el procesamiento de los usuarios (que debido a un error es nulo) pero devolver las secciones de "comentarios" y "preguntas". La página continuaría funcionando, excepto que faltará la sección "usuarios" (que es un error). ¿Deberíamos fallar temprano y romper toda la página o continuar trabajando y esperar a que alguien note que falta la sección "usuarios"?
fuente
assert(foo != null, "foo is web control within the repeater, there's no reason to expect it to be null, etc, etc...");
Respuestas:
La pregunta no es tanto si debe verificar
null
o dejar que el tiempo de ejecución arroje una excepción; es cómo debes responder a una situación tan inesperada.Sus opciones, entonces, son:
NullReferenceException
) y déjala burbujear; Si no realiza lanull
verificación usted mismo, esto es lo que sucede automáticamente.null
cheque o atrapando aNullReferenceException
y lanzando la excepción más específica.No hay una regla general sobre cuál es la mejor solución. Personalmente, diría:
fuente
En mi humilde opinión, tratar de manejar valores nulos que no esperas conduce a un código demasiado complicado. Si no espera nulo, déjelo en claro arrojándolo
ArgumentNullException
. Me siento realmente frustrado cuando la gente comprueba si el valor es nulo y luego intenta escribir un código que no tiene ningún sentido. Lo mismo se aplica al usoSingleOrDefault
(o peor aún, a la recolección) cuando alguien realmente esperaSingle
y en muchos otros casos cuando las personas tienen miedo (realmente no sé de qué) enunciar claramente su lógica.fuente
ArgumentNullException
, uno debe usar contratos de código (a menos que sea un código fuente anterior a 2010 que no admite contratos de código).ArgumentNullException
recuento como "manejo" del valor nulo: evita una excepción de referencia nula posterior.ArgumentNullException
deja muy claro cuál es el problema y cómo solucionarlo. C # no tiende a usar "indefinido". Si llamas a algo de forma indefinida, entonces la forma en que lo llamas no tiene sentido. Una excepción es la forma correcta de indicar eso. Ahora, a veces haré que los métodos de análisis que devuelven sean nulos siint
(por ejemplo) no se puede analizar. Pero ese es un caso en el que un resultado no analizable es una posibilidad legítima en lugar de un error de uso. Ver también este artículo .El hábito de verificar
null
según mi experiencia proviene de antiguos desarrolladores de C o C ++, en esos lenguajes tiene una buena posibilidad de ocultar un error grave cuando no lo está buscandoNULL
. Como sabe, en Java o C # las cosas son diferentes, usar una referencia nula sin verificarnull
causará una excepción, por lo tanto, el error no se ocultará en secreto siempre que no lo ignore en el lado de la llamada. Entonces, en la mayoría de los casos, verificar explícitamentenull
no tiene mucho sentido y complica demasiado el código más de lo necesario. Es por eso que no considero que la verificación nula sea un buen hábito en esos idiomas (al menos, no en general).Por supuesto, hay excepciones a esa regla, aquí están las que se me ocurren:
desea un mensaje de error mejor, legible por humanos, que le indique al usuario en qué parte del programa se produjo la referencia nula incorrecta. Por lo tanto, su prueba para tiros nulos es solo una excepción diferente, con un texto de error diferente. Obviamente, ningún error quedará enmascarado de esa manera.
desea hacer que su programa "falle más temprano". Por ejemplo, desea verificar un valor nulo de un parámetro constructor, donde la referencia del objeto de lo contrario solo se almacenaría en una variable miembro y se usaría más adelante.
puede lidiar con la situación de una referencia nula de forma segura, sin el riesgo de fallas posteriores y sin enmascarar un error grave.
desea un tipo de señal de error completamente diferente (por ejemplo, devolver un código de error) en su contexto actual
fuente
Utilice afirmaciones para probar e indicar condiciones previas / posteriores e invariantes para su código.
Hace que sea mucho más fácil comprender qué espera el código y qué se puede esperar que maneje.
Una afirmación, IMO, es una verificación adecuada porque es:
Creo que el código autodocumentado es importante, por lo tanto, tener un cheque es bueno. La programación defensiva, a prueba de fallas, facilita el mantenimiento, que es donde generalmente se pasa la mayor parte del tiempo.
Actualizar
Wrt su elaboración. Por lo general, es bueno darle al usuario final una aplicación que funciona bien bajo errores, es decir, muestra la mayor cantidad posible. La robustez es buena, porque el cielo solo sabe lo que se romperá en un momento crítico.
Sin embargo, los desarrolladores y evaluadores deben estar al tanto de los errores lo antes posible, por lo que probablemente desee un marco de registro con un enlace que pueda mostrar una alerta al usuario de que hubo un problema. La alerta probablemente debería mostrarse a todos los usuarios, pero probablemente puede adaptarse de manera diferente según el entorno de tiempo de ejecución.
fuente
Falla temprano, falla a menudo.
null
es uno de los mejores valores inesperados que puede obtener, porque puede fallar rápidamente tan pronto como intente usarlo. Otros valores inesperados no son tan fáciles de detectar. Dado quenull
automáticamente falla para usted cada vez que intenta usarlo, diría que no requiere una verificación explícita.fuente
Verificar un nulo debería ser tu segunda naturaleza
Su API será utilizada por otras personas y es probable que sus expectativas sean diferentes a las suyas.
Las pruebas unitarias exhaustivas normalmente resaltan la necesidad de una verificación de referencia nula
Verificar nulos no implica declaraciones condicionales. Si le preocupa que la verificación nula haga que su código sea menos legible, puede considerar los contratos de código .NET.
Considere instalar ReSharper (o similar) para buscar en su código las comprobaciones de referencias nulas faltantes
fuente
Consideraría la pregunta desde el punto de vista de la semántica, es decir, preguntarme qué representa un puntero NULO o un argumento de referencia nulo.
Si una función o método foo () tiene un argumento x de tipo T, x puede ser obligatorio u opcional .
En C ++ puede pasar un argumento obligatorio como
En ambos casos, no tiene el problema de verificar un puntero NULL. Por lo tanto, en muchos casos, no es necesario un control de puntero nulo en absoluto para los argumentos obligatorios.
Si está pasando un argumento opcional , puede usar un puntero y usar el valor NULL para indicar que no se dio ningún valor:
Una alternativa es usar un puntero inteligente como
En este caso, es probable que desee comprobar el puntero en el código, porque hace una diferencia para el método foo () si x contiene un valor o no tiene ningún valor.
El tercer y último caso es que usa un puntero para un argumento obligatorio . En este caso, llamar a foo (x) con x == NULL es un error y debe decidir cómo manejar esto (lo mismo que con un índice fuera de límites o cualquier entrada no válida):
Además de una mejor manera de fallar (dar al usuario más información), una ventaja del segundo enfoque es que es más probable que se encuentre el error: el programa fallará cada vez que se llame al método con los argumentos incorrectos, mientras que con En el enfoque 1, el error solo aparecerá si se ejecuta una instrucción que hace referencia al puntero (por ejemplo, si se ingresa la rama derecha de una instrucción if). Entonces el enfoque 2 ofrece una forma más fuerte de "fallar temprano".
En Java, tiene menos opciones porque todos los objetos se pasan utilizando referencias que siempre pueden ser nulas. Nuevamente, si el valor nulo representa un valor NINGUNO de un argumento opcional, entonces verificar el valor nulo (probablemente) será parte de la lógica de implementación.
Si el valor nulo es una entrada no válida, entonces tiene un error y yo aplicaría consideraciones similares al caso de los punteros de C ++. Dado que en Java puede detectar excepciones de puntero nulo, el enfoque 1 anterior es equivalente al enfoque 2 si el método foo () elimina la referencia de todos los argumentos de entrada en todas las rutas de ejecución (lo que probablemente ocurre muy a menudo para métodos pequeños).
Resumiendo
EDITAR
Gracias a Stilgar por más detalles.
En su caso, parece que tiene nulo como resultado de un método. Una vez más, creo que primero debe aclarar (y corregir) la semántica de sus métodos antes de tomar una decisión.
Entonces, tiene el método m1 () que llama al método m2 () y m2 () devuelve una referencia (en Java) o un puntero (en C ++) a algún objeto.
¿Cuál es la semántica de m2 ()? ¿Debería m2 () devolver siempre un resultado no nulo? Si este es el caso, entonces un resultado nulo es un error interno (en m2 ()). Si marca el valor de retorno en m1 (), puede tener un manejo de errores más robusto. Si no lo hace, probablemente tendrá una excepción, tarde o temprano. Tal vez no. Espero que la excepción se produzca durante las pruebas y no después de la implementación. Pregunta : si m2 () nunca debe devolver nulo, ¿por qué devuelve nulo? Tal vez debería lanzar una excepción en su lugar? m2 () probablemente tenga errores.
La alternativa es que devolver nulo es parte de la semántica del método m2 (), es decir, tiene un resultado opcional, que debe documentarse en la documentación Javadoc del método. En este caso, está bien tener un valor nulo. El método m1 () debería verificarlo como cualquier otro valor: no hay ningún error aquí, pero probablemente verificar si el resultado es nulo es solo parte de la lógica del programa.
fuente
Mi enfoque general es nunca probar una condición de error que no sabe cómo manejar . Entonces la pregunta que hay que responder en cada caso concreto se convierte en: ¿puedes hacer algo razonable en presencia de lo inesperado
null
valor?Si puede continuar de forma segura sustituyendo otro valor (una colección vacía, digamos, o un valor o instancia predeterminado), entonces hágalo por todos los medios. El ejemplo de @ Shahbaz de World of Warcraft de sustituir una imagen por otra cae en esa categoría; Es probable que la imagen en sí sea solo decoración y no tenga un impacto funcional . (En una compilación de depuración, usaría un color brillante para llamar la atención sobre el hecho de que se ha producido una sustitución, pero eso depende en gran medida de la naturaleza de la aplicación). Sin embargo, registre detalles sobre el error, para que sepa que está ocurriendo; de lo contrario, ocultará un error que probablemente quiera saber. Ocultarlo al usuario es una cosa (nuevamente, suponiendo que el procesamiento pueda continuar de manera segura); ocultarlo del desarrollador es otra muy distinta.
Si no puede continuar de manera segura en presencia de un valor nulo, falle con un error sensible; en general, prefiero fallar lo antes posible cuando es obvio que la operación no puede tener éxito, lo que implica verificar las condiciones previas. Hay momentos en los que no desea fallar de inmediato, pero difiere la falla el mayor tiempo posible; Esta consideración surge, por ejemplo, en aplicaciones relacionadas con la criptografía, donde los ataques de canal lateral podrían divulgar información que no desea que se conozca. Al final, deje que el error aparezca en un controlador de errores general, que a su vez registra detalles relevantes y muestra un mensaje de error agradable (por muy bueno que sea) para el usuario.
También vale la pena señalar que la respuesta adecuada puede muy bien diferir entre las compilaciones de prueba / QA / depuración y las compilaciones de producción / lanzamiento. En las compilaciones de depuración, iría por fallar temprano y duro con mensajes de error muy detallados, para que sea completamente obvio que hay un problema y permitir que una autopsia determine qué se debe hacer para solucionarlo. Las compilaciones de producción, cuando se enfrentan a errores recuperables de forma segura , probablemente deberían favorecer la recuperación.
fuente
Claro
NULL
que no se espera , ¡pero siempre hay errores!Creo que debe verificar
NULL
si no lo espera o no. Déjame darte ejemplos (aunque no están en Java).En World of Warcraft, debido a un error, la imagen de uno de los desarrolladores apareció en un cubo para muchos objetos. Imagínese si el motor de gráficos no esperara
NULL
punteros, habría habido un bloqueo, mientras que se convirtió en una experiencia no tan fatal (incluso divertida) para el usuario. Lo mínimo es que no habría perdido inesperadamente su conexión o ninguno de sus puntos guardados.Este es un ejemplo en el que la comprobación de NULL evitó un bloqueo y el caso se manejó en silencio.
Imagine un programa de cajero bancario. La interfaz realiza algunas verificaciones y envía datos de entrada a una parte subyacente para realizar las transferencias de dinero. Si la parte central espera no recibir
NULL
, entonces un error en la interfaz puede causar un problema en la transacción, posiblemente perdiendo algo de dinero en el medio (o peor, duplicado).Por "un problema" me refiero a un bloqueo (como en el bloqueo del bloqueo (digamos que el programa está escrito en C ++), no una excepción de puntero nulo). En este caso, la transacción debe revertirse si se encuentra NULL (¡lo cual, por supuesto, no sería posible si no verificara las variables con NULL!)
Estoy seguro de que tienes la idea.
La verificación de la validez de los argumentos de sus funciones no satura su código, ya que se trata de un par de verificaciones en la parte superior de su función (no se extiende en el medio) y aumenta en gran medida la robustez.
Si tiene que elegir entre "el programa le dice al usuario que ha fallado y se apaga o regresa a un estado coherente, posiblemente creando un registro de error" y "Infracción de acceso", ¿no elegiría el primero? Eso significa que cada función debe estar preparada para ser llamada por un código defectuoso en lugar de esperar que todo sea correcto, simplemente porque siempre existen errores.
fuente
Como señalaron las otras respuestas Si no espera
NULL
, deje en claro arrojandoArgumentNullException
. En mi opinión, cuando está depurando el proyecto, le ayuda a descubrir las fallas en la lógica de su programa antes.Por lo tanto, va a lanzar su software, si restaura esas
NullRefrences
comprobaciones, no se pierde nada en la lógica de su software, solo se asegura doblemente de que no aparezca un error grave.Otro punto es que si la
NULL
verificación está en los parámetros de una función, entonces puede decidir si la función es el lugar correcto para hacerlo o no; si no, encuentre todas las referencias a la función y luego, antes de pasar un parámetro a la función, revise los escenarios probables que pueden conducir a una referencia nula.fuente
Cada uno
null
en su sistema es una bomba de tiempo que espera ser descubierta. Verifiquenull
en los límites donde su código interactúa con el código que no controla, y prohíbanull
dentro de su propio código. Esto lo libra del problema sin confiar ingenuamente en el código extranjero. Utilice excepciones o algún tipo deMaybe
/Nothing
tipo en su lugar.fuente
Si bien se lanzará una excepción de puntero nulo, a menudo se lanzará lejos del punto donde obtuvo el puntero nulo. Hazte un favor y acorta el tiempo que lleva arreglar el error al menos registrando el hecho de que obtienes un puntero nulo lo antes posible.
Incluso si no sabe qué hacer ahora, registrar el evento le ahorrará tiempo más tarde. Y tal vez el tipo que recibe el boleto podrá usar el tiempo ahorrado para encontrar la respuesta adecuada.
fuente
Como
Fail early, fail fast
según lo establecido por @DeadMG (@empi) no es posible porque la aplicación web debe funcionar bien bajo errores, debe aplicar la reglasin excepción capturando sin iniciar sesión
para que ustedes, como desarrolladores, sean conscientes de los posibles problemas al inspeccionar los registros.
Si está utilizando un marco de registro como log4net o log4j, puede configurar una salida de registro especial adicional (también conocida como appender)
que le envíe por correo errores y errores de fataler. para que te mantengas informado.[actualizar]
Si el usuario final de la página no tiene conocimiento del error, puede configurar un apéndice que le envíe por correo los errores y los errores de error para mantenerse informado.
si está bien que el usuario final de la página vea mensajes de error crípticos, puede configurar un apéndice que coloque el registro de errores para el procesamiento de la página al final de la página.
No importa cómo utilice el registro si se traga la excepción, el programa continúa con datos que tienen sentido y la deglución ya no es silenciosa.
fuente
Ya hay buenas respuestas a esta pregunta, puede ser una adición a ellas: prefiero las afirmaciones en mi código. Si su código solo puede funcionar con objetos válidos, pero con tuerca nula, debe afirmar el valor nulo.
Las afirmaciones en este tipo de situación permitirían que cualquier prueba de unidad y / o integración fallara con el mensaje exacto, por lo que puede resolver el problema bastante rápido antes de la producción. Si tal error pasa por alto las pruebas y pasa a producción (donde las afirmaciones deben desactivarse), recibirá NpEx y un error grave, pero es mejor, que algún error menor que es mucho más difuso y aparece en otro lugar en el código, y sería más costoso de arreglar.
Además, si alguien debe trabajar con las aserciones de su código, le informa sobre las suposiciones que hizo durante el diseño / escritura de su código (en este caso: ¡Hola amigo, este objeto debe presentarse!), Lo que facilita su mantenimiento.
fuente
Normalmente verifico los nulos, pero "manejo" los nulos inesperados lanzando una excepción que da un poco más de detalle sobre exactamente dónde ocurrió el nulo.
Editar: si obtiene un valor nulo cuando no espera que algo esté mal, el programa debería morir.
fuente
Depende de la implementación del lenguaje de las excepciones. Las excepciones le permiten tomar algunas medidas para evitar daños a los datos o liberar recursos de forma limpia en caso de que su sistema entre en un estado o condición imprevista. Esto es diferente del manejo de errores, que son condiciones que puede anticipar. Mi filosofía es que debe tener el menor manejo de excepciones posible. Es ruido. ¿Puede su método hacer algo razonable manejando la excepción? ¿Se puede recuperar? ¿Puede reparar o prevenir daños mayores? Si solo va a atrapar la excepción y luego regresa del método o falla de alguna otra manera inofensiva, entonces realmente no tenía sentido capturar la excepción. Solo habría agregado ruido y complejidad a su método, y puede estar ocultando la fuente de una falla en su diseño. En este caso, dejaría que la falla burbujeara y fuera atrapada por un bucle externo. Si puede hacer algo como reparar un objeto o marcarlo como corrupto o liberar algún recurso crítico como un archivo de bloqueo o cerrando limpiamente un socket, entonces este es un buen uso de excepciones. Si realmente espera que NULL se muestre con frecuencia como un caso perimetral válido, entonces debe manejar esto en el flujo lógico normal con declaraciones if-the-else o switch-case o lo que sea, no con un manejo de excepción. Por ejemplo, un campo deshabilitado en un formulario puede establecerse en NULL, que es diferente de una cadena vacía que representa un campo dejado en blanco. Esta es un área donde debe usar el buen juicio y el sentido común. Nunca he oído hablar de buenas reglas generales sólidas sobre cómo lidiar con este problema en cada situación. Si puede hacer algo como reparar un objeto o marcarlo como corrupto o liberar algún recurso crítico como un archivo de bloqueo o cerrando limpiamente un socket, este es un buen uso de excepciones. Si realmente espera que NULL se muestre con frecuencia como un caso perimetral válido, entonces debe manejar esto en el flujo lógico normal con declaraciones if-the-else o switch-case o lo que sea, no con un manejo de excepción. Por ejemplo, un campo deshabilitado en un formulario puede establecerse en NULL, que es diferente de una cadena vacía que representa un campo dejado en blanco. Esta es un área donde debe usar el buen juicio y el sentido común. Nunca he oído hablar de buenas reglas generales sólidas sobre cómo lidiar con este problema en cada situación. Si puede hacer algo como reparar un objeto o marcarlo como corrupto o liberar algún recurso crítico como un archivo de bloqueo o cerrando limpiamente un socket, entonces este es un buen uso de excepciones. Si realmente espera que NULL se muestre con frecuencia como un caso perimetral válido, entonces debe manejar esto en el flujo lógico normal con declaraciones if-the-else o switch-case o lo que sea, no con un manejo de excepción. Por ejemplo, un campo deshabilitado en un formulario puede establecerse en NULL, que es diferente de una cadena vacía que representa un campo dejado en blanco. Esta es un área donde debe usar el buen juicio y el sentido común. Nunca he oído hablar de buenas reglas generales sólidas sobre cómo lidiar con este problema en cada situación.
Java falla en su implementación de manejo de excepciones con "excepciones comprobadas" donde cada posible excepción que puedan generar los objetos utilizados en un método debe capturarse o declararse en la cláusula "throws" del método. Es lo opuesto a C ++ y Python, donde puede elegir manejar las excepciones como mejor le parezca. En Java, si modifica el cuerpo de un método para incluir una llamada que puede generar una excepción, tiene la opción de agregar un manejo explícito para una excepción que quizás no le importe, agregando así ruido y complejidad a su código, o debe agregue esa excepción a la cláusula "throws" del método que está modificando, que no solo agrega ruido y desorden sino que cambia la firma de su método. Luego debe ir a modificar el código donde sea que se use su método para manejar la nueva excepción que su método ahora puede generar o agregar la excepción a la cláusula "throws" de esos métodos, lo que desencadena un efecto dominó de los cambios de código. El "requisito de captura o especificación" debe ser uno de los aspectos más molestos de Java. La excepción de excepción para RuntimeExceptions y la racionalización oficial de Java para este error de diseño es poco convincente.
Ese último párrafo tuvo poco que ver con responder su pregunta. Simplemente odio Java.
- Noah
fuente