¿Se debe verificar si es nulo si no espera que sea nulo?

113

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"?

Stilgar
fuente
6262
Principio de Fox Mulder: no confíes en nadie.
Yannis
14
@YannisRizos: ese principio es bueno, es tan bueno que los creadores de Java y C # permiten que el entorno de tiempo de ejecución del lenguaje lo haga automáticamente. Por lo tanto, normalmente no es necesario hacer comprobaciones explícitas de nulos en todas partes de su código en esos idiomas.
Doc Brown
44
posible duplicado de ¿Debería un método validar sus parámetros?
Martin York
Estoy de acuerdo con la respuesta de tdammers. Creo que verificar nulo es incorrecto porque no tiene una forma razonable de manejarlo. Sin embargo, en su escenario puede manejar una sección que falla, como validar a un usuario (u obtener comentarios o lo que sea) y tener un cuadro de "oh no ha ocurrido un error". Personalmente registro todas las excepciones en mi aplicación y filtro ciertas excepciones como 404s y la conexión se cierra inesperadamente
2
assert(foo != null, "foo is web control within the repeater, there's no reason to expect it to be null, etc, etc...");
zzzzBov

Respuestas:

105

La pregunta no es tanto si debe verificar nullo 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:

  • Lanza una excepción genérica ( NullReferenceException) y déjala burbujear; Si no realiza la nullverificación usted mismo, esto es lo que sucede automáticamente.
  • Lanza una excepción personalizada que describe el problema en un nivel superior; esto se puede lograr ya sea lanzando el nullcheque o atrapando a NullReferenceExceptiony lanzando la excepción más específica.
  • Recupere mediante la sustitución de un valor predeterminado adecuado.

No hay una regla general sobre cuál es la mejor solución. Personalmente, diría:

  • La excepción genérica es mejor si la condición de error sería un signo de un error grave en su código, es decir, el valor que es nulo nunca debe permitirse llegar en primer lugar. Tal excepción puede aparecer en el registro de errores que haya configurado, para que alguien sea notificado y corrija el error.
  • La solución de valor predeterminado es buena si proporcionar una salida semi-útil es más importante que la corrección, como un navegador web que acepta HTML técnicamente incorrecto y hace un gran esfuerzo para hacerlo con sensatez.
  • De lo contrario, iría con la excepción específica y lo manejaría en un lugar adecuado.
tdammers
fuente
1
@Stilgar para el usuario final no hay diferencia. para el desarrollador, una excepción específica como "no se puede acceder al servidor de base de datos" es más intuitiva que "excepción de puntero nulo"
k3b
32
Fallar duro y temprano significa que hay menos riesgo de que las cosas vayan más sutilmente mal en el futuro, cosas como la autenticación incorrecta, los datos corruptos persistieron en el almacenamiento, la filtración de información y otras cosas divertidas.
Lars Viklund
1
@Stilgar: Aquí hay dos preocupaciones: comportamiento desatendido y mantenibilidad. Mientras que para el comportamiento desatendido hay poca diferencia entre una excepción específica y una genérica, para la mantenibilidad puede marcar la diferencia entre "oh, entonces es por eso que las cosas explotan" y un debugathlon de varias horas.
tdammers
3
tdammers tiene toda la razón. La "referencia de objeto no establecida en una instancia de un objeto" es una de las excepciones más molestas que veo con más frecuencia. Preferiría ver una excepción específica si se trata de una excepción ArgumentNullException con el nombre del parámetro o una excepción personalizada "No existe ningún registro de publicación para el usuario TK421".
Sean Chase
1
@ k3b También para el usuario final, "el servidor de base de datos no es accesible" es muy útil. Al menos, para cualquier usuario final que tenga una pista sobre problemas comunes: puede ayudarlo a descubrir que un cable está suelto, el enrutador necesita reiniciarse, etc., en lugar de enojarse por el software defectuoso.
Julia Hayward
58

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 uso SingleOrDefault(o peor aún, a la recolección) cuando alguien realmente espera Singley en muchos otros casos cuando las personas tienen miedo (realmente no sé de qué) enunciar claramente su lógica.

