He oído decir que la inclusión de referencias nulas en lenguajes de programación es el "error de mil millones de dólares". ¿Pero por qué? Claro, pueden causar NullReferenceExceptions, pero ¿y qué? Cualquier elemento del lenguaje puede ser una fuente de errores si se usa incorrectamente.
¿Y cuál es la alternativa? Supongo que en lugar de decir esto:
Customer c = Customer.GetByLastName("Goodman"); // returns null if not found
if (c != null)
{
Console.WriteLine(c.FirstName + " " + c.LastName + " is awesome!");
}
else { Console.WriteLine("There was no customer named Goodman. How lame!"); }
Podrías decir esto:
if (Customer.ExistsWithLastName("Goodman"))
{
Customer c = Customer.GetByLastName("Goodman") // throws error if not found
Console.WriteLine(c.FirstName + " " + c.LastName + " is awesome!");
}
else { Console.WriteLine("There was no customer named Goodman. How lame!"); }
¿Pero cómo es eso mejor? De cualquier manera, si olvida verificar que el cliente existe, obtendrá una excepción.
Supongo que una CustomerNotFoundException es un poco más fácil de depurar que una NullReferenceException en virtud de ser más descriptiva. Eso es todo?
language-design
exceptions
pointers
null
Tim Goodman
fuente
fuente
Respuestas:
nulo es malo
Hay una presentación sobre InfoQ sobre este tema: Referencias nulas: el error del billón de dólares por Tony Hoare
Tipo de opción
La alternativa de la programación funcional es usar un tipo de Opción , que puede contener
SOME value
oNONE
.Un buen artículo El Patrón de "Opción" que discute el tipo de Opción y proporciona una implementación para Java.
También he encontrado un informe de error para Java sobre este problema: agregue tipos de opciones agradables a Java para evitar NullPointerExceptions . La característica solicitada se introdujo en Java 8.
fuente
String
tipo no es realmente una cadena. Es unString or Null
tipo Los lenguajes que se adhieren completamente a la idea de los tipos de opción no permiten valores nulos en su sistema de tipos, lo que garantiza que si define una firma de tipo que requiere unString
(o cualquier otra cosa), debe tener un valor. ElOption
tipo está ahí para dar una representación a nivel de tipo de cosas que pueden o no tener un valor, para que sepa dónde debe manejar explícitamente esas situaciones.El problema es que, debido a que en teoría cualquier objeto puede ser nulo y arrojar una excepción cuando intentas usarlo, tu código orientado a objetos es básicamente una colección de bombas sin explotar.
Tiene razón en que el manejo elegante de errores puede ser funcionalmente idéntico a las
if
declaraciones de verificación nula . Pero, ¿qué sucede cuando algo de lo que te has convencido a ti mismo de que no podría ser nulo es, de hecho, un nulo? Kerboom Pase lo que pase después, estoy dispuesto a apostar que 1) no será elegante y 2) no le gustará.Y no descarte el valor de "fácil de depurar". El código de producción maduro es una criatura loca y en expansión; cualquier cosa que le brinde más información sobre lo que salió mal y dónde puede ahorrarle horas de excavación.
fuente
public Foo doStuff()
no significa "hacer cosas y devolver el resultado de Foo" significa "hacer cosas y devolver el resultado de Foo, O devolver nulo, pero no tienes idea de si eso sucederá o no". El resultado es que tiene que marcar nulo POR TODAS PARTES para estar seguro. También podría eliminar los valores nulos y utilizar un tipo o patrón especial (como un tipo de valor) para indicar un valor sin sentido.Hay varios problemas con el uso de referencias nulas en el código.
Primero, generalmente se usa para indicar un estado especial . En lugar de definir una nueva clase o constante para cada estado, ya que las especializaciones se realizan normalmente, usar una referencia nula es usar un tipo / valor generalizado masivo y con pérdida .
En segundo lugar, el código de depuración se vuelve más difícil cuando aparece una referencia nula e intenta determinar qué lo generó , qué estado está vigente y su causa, incluso si puede rastrear su ruta de ejecución ascendente.
Tercero, las referencias nulas introducen rutas de código adicionales para probar .
Cuarto, una vez que las referencias nulas se utilizan como estados válidos para los parámetros, así como los valores de retorno, la programación defensiva (para los estados causados por el diseño) requiere más verificación de referencias nulas en varios lugares ... por si acaso.
Quinto, el tiempo de ejecución del lenguaje ya está realizando verificaciones de tipo cuando realiza una búsqueda de selector en la tabla de métodos de un objeto. Entonces, está duplicando el esfuerzo al verificar si el tipo de objeto es válido / no válido y luego hacer que el tiempo de ejecución verifique el tipo de objeto válido para invocar su método.
¿Por qué no utilizar el patrón NullObject para aprovechar la verificación del tiempo de ejecución para que invoque métodos NOP específicos de ese estado (conforme a la interfaz del estado normal) y al mismo tiempo eliminar todas las comprobaciones adicionales de referencias nulas en toda su base de código?
Que implica más trabajo mediante la creación de una clase NullObject para cada interfaz con la que se desea representar un estado especial. Pero al menos la especialización está aislada para cada estado especial, en lugar del código en el que el estado podría estar presente. IOW, el número de pruebas se reduce porque tiene menos rutas de ejecución alternativas en sus métodos.
fuente
[self.navigationController.presentingViewController dismissViewControllerAnimated: YES completion: nil];
el tiempo de ejecución lo trata como una secuencia de no-ops, la vista no se elimina de la pantalla y puede sentarse frente a Xcode rascándose la cabeza durante horas tratando de averiguar por qué.Los nulos no son tan malos, a menos que no los esperes. Usted debe necesitar especificar explícitamente en el código que usted está esperando nulo, lo que es un problema del lenguaje de diseño. Considera esto:
Si intenta invocar métodos en un
Customer?
, debería obtener un error en tiempo de compilación. La razón por la que más idiomas no hacen esto (IMO) es que no permiten que el tipo de variable cambie según el ámbito en el que se encuentre. Si el idioma puede manejar eso, entonces el problema puede resolverse completamente dentro del sistema de tiposTambién existe la forma funcional de manejar este problema, usando tipos de opciones y
Maybe
, pero no estoy tan familiarizado con él. Prefiero esta manera porque el código correcto debería teóricamente solo necesitar un carácter agregado para compilar correctamente.fuente
GetByLastName
devuelve nulo si no se encuentra (en lugar de lanzar una excepción). Solo puedo ver si regresaCustomer?
o noCustomer
.Maybe
- AMaybe Customer
esJust c
(dondec
es aCustomer
) oNothing
. En otros idiomas, se llamaOption
: vea algunas de las otras respuestas a esta pregunta.Customer.FirstName
es de tipoString
(en oposición aString?
). Ese es el beneficio real: solo las variables / propiedades que tienen un valor significativo de nada deben declararse como nulables.Hay muchas respuestas excelentes que cubren los síntomas desafortunados de
null
, por lo que me gustaría presentar un argumento alternativo: Null es una falla en el sistema de tipos.El propósito de un sistema de tipos es asegurar que los diferentes componentes de un programa "encajen" adecuadamente; un programa bien escrito no puede "descarrilarse" en un comportamiento indefinido.
Considere un dialecto hipotético de Java, o cualquiera que sea su lenguaje de tipo estático preferido, donde puede asignar la cadena
"Hello, world!"
a cualquier variable de cualquier tipo:Y puede verificar variables de esta manera:
No hay nada imposible en esto: alguien podría diseñar ese lenguaje si quisiera. El valor especial no tiene que ser
"Hello, world!"
- que podría haber sido el número 42, la tupla(1, 4, 9)
, o, por ejemplo,null
. ¿Pero por qué harías esto? Una variable de tipoFoo
solo debe contenerFoo
s, ¡ese es el punto principal del sistema de tipos!null
No esFoo
más de lo que"Hello, world!"
es. Peor aún,null
no es un valor de ningún tipo, ¡y no hay nada que puedas hacer con él!El programador nunca puede estar seguro de que una variable realmente contenga un
Foo
, y tampoco puede hacerlo el programa; Para evitar un comportamiento indefinido, debe verificar las variables"Hello, world!"
antes de usarlas comoFoo
s. Tenga en cuenta que hacer la verificación de cadena en el fragmento anterior no propaga el hecho de que foo1 es realmente unFoo
-bar
probablemente también tendrá su propia verificación, solo para estar seguro.Compare eso con el uso de un
Maybe
/Option
tipo con coincidencia de patrones:Dentro de la
Just foo
cláusula, tanto usted como el programa saben con certeza que nuestraMaybe Foo
variable realmente contiene unFoo
valor: que la información se propaga por la cadena de llamadas ybar
no necesita hacer ninguna verificación. Debido a queMaybe Foo
es un tipo distintoFoo
, se ve obligado a manejar la posibilidad de que pueda contenerNothing
, por lo que nunca puede ser sorprendido por aNullPointerException
. Puede razonar sobre su programa mucho más fácilmente y el compilador puede omitir comprobaciones nulas sabiendo que todas las variables de tipoFoo
realmente contienenFoo
s. Todos ganan.fuente
my Int $variable = Int
DóndeInt
está el valor de tipo nuloInt
?this type only contains integers
en oposición athis type contains integers and this other value
, tendrá un problema cada vez que sólo se desea enteros.??
,?.
y así sucesivamente) para las cosas que lenguajes como Haskell implementan funciones como la biblioteca. Creo que es mucho más ordenado.null
/Nothing
propagación no es "especial" de ninguna manera, entonces, ¿por qué deberíamos aprender más sintaxis para tenerla?(Lanzando mi sombrero en el ring por una vieja pregunta;))
El problema específico con nulo es que rompe la escritura estática.
Si tengo un
Thing t
, entonces el compilador puede garantizar que puedo llamart.doSomething()
. Bueno, A MENOS quet
sea nulo en tiempo de ejecución. Ahora todas las apuestas están apagadas. El compilador dijo que estaba bien, pero descubrí mucho más tarde quet
NO lo hacedoSomething()
. Entonces, en lugar de poder confiar en el compilador para detectar errores de tipo, tengo que esperar hasta el tiempo de ejecución para detectarlos. ¡También podría usar Python!Entonces, en cierto sentido, nulo introduce el tipeo dinámico en un sistema tipado estáticamente, con los resultados esperados.
La diferencia entre que una división entre cero o un log negativo, etc. es que cuando i = 0, sigue siendo un int. El compilador aún puede garantizar su tipo. El problema es que la lógica aplica mal ese valor de una manera que no está permitida por la lógica ... pero si la lógica lo hace, esa es más o menos la definición de un error.
(El compilador puede detectar algunos de esos problemas, por cierto. Como marcar expresiones como
i = 1 / 0
en tiempo de compilación. Pero realmente no puede esperar que el compilador siga una función y se asegure de que todos los parámetros sean consistentes con la lógica de la función)El problema práctico es que haces mucho trabajo extra y agregas controles nulos para protegerte en el tiempo de ejecución, pero ¿qué pasa si olvidas uno? El compilador le impide asignar:
Entonces, ¿por qué debería permitir la asignación a un valor (nulo) que romperá las desreferencias
s
?Al mezclar "sin valor" con referencias "escritas" en su código, está poniendo una carga adicional en los programadores. Y cuando suceden NullPointerExceptions, rastrearlas puede llevar aún más tiempo. En lugar de confiar en la escritura estática para decir "esto es una referencia a algo esperado", está dejando que el lenguaje diga "esto bien podría ser una referencia a algo esperado".
fuente
*int
identifica unint
, oNULL
. Del mismo modo en C ++, una variable de tipo*Widget
identifica aWidget
, una cosa cuyo tipo deriva deWidget
, oNULL
. El problema con Java y derivados como C # es que usan la ubicación de almacenamientoWidget
para significar*Widget
. La solución no es fingir que*Widget
no debería ser capaz de mantenerNULL
, sino más bien tener unWidget
tipo de ubicación de almacenamiento adecuado .Un
null
puntero es una herramienta.No es un enemigo. Deberías usarlos bien.
Piense un momento acerca de cuánto tiempo lleva encontrar y corregir un error de puntero no válido típico , en comparación con localizar y corregir un error de puntero nulo. Es fácil verificar un puntero
null
. Es un PITA verificar si un puntero no nulo apunta o no a datos válidos.Si aún no está convencido, agregue una buena dosis de subprocesamiento múltiple a su escenario, luego piense nuevamente.
Moraleja de la historia: no tires a los niños con el agua. Y no culpe a las herramientas por el accidente. El hombre que inventó el número cero hace mucho tiempo fue bastante inteligente, ya que ese día se podía llamar "nada". El
null
puntero no está tan lejos de eso.EDITAR: Aunque el
NullObject
patrón parece ser una mejor solución que las referencias que pueden ser nulas, presenta problemas por sí solo:Una referencia que contiene un
NullObject
debería (según la teoría) no hace nada cuando se llama a un método. Por lo tanto, se pueden introducir errores sutiles debido a referencias erróneamente no asignadas, que ahora se garantiza que no son nulas (¡sí!) Pero realizan una acción no deseada: nada. Con un NPE es obvio dónde radica el problema. Con unNullObject
comportamiento que de alguna manera (pero incorrecto), presentamos el riesgo de que los errores se detecten (demasiado) tarde. Esto no es, por accidente, similar a un problema de puntero no válido y tiene consecuencias similares: la referencia apunta a algo que parece datos válidos, pero no lo es, introduce errores de datos y puede ser difícil de localizar. Para ser sincero, en estos casos preferiría sin pestañear un NPE que falla inmediatamente, ahora,sobre un error lógico / de datos que de repente me golpea más adelante en el camino. ¿Recuerda el estudio de IBM sobre el costo de los errores en función de en qué etapa se detectan?La noción de no hacer nada cuando se llama a un método en
NullObject
no se cumple, cuando se llama a un captador de propiedades o una función, o cuando el método devuelve valores a través deout
. Digamos que el valor de retorno es unint
y decidimos que "no hacer nada" significa "devolver 0". Pero, ¿cómo podemos estar seguros de que 0 es el "nada" correcto para ser (no) devuelto? Después de todo, elNullObject
no debe hacer nada, pero cuando se le pide un valor, tiene que reaccionar de alguna manera. Fallar no es una opción: utilizamos elNullObject
para evitar el NPE, y seguramente no lo cambiaremos por otra excepción (no implementado, dividir por cero, ...), ¿verdad? Entonces, ¿cómo no devuelves nada correctamente cuando tienes que devolver algo?No puedo evitarlo, pero cuando alguien intenta aplicar el
NullObject
patrón a todos y cada uno de los problemas, se parece más a intentar reparar un error haciendo otro. Es sin duda una solución buena y útil en algunos casos, pero seguramente no es la bala mágica para todos los casos.fuente
null
debería existir? Es posible agitar a mano cualquier defecto de idioma con "no es un problema, simplemente no se equivoque".Maybe T
que puede contenerNothing
unT
valor o uno . La clave aquí es que se ve obligado a verificar si un contenidoMaybe
realmente contiene unT
antes de que se le permita usarlo, y proporcionar un curso de acción en caso de que no lo contenga . Y debido a que ha rechazado punteros inválidos, ahora nunca tiene que verificar nada que no sea unMaybe
.t.Something()
? ¿O tiene alguna forma de saber que ya hizo la verificación y no obligarlo a hacerlo nuevamente? ¿Qué pasa si hiciste la verificación antes de pasart
a este método? Con el tipo Opción, el compilador sabe si ya ha realizado la comprobación o no al observar el tipo det
. Si es una Opción, no la ha marcado, mientras que si es el valor sin envolver, entonces sí.Las referencias nulas son un error porque permiten código no sensorial:
Existen alternativas, si aprovecha el sistema de tipos:
La idea general es colocar la variable en un cuadro, y lo único que puede hacer es desempaquetarla, preferiblemente alistando la ayuda del compilador como se propone para Eiffel.
Haskell lo tiene desde cero (
Maybe
), en C ++ puede aprovechar,boost::optional<T>
pero aún puede obtener un comportamiento indefinido ...fuente
Maybe
no está integrado en el núcleo del lenguaje, su definición sólo pasa a ser en el preludio estándar:data Maybe t = Nothing | Just t
.T
divididos por otros valores de tipoT
, limitaría la operación a valores de tipoT
divididos por valores de tipoNonZero<T>
.Tipos opcionales y coincidencia de patrones. Como no conozco C #, aquí hay un código en un lenguaje ficticio llamado Scala # :-)
fuente
El problema con los nulos es que los lenguajes que les permiten te obligan a programar defensivamente contra él. Se requiere mucho esfuerzo (mucho más que tratar de usar bloqueos defensivos) para asegurarse de que
Entonces, de hecho, los nulos terminan siendo algo costoso.
fuente
La cuestión de hasta qué punto su lenguaje de programación intenta probar la corrección de su programa antes de ejecutarlo. En un lenguaje de tipo estático, demuestra que tiene los tipos correctos. Al pasar al valor predeterminado de referencias no anulables (con referencias anulables opcionales) puede eliminar muchos de los casos en los que se pasa nulo y no debería ser así. La pregunta es si el esfuerzo adicional en el manejo de referencias no anulables vale la pena en términos de corrección del programa.
fuente
El problema no es tanto nulo, es que no se puede especificar un tipo de referencia no nulo en muchos idiomas modernos.
Por ejemplo, su código podría verse así
Cuando se transmiten las referencias de clase, la programación defensiva lo alienta a verificar todos los parámetros para ver si son nulos nuevamente, incluso si acaba de verificarlos como nulos de antemano, especialmente al construir unidades reutilizables bajo prueba. ¡La misma referencia podría comprobarse nula fácilmente 10 veces en la base de código!
¿No sería mejor si pudieras tener un tipo anulable normal cuando obtienes la cosa, tratar con nulo en ese momento y luego, pasarlo como un tipo no anulable a todas tus pequeñas funciones y clases de ayuda sin verificar nulos todos ¿hora?
Ese es el punto de la solución Opción: no permitirle activar estados de error, sino permitir el diseño de funciones que implícitamente no pueden aceptar argumentos nulos. Si la implementación "predeterminada" de tipos es anulable o no anulable es menos importante que la flexibilidad de tener ambas herramientas disponibles.
http://twistedoakstudios.com/blog/Post330_non-nullable-types-vs-c-fixing-the-billion-dollar-mistake
Esta también figura como la característica más votada # 2 para C # -> https://visualstudio.uservoice.com/forums/121579-visual-studio/category/30931-languages-c
fuente
None
cada paso del camino.Nulo es malo. Sin embargo, la falta de un nulo puede ser un mal mayor.
El problema es que en el mundo real a menudo tienes una situación en la que no tienes datos. Su ejemplo de la versión sin nulos aún puede explotar: o cometió un error lógico y olvidó buscar a Goodman o tal vez Goodman se casó entre cuando lo revisó y cuando la buscó. (Ayuda a evaluar la lógica si crees que Moriarty está mirando cada parte de tu código y tratando de hacerte tropezar).
¿Qué hace la búsqueda de Goodman cuando ella no está allí? Sin un valor nulo, debe devolver algún tipo de cliente predeterminado, y ahora está vendiendo cosas a ese valor predeterminado.
Básicamente, se trata de si es más importante que el código funcione sin importar qué o si funciona correctamente. Si el comportamiento inapropiado es preferible a ningún comportamiento, entonces no querrá valores nulos. (Para ver un ejemplo de cómo esta podría ser la elección correcta, considere el primer lanzamiento del Ariane V. Un error no detectado / 0 provocó que el cohete diera un giro difícil y la autodestrucción se disparara al desarmarse debido a esto. El valor estaba tratando de calcular que en realidad ya no sirvió para nada en el instante en que se encendió el amplificador, habría puesto en órbita a pesar de la rutina que devolvía la basura).
Al menos 99 de cada 100 veces elegiría usar nulos. Sin embargo, saltaría de alegría al notar la versión de ellos.
fuente
null
es la única (o incluso preferible) forma de modelar la ausencia de un valor.Optimizar para el caso más común.
Tener que verificar nulo todo el tiempo es tedioso: desea poder obtener el objeto Cliente y trabajar con él.
En el caso normal, esto debería funcionar bien: realiza la búsqueda, obtiene el objeto y lo usa.
En el caso excepcional , cuando está buscando (aleatoriamente) un cliente por su nombre, sin saber si ese registro / objeto existe, necesitaría alguna indicación de que esto falló. En esta situación, la respuesta es lanzar una excepción RecordNotFound (o dejar que el proveedor de SQL a continuación haga esto por usted).
Si se encuentra en una situación en la que no sabe si puede confiar en los datos que ingresan (el parámetro), tal vez porque fueron ingresados por un usuario, entonces también podría proporcionar el 'TryGetCustomer (nombre, cliente externo)' modelo. Referencia cruzada con int.Parse e int. TryParse.
fuente
¡Las excepciones no se evalúan!
Están ahí por una razón. Si su código está funcionando mal, hay un patrón genial, llamado excepción , y le dice que algo está mal.
Al evitar el uso de objetos nulos, está ocultando parte de esas excepciones. No estoy hablando del ejemplo OP donde convierte la excepción de puntero nulo en una excepción bien escrita, esto podría ser algo bueno ya que aumenta la legibilidad. Sin embargo, cuando toma la solución de tipo Opción como señaló @Jonas, está ocultando excepciones.
Que en su aplicación de producción, en lugar de que se produzca una excepción al hacer clic en el botón para seleccionar una opción vacía, no sucede nada. En lugar de una excepción de puntero nulo se generaría y probablemente obtendríamos un informe de esa excepción (como en muchas aplicaciones de producción tenemos dicho mecanismo), y de lo que podríamos solucionarlo.
Hacer que su código sea a prueba de balas evitando excepciones es una mala idea, hacer que su código sea a prueba de balas arreglando excepciones, bueno, este es el camino que elegiría.
fuente
None
de algo que no sea una Opción en un error en tiempo de compilación, y de la misma manera, use unOption
como si tuviera un valor sin verificar primero que es un error en tiempo de compilación. Los errores de compilación son ciertamente mejores que las excepciones de tiempo de ejecución.s: String = None
, tienes que decirs: Option[String] = None
. Y sis
es una Opción, no puede decirs.length
, sin embargo, puede verificar que tenga un valor y extraer el valor al mismo tiempo, con coincidencia de patrones:s match { case Some(str) => str.length; case None => throw RuntimeException("Where's my value?") }
s: String = null
, pero ese es el precio de la interoperabilidad con Java. Generalmente no permitimos ese tipo de cosas en nuestro código Scala.Nulls
son problemáticos porque deben verificarse explícitamente, pero el compilador no puede advertirle que olvidó verificarlos. Solo el análisis estático que lleva mucho tiempo puede decirle eso. Afortunadamente, hay varias buenas alternativas.Saca la variable del alcance. Con demasiada frecuencia,
null
se usa como un marcador de posición cuando un programador declara una variable demasiado pronto o la mantiene demasiado tiempo. El mejor enfoque es simplemente deshacerse de la variable. No lo declare hasta que tenga un valor válido para poner allí. Esta no es una restricción tan difícil como podría pensar, y hace que su código sea mucho más limpio.Usa el patrón de objeto nulo. A veces, un valor faltante es un estado válido para su sistema. El patrón de objeto nulo es una buena solución aquí. Evita la necesidad de verificaciones explícitas constantes, pero aún así le permite representar un estado nulo. No se trata de "ocultar una condición de error", como afirman algunas personas, porque en este caso un estado nulo es un estado semántico válido. Dicho esto, no debe usar este patrón cuando un estado nulo no es un estado semántico válido. Simplemente debe sacar su variable fuera del alcance.
Use un Quizás / Opción. En primer lugar, esto permite que el compilador le advierta que necesita verificar un valor faltante, pero hace más que reemplazar un tipo de verificación explícita con otro. Utilizando
Options
, puede encadenar su código y continuar como si su valor existiera, sin necesidad de verificarlo hasta el último minuto absoluto. En Scala, su código de ejemplo se vería así:En la segunda línea, donde estamos construyendo el mensaje impresionante, no hacemos una verificación explícita si se encontró al cliente, y la belleza es que no necesitamos hacerlo . No es hasta la última línea, que podría ser muchas líneas más tarde, o incluso en un módulo diferente, donde explícitamente nos preocupamos por lo que sucede si
Option
es asíNone
. No solo eso, si nos hubiéramos olvidado de hacerlo, no habría marcado el tipo. Incluso entonces, se hace de una manera muy natural, sin unaif
declaración explícita .Compare eso con un
null
, donde escribe las comprobaciones bien, pero donde tiene que hacer una verificación explícita en cada paso del camino o toda su aplicación explota en el tiempo de ejecución, si tiene suerte durante un caso de uso, su unidad prueba el ejercicio. Simplemente no vale la pena.fuente
El problema central de NULL es que hace que el sistema no sea confiable. En 1980 Tony Hoare en el periódico dedicado a su Premio Turing escribió:
El lenguaje ADA ha cambiado mucho desde entonces, sin embargo, tales problemas todavía existen en Java, C # y muchos otros lenguajes populares.
Es deber del desarrollador crear contratos entre un cliente y un proveedor. Por ejemplo, en C #, como en Java, puede usar
Generics
para minimizar el impacto de laNull
referencia creando solo lecturaNullableClass<T>
(dos Opciones):y luego úsalo como
o use dos opciones de estilo con métodos de extensión C #:
La diferencia es significativa. Como cliente de un método, sabe qué esperar. Un equipo puede tener la regla:
Si una clase no es de tipo NullableClass, entonces su instancia no debe ser nula .
El equipo puede fortalecer esta idea utilizando Diseño por contrato y verificación estática en el momento de la compilación, por ejemplo, con la condición previa:
o para una cuerda
Este enfoque puede aumentar drásticamente la confiabilidad de la aplicación y la calidad del software. Design by Contract es un caso de lógica Hoare , que fue poblada por Bertrand Meyer en su famoso libro de Construcción de Software Orientado a Objetos y lenguaje Eiffel en 1988, pero no se usa de manera inválida en la creación moderna de software.
fuente
No.
El hecho de que un idioma no
null
tenga en cuenta un valor especial como es el problema del idioma. Si observa idiomas como Kotlin o Swift , que obligan al escritor a abordar explícitamente la posibilidad de un valor nulo en cada encuentro, entonces no hay peligro.En lenguajes como la que nos ocupa, el lenguaje permite
null
ser un valor de cualquier -ish tipo, pero no proporciona ningún construcciones para reconocer que más tarde, lo que lleva a las cosas que sonnull
de forma inesperada (especialmente cuando se pasa a usted de algún otro lugar), y por lo tanto, se bloquea. Ese es el mal: dejarte usarnull
sin hacerte lidiar con eso.fuente
Básicamente, la idea central es que los problemas de referencia de puntero nulo deben quedar atrapados en el momento de la compilación en lugar del tiempo de ejecución. Entonces, si está escribiendo una función que toma algún objeto y no desea que nadie lo llame con referencias nulas, entonces el sistema de tipos debería permitirle especificar ese requisito para que el compilador pueda usarlo para garantizar que la llamada a su función nunca compilar si la persona que llama no cumple con sus requisitos. Esto se puede hacer de muchas maneras, por ejemplo, decorando sus tipos o usando tipos de opciones (también llamados tipos anulables en algunos idiomas), pero obviamente eso es mucho más trabajo para los diseñadores de compiladores.
Creo que es comprensible por qué en los años sesenta y setenta, el diseñador de compiladores no hizo todo lo posible para implementar esta idea porque las máquinas tenían recursos limitados, los compiladores debían ser relativamente simples y nadie sabía realmente qué tan malo sería esto. años más adelante.
fuente
Tengo una simple objeción contra
null
:Rompe la semántica de su código por completo al introducir ambigüedad .
A menudo espera que el resultado sea de cierto tipo. Esto es semánticamente sólido. Digamos que usted le pidió a la base de datos un usuario con cierto
id
, espera que el resultado sea de cierto tipo (=user
). Pero, ¿qué pasa si no hay usuario de esa identificación? Una solución (mala) es: agregar unnull
valor. Entonces, el resultado es ambiguo : es unuser
o esnull
. Peronull
no es el tipo esperado. Y aquí es donde comienza el olor del código .Al evitarlo
null
, deja su código semánticamente claro.Siempre hay formas de
null
evitarlo: refactorizar a coleccionesNull-objects
,optionals
etc.fuente
Si será semánticamente posible acceder a una ubicación de almacenamiento de referencia o tipo de puntero antes de ejecutar el código para calcular un valor para él, no hay una elección perfecta de lo que debería suceder. Tener tales ubicaciones predeterminadas a referencias de puntero nulo que se pueden leer y copiar libremente, pero que fallarán si se desreferencian o indexan, es una opción posible. Otras opciones incluyen:
La última opción podría tener algún atractivo, especialmente si un lenguaje incluye tipos anulables y no anulables (uno podría llamar a los constructores de matrices especiales para cualquier tipo, pero solo se requiere llamarlos al crear matrices de tipos no anulables), pero probablemente no hubiera sido factible cuando se inventaron los punteros nulos. De las otras opciones, ninguna parece más atractiva que permitir la copia de punteros nulos, pero no desreferenciarlos o indexarlos. El enfoque n. ° 4 puede ser conveniente para tener como opción, y debería ser bastante económico de implementar, pero ciertamente no debería ser la única opción. Requerir que los punteros deben apuntar de manera predeterminada a algún objeto válido en particular es mucho peor que tener punteros predeterminados a un valor nulo que puede leerse o copiarse pero no desreferenciarse o indexarse.
fuente
Sí, NULL es un diseño terrible , en un mundo orientado a objetos. En pocas palabras, el uso NULL conduce a:
Consulte esta publicación de blog para obtener una explicación detallada: http://www.yegor256.com/2014/05/13/why-null-is-bad.html
fuente