No entiendo muy bien el ataque constante de referencias nulas por parte de algunos amigos del lenguaje de programación. ¿Qué hay de malo en ellos? Si solicito acceso de lectura a un archivo que no existe, entonces estoy perfectamente feliz de obtener una excepción o una referencia nula y, sin embargo, las excepciones se consideran buenas, pero las referencias nulas se consideran malas. ¿Cuál es el razonamiento detrás de esto?
programming-languages
exceptions
null
davidk01
fuente
fuente
Respuestas:
Las referencias nulas no son "rechazadas" más que las excepciones, al menos por cualquiera que haya conocido o leído. Creo que estás malinterpretando la sabiduría convencional.
Lo malo es un intento de acceder a una referencia nula (o desreferenciar un puntero nulo, etc.). Esto es malo porque siempre indica un error; nunca haría algo como esto a propósito, y si lo está haciendo a propósito, entonces eso es aún peor, porque es lo que hace imposible distinguir el comportamiento esperado de un comportamiento incorrecto.
Hay ciertos grupos marginales por ahí que realmente odian el concepto de nulidad por alguna razón, pero como señala Ed , si no tiene
null
onil
simplemente tendrá que reemplazarlo por otra cosa, lo que podría conducir a algo peor que un bloqueo (como la corrupción de datos).Muchos marcos, de hecho, abarcan ambos conceptos; por ejemplo, en .NET, un patrón frecuente que verá es un par de métodos, uno con el prefijo de la palabra
Try
(comoTryGetValue
). En elTry
caso, la referencia se establece en su valor predeterminado (generalmentenull
), y en el otro caso, se genera una excepción. No hay nada malo con ninguno de los enfoques; ambos se usan con frecuencia en entornos que los admiten.Realmente todo depende de la semántica. Si
null
es un valor de retorno válido, como en el caso general de buscar una colección, entonces devuelvanull
. Por otro lado, si es no un valor de retorno válido - por ejemplo, buscar un registro usando una clave principal que vino de su propia base de datos - luego regresarnull
sería una mala idea porque la persona que llama no se lo esperaba y, probablemente, No lo comprobaré.Es realmente muy simple descubrir qué semántica usar: ¿Tiene algún sentido que el resultado de una función sea indefinido? Si es así, puede devolver una referencia nula. Si no, lanza una excepción.
fuente
null
, así que realmente, Haskell es bastante irrelevante aquí.null
ha resistido tan bien durante tanto tiempo, y la mayoría de las personas que dicen lo contrario parecen ser académicos con poca experiencia en el diseño de aplicaciones del mundo real con restricciones como requisitos incompletos o consistencia eventual.La gran diferencia es que si deja de lado el código para manejar NULL, su código continuará posiblemente bloqueándose en una etapa posterior con algún mensaje de error no relacionado, donde, como con las excepciones, la excepción se generaría en el punto inicial de falla (apertura un archivo para leer en tu ejemplo).
fuente
Porque los valores nulos no son una parte necesaria de un lenguaje de programación y son una fuente constante de errores. Como usted dice, abrir un archivo puede provocar un error, que podría comunicarse de nuevo como un valor de retorno nulo o mediante una excepción. Si no se permitieran valores nulos, entonces hay una forma coherente y singular de comunicar la falla.
Además, este no es el problema más común con los nulos. La mayoría de las personas recuerda comprobar si hay nulo después de llamar a una función que puede devolverla. El problema surge mucho más en su propio diseño al permitir que las variables sean nulas en varios puntos de la ejecución de su programa. Puede diseñar su código de manera que los valores nulos nunca estén permitidos, pero si no se permitieran nulos a nivel de idioma, nada de esto sería necesario.
Sin embargo, en la práctica aún necesitaría alguna forma de indicar si una variable se inicializa o no. Entonces tendría una forma de error en el que su programa no se bloquea, sino que continúa usando algún valor predeterminado posiblemente no válido. Sinceramente, no sé cuál es mejor. Por mi dinero me gusta colapsar temprano y con frecuencia.
fuente
null
un valor de retorno: la información que solicitó no existe. No hay diferencia De cualquier manera, la persona que llama tiene que validar el valor de retorno si realmente necesita usar esa información para algún propósito en particular. Si se trata de validar unnull
objeto nulo o una mónada, no hay diferencia práctica.Tony Hoare, quien creó la idea de una referencia nula en primer lugar, lo llama su error de un millón de dólares .
El problema no se trata de referencias nulas per-se, sino de la falta de una verificación de tipo adecuada en la mayoría de los idiomas seguros (de lo contrario).
Esta falta de soporte, desde el lenguaje, significa que los errores "errores nulos" pueden estar al acecho en el programa durante mucho tiempo antes de ser detectados. Tal es la naturaleza de los errores, por supuesto, pero ahora se sabe que los "errores nulos" son evitables.
Este problema está especialmente presente en C o C ++ (por ejemplo) debido al error "duro" que causa (un bloqueo del programa, inmediato, sin recuperación elegante).
En otros idiomas, siempre está la cuestión de cómo manejarlos.
En Java o C #, obtendría una excepción si intenta invocar un método en una referencia nula, y podría estar bien. Y, por lo tanto, la mayoría de los programadores de Java o C # están acostumbrados a esto y no entienden por qué uno querría hacer lo contrario (y reírse de C ++).
En Haskell, debe proporcionar explícitamente una acción para el caso nulo y, por lo tanto, los programadores de Haskell se regodean con sus colegas, porque lo hicieron bien (¿verdad?).
Es, en realidad, el viejo debate sobre el código de error / excepción, pero esta vez con un Sentinel Value en lugar del código de error.
Como siempre, lo que sea más apropiado realmente depende de la situación y la semántica que está buscando.
fuente
null
Realmente es malo, porque subvierte el sistema de tipos . (De acuerdo, la alternativa tiene un inconveniente en su verbosidad, al menos en algunos idiomas.)No puede adjuntar un mensaje de error legible por humanos a un puntero nulo.
(Sin embargo, puede dejar un mensaje de error en un archivo de registro).
En algunos lenguajes / entornos que permiten la aritmética de punteros, si uno de los argumentos de puntero es nulo y se deja en el cálculo, el resultado sería una inválida , no nulo puntero. (*) Más poder para ti.
(*) Esto sucede mucho en la programación COM , donde si intenta llamar a un método de interfaz pero el puntero de la interfaz es nulo, daría lugar a una llamada a una dirección no válida que es casi, pero no del todo, exactamente diferente a cero.
fuente
Devolver NULL (o cero numérico, o booleano falso) para indicar un error es incorrecto tanto técnica como conceptualmente.
Técnicamente, está cargando al programador con la comprobación inmediata del valor de retorno , en el punto exacto donde se devuelve. Si abre veinte archivos seguidos y la señalización de error se realiza devolviendo NULL, entonces el código consumidor debe verificar cada archivo leído individualmente, y salir de cualquier bucle y construcciones similares. Esta es una receta perfecta para el código desordenado. Sin embargo, si elige señalar el error lanzando una excepción, el código consumidor puede optar por manejar la excepción de inmediato, o dejar que aumente tantos niveles como sea apropiado, incluso a través de llamadas de función. Esto hace que el código sea mucho más limpio.
Conceptualmente, si abre un archivo y algo sale mal, entonces devolver un valor (incluso NULL) está mal. No tiene nada que devolver, porque su operación no terminó. Devolver NULL es el equivalente conceptual de "He leído con éxito el archivo, y esto es lo que contiene: nada". Si eso es lo que desea expresar (es decir, si NULL tiene sentido como resultado real de la operación en cuestión), devuelva NULL por todos los medios, pero si desea señalar un error, use excepciones.
Históricamente, los errores se informaron de esta manera porque los lenguajes de programación como C no tienen un manejo de excepciones integrado en el lenguaje, y la forma recomendada (usando saltos largos) es un poco complicada y un poco intuitiva.
También hay un aspecto de mantenimiento del problema: con excepciones, debe escribir un código adicional para manejar la falla; si no lo hace, el programa fallará temprano y duro (lo cual es bueno). Si devuelve NULL para señalar errores, el comportamiento predeterminado del programa es ignorar el error y continuar, hasta que conduzca a otros problemas en el futuro: datos corruptos, segfaults, NullReferenceExceptions, según el idioma. Para señalar el error temprano y en voz alta, debe escribir un código adicional y adivinar qué: esa es la parte que se deja de lado cuando está en una fecha límite ajustada.
fuente
Como ya se señaló, muchos idiomas no convierten la desreferenciación de un puntero nulo en una excepción capturable. Hacer eso es un truco relativamente moderno. Cuando se reconoció por primera vez el problema del puntero nulo, aún no se habían inventado excepciones.
Si permite punteros nulos como un caso válido, ese es un caso especial. Necesita lógica de manejo de casos especiales, a menudo en muchos lugares diferentes. Esa es una complejidad extra.
Ya sea que se relacione con punteros potencialmente nulos o no, si no usa lanzamientos de excepción para manejar casos excepcionales, debe manejar esos casos excepcionales de otra manera. Por lo general, cada llamada a la función debe tener comprobaciones para esos casos excepcionales, ya sea para evitar que la llamada a la función se llame inapropiadamente o para detectar el caso de falla cuando la función sale. Esa es una complejidad adicional que se puede evitar utilizando excepciones.
Más complejidad generalmente significa más errores.
Las alternativas al uso de punteros nulos en las estructuras de datos (por ejemplo, para marcar el inicio / final de una lista vinculada) incluyen el uso de elementos centinela. Estos pueden proporcionar la misma funcionalidad con mucha menos complejidad. Sin embargo, puede haber otras formas de gestionar la complejidad. Una forma es envolver el puntero potencialmente nulo en una clase de puntero inteligente para que las comprobaciones nulas solo sean necesarias en un lugar.
¿Qué hacer con el puntero nulo cuando se detecta? Si no puede incorporar el manejo de casos excepcionales, siempre puede lanzar una excepción y delegar efectivamente ese manejo de casos especiales a la persona que llama. Y eso es exactamente lo que hacen algunos idiomas de forma predeterminada cuando se desreferencia un puntero nulo.
fuente
Específico para C ++, pero las referencias nulas se evitan allí porque la semántica nula en C ++ está asociada con los tipos de puntero. Es bastante razonable que una función de apertura de archivo falle y devuelva un puntero nulo; de hecho, la
fopen()
función hace exactamente eso.fuente
Depende del idioma.
Por ejemplo, Objective-C le permite enviar un mensaje a un objeto nulo (nulo) sin problemas. Una llamada a nil devuelve nil también y se considera una función de idioma.
Personalmente me gusta ya que puedes confiar en ese comportamiento y evitar todas esas
if(obj == null)
construcciones anidadas enrevesadas .Por ejemplo:
Se puede acortar a:
En resumen, hace que su código sea más legible.
fuente
No me importa si devuelves un valor nulo o lanzas una excepción, siempre y cuando lo documentas.
fuente
GetAddressMaybe
oGetSpouseNameOrNull
.Una referencia nula generalmente ocurre porque se perdió algo en la lógica del programa, es decir: llegó a una línea de código sin pasar por la configuración requerida para ese bloque de código.
Por otro lado, si arroja una excepción por algo, significa que reconoció que podría ocurrir una situación particular en el funcionamiento normal del programa, y se está manejando.
fuente
Las referencias nulas suelen ser muy útiles: por ejemplo, un elemento en una lista vinculada puede tener algún sucesor o no. Usar
null
para "no sucesor" es perfectamente natural. O bien, una persona puede tener un cónyuge o no: usarnull
para "la persona no tiene cónyuge" es perfectamente natural, mucho más natural que tener un valor especial "no tiene cónyuge" al que elPerson.Spouse
miembro podría referirse.Pero: muchos valores no son opcionales. En un programa OOP típico, diría que más de la mitad de las referencias no pueden ser
null
posteriores a la inicialización, o el programa fallará. De lo contrario, el código tendría que estar lleno deif (x != null)
cheques. Entonces, ¿por qué cada referencia debe ser anulable por defecto? Realmente debería ser al revés: las variables no deberían ser anulables de forma predeterminada, y debería decir explícitamente "oh, y este valor también puede serlonull
".fuente
Su pregunta está redactada de manera confusa. ¿Se refiere a una excepción de referencia nula (que en realidad es causada por un intento de eliminarreferencia nula)? La razón clara para no querer ese tipo de excepción es que no le brinda información sobre lo que salió mal, o incluso cuándo: el valor podría haberse establecido en nulo en cualquier punto del programa. Escribe "Si solicito acceso de lectura a un archivo que no existe, entonces estoy perfectamente feliz de obtener una excepción o una referencia nula", pero no debería estar perfectamente feliz de obtener algo que no indique la causa . En ninguna parte de la cadena "se hizo un intento de desreferenciar nulo" hay alguna mención de lectura o de archivos no existentes. Tal vez quiere decir que estaría perfectamente feliz de ser nulo como valor de retorno de una llamada de lectura; es un asunto bastante diferente, pero aún no le da información sobre por qué falló la lectura; eso'
fuente