empi
fuente
En lugar de 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).
Arseni Mourzenko
Estoy de acuerdo contigo. Si no se espera el valor nulo, no se debe manejar de manera extraña, solo se debe informar.
Giorgio
99
si leo la pregunta correctamente, arrojo un ArgumentNullExceptionrecuento como "manejo" del valor nulo: evita una excepción de referencia nula posterior.
KutuluMike
@MichaelEdenfield: No creo que eso cuente como un manejo real de acuerdo con la pregunta dada (nuestro objetivo no es hacer que su código funcione, pase lo que pase ). Para ser honesto, a menudo me olvido de escribir el bloque apropiado que arrojará si se pasa nulo. Sin embargo, considero que lanzar NullArgumentException es la mejor práctica y, en mi opinión, la peor práctica es escribir código que no pueda manejar nulo, pero en lugar de arrojar excepciones devuelve, por ejemplo, otro nulo como resultado del método (o cadena vacía, o colección vacía, o -1 , u omite la ejecución del método si el tipo de retorno es nulo, etc.).
empi
2
@Giorgio, ArgumentNullExceptiondeja 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 si int(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 .
Kyralessa
35

El hábito de verificar nullsegú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á buscando NULL. Como sabe, en Java o C # las cosas son diferentes, usar una referencia nula sin verificar nullcausará 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ícitamente nullno 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

Doc Brown
fuente
3
"quieres un mensaje de error mejor y legible para los humanos", en mi opinión, esa es la mejor razón para hacerlo. "La referencia de objeto no establecida en una instancia de un objeto" o "Excepción de referencia nula" nunca es tan útil como "Producto no instanciado".
pdr
@pdr: Otra razón para verificar es asegurarse de que siempre se detecte.
Giorgio
13
"Antiguos desarrolladores de C", tal vez. "Ex desarrolladores de C ++" solo si esos mismos son desarrolladores de C en recuperación. Como desarrollador moderno de C ++, sospecharía mucho sobre el código que utiliza punteros desnudos o asignación dinámica imprudente. De hecho, me vería tentado a cambiar el argumento y decir que C ++ no sufre el problema que describe el OP porque sus variables no tienen la propiedad especial de nulidad especial que tienen Java y C #.
Kerrek SB
77
Su primer párrafo es solo la mitad de la historia: sí, si usa una referencia nula en C #, obtendrá una excepción, mientras que en C ++ obtendrá un comportamiento indefinido. Pero en C # bien escrito, está atascado con referencias mucho más anulables que en C ++ bien escrito.
James McNellis
Cuanto mejor sea la encapsulación, mejor se aplica esta respuesta.
radarbob
15

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:

  • eficiente (generalmente no se usa en la versión),
  • breve (generalmente una sola línea) y
  • claro (condición previa violada, esto es un error de programación).

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.

Macke
fuente
afirma que no estar presente en el lanzamiento es un gran negativo para mí, el lanzamiento es el momento exacto en que desea la información agregada
jk.
1
Bueno, siéntase libre de usar una función de lanzamiento condicional ^ h ^ h ^ h que esté activa en el lanzamiento también si lo necesita. He rodado el mío con bastante frecuencia.
Macke
7

Falla temprano, falla a menudo. nulles 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 que nullautomáticamente falla para usted cada vez que intenta usarlo, diría que no requiere una verificación explícita.

DeadMG
fuente
28
Espero que quería decir: fallar temprano, fallar rápido, no muy a menudo ...
Empi
12
El problema es que sin verificaciones explícitas, un valor nulo a veces puede propagarse bastante antes de que cause una excepción, y luego puede ser muy difícil averiguar dónde se originó.
Michael Borgwardt
@MichaelBorgwardt ¿No es para eso que están los rastros de la pila?
R0MANARMY
66
@ R0MANARMY: los seguimientos de pila no ayudan cuando se guarda un valor nulo en algún campo y la excepción NullPointerException se produce mucho más tarde.
Michael Borgwardt
@ R0MANARMY incluso si pudiera confiar en el número de línea en el seguimiento de la pila (en muchos casos no puede) algunas líneas contienen múltiples variables que pueden ser nulas.
Ohad Schneider
6
  • 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

CodeART
fuente
El uso de contratos de código para precondiciones es simplemente declaraciones condicionales glorificadas.
un CVn
Sí, estoy de acuerdo, pero también creo que el código se ve más limpio cuando se usan contratos de código, es decir, se mejora su legibilidad.
CodeART
4

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

  • Un valor, por ejemplo, void foo (T x)
  • Una referencia, por ejemplo, void foo (T & x).

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:

void foo(T* x)

Una alternativa es usar un puntero inteligente como

void foo(shared_ptr<T> x)

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):

  1. Sin verificación: si hay un error, deje que se bloquee y espere que este error aparezca lo suficientemente temprano (durante las pruebas). Si no lo hace (si no falla lo suficientemente temprano), espero que no aparezca en un sistema de producción porque la experiencia del usuario será que vean la falla de la aplicación.
  2. Verifique todos los argumentos al comienzo del método e informe argumentos NULL inválidos, por ejemplo, arroje una excepción desde foo () y deje que algún otro método lo maneje. Probablemente lo mejor que se puede hacer es mostrar al usuario una ventana de diálogo que indique que la aplicación ha encontrado un error interno y que se va a cerrar.

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

  • Tanto en C ++ como en Java, verificar si los argumentos opcionales son nulos es parte de la lógica del programa.
  • En C ++, siempre verificaría los argumentos obligatorios para asegurar que se produzca una excepción adecuada para que el programa pueda manejarlo de manera robusta.
  • En Java (o C #), verificaría los argumentos obligatorios si hay rutas de ejecución que no se lanzarán para tener una forma más fuerte de "falla temprana".

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.

Giorgio
fuente
En nuestro caso, no fue el argumento lo que fue nulo. Hicimos una llamada a la base de datos para algo que se esperaba que estuviera presente, pero en la práctica no estaba presente. La pregunta es si deberíamos verificar que todos los objetos existan o deberíamos verificar solo si esperamos que falte.
Stilgar
Por lo tanto, el resultado devuelto por un método era una referencia a un objeto y nulo era el valor utilizado para representar el resultado: NO ENCONTRADO. ¿Entiendo correctamente?
Giorgio
He actualizado la pregunta con detalles adicionales.
Stilgar
3

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 nullvalor?

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.

un CVn
fuente
Por supuesto, hay un controlador de errores general en la cadena. El problema con los registros es que puede no haber nadie que los revise durante mucho tiempo.
Stilgar
@Stilgar, si no hay nadie para verificar los registros, entonces eso no es un problema con la existencia o ausencia de comprobaciones nulas.
un CVn
2
Hay un problema. Los usuarios finales pueden no darse cuenta de que el sistema no funciona correctamente durante mucho tiempo si hay comprobaciones nulas que simplemente ocultan el error y lo escriben en los registros.
Stilgar
@Stilgar, eso depende completamente de la naturaleza del error. Como dije en la publicación, solo si puede continuar de manera segura, se debe reemplazar / ignorar el nulo inesperado. Usar una imagen en lugar de otra en una versión de lanzamiento (en versiones de depuración, fallar lo antes posible y lo más difícil posible para que el problema sea obvio) es una bestia muy diferente a devolver resultados no válidos para un cálculo, por ejemplo. (Respuesta editada para incluir eso.)
un CVn
1
Me gustaría fallar rápido y temprano incluso en la producción, a menos que de alguna manera puedas iniciar sesión e informar el error (sin depender demasiado de los usuarios inteligentes). Ocultar los errores desplegados de los usuarios es una cosa: ocultarlos de usted mismo es peligroso. También permitir que el usuario viva con errores sutiles puede erosionar su confianza en su aplicación. A veces es mejor morder la bala y hacerles saber que hubo un error y que lo reparó rápidamente.
Merlyn Morgan-Graham
2

Claro NULLque no se espera , ¡pero siempre hay errores!

Creo que debe verificar NULLsi 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.

Shahbaz
fuente
Su segundo ejemplo refuta su punto. En una transacción bancaria, si algo sale mal, la transacción debería abortar. Es precisamente cuando cubre el error que se puede perder o duplicar dinero. Verificar nulo sería como "El cliente envía dinero nulo ... bueno, simplemente enviaré $ 10 (el equivalente de la imagen del desarrollador)"
Stilgar
@Stilgar, lo contrario! Verificar NULL sería abortar con el mensaje. No verificar NULL sería un bloqueo . Ahora, el bloqueo al comienzo de la transacción puede no ser malo, pero en el medio puede ser catastrófico. (¡No dije que la transferencia bancaria debería manejar el error como el juego! Solo el hecho de que debería manejarlo)
Shahbaz
2
El punto principal de una "transacción" es que no puede completarse a medias :) Si hay un error, se revierte.
Stilgar
Este es un consejo terrible para cualquier cosa donde la semántica transaccional sea realmente necesaria. Debe hacer que todas sus transacciones sean atómicas e idempotentes, de lo contrario, cualquier error que tenga garantizará los recursos perdidos o duplicados (dinero). Si tiene que lidiar con operaciones no atómicas o no idempotentes, debe envolverlo con mucho cuidado en capas que garanticen un comportamiento atómico e idempotente (incluso si tiene que escribir esas capas usted mismo). Siempre piense: ¿qué sucede si un meteorito golpea el servidor web en el momento en que se realiza esta transacción?
Merlyn Morgan-Graham
1
@ MerlynMorgan-Graham, lo hice. Espero que eso aclare la confusión.
Shahbaz
2

Como señalaron las otras respuestas Si no espera NULL, deje en claro arrojando ArgumentNullException. 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 NullRefrencescomprobaciones, 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 NULLverificació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.

Ahmad
fuente
1

Cada uno nullen su sistema es una bomba de tiempo que espera ser descubierta. Verifique nullen los límites donde su código interactúa con el código que no controla, y prohíba nulldentro de su propio código. Esto lo libra del problema sin confiar ingenuamente en el código extranjero. Utilice excepciones o algún tipo de Maybe/ Nothingtipo en su lugar.

Doval
fuente
0

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.

Jon Strayer
fuente
-1

Como Fail early, fail fastsegún lo establecido por @DeadMG (@empi) no es posible porque la aplicación web debe funcionar bien bajo errores, debe aplicar la regla

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

k3b
fuente
1
La pregunta es ¿qué pasa con la página?
Stilgar
¿Cómo sabemos que los datos tienen sentido y que el sistema está en un estado consistente? Después de todo, el error fue inesperado, por lo que no sabemos cuál fue el impacto.
Stilgar
"Dado que Fail temprano, fallar rápido como lo indica @DeadMG (@empi) no es posible porque la aplicación web debe funcionar bien en caso de errores, debe aplicar la regla": siempre se pueden detectar todas las excepciones (catch (Throwable t)) y redirigir a alguna página de error. ¿No sería esto posible?
Giorgio
-1

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.

Obtener
fuente
¿Podría por favor escribir algo para votar? Agradecería una discusión. Gracias
GeT
No rechacé la votación, y estoy de acuerdo con su idea general. Sin embargo, el lenguaje necesita algo de trabajo. Sus afirmaciones definen el contrato de su API, y usted falla temprano / falla rápidamente si se violan esos contratos. Hacer esto en la máscara de su aplicación les permitirá a los usuarios de su código saber que hicieron algo mal en su código. Si ven una pila rastrear en los recovecos de su código, es probable que piensen que su código tiene errores, y usted también podría pensarlo hasta que obtenga una réplica sólida y la depure.
Merlyn Morgan-Graham
-1

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.

Loren Pechtel
fuente
-1

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

Noah Spurrier
fuente
esta publicación es bastante difícil de leer (muro de texto). ¿Te importaría editarlo en una mejor forma?
mosquito