language agnostic - ¿Qué haríamos sin NULL?
language-agnostic language-design (11)
Una vez leí que tener tipos anulables es un mal absoluto. Creo que fue en un artículo escrito por la misma persona que los creó (¿en Ada?) Creo que este es el artículo
De todos modos, ¿qué pasa si, de forma predeterminada, un idioma como C # utiliza tipos no anulables? ¿Cómo reemplazaría algunos de los modismos comunes en C # o Ruby o cualquier otro idioma común donde null
sea un valor aceptable?
¿Qué haríamos sin NULL? ¡Inventalo! :-) No tiene que ser un científico espacial para usar 0 si está buscando un valor de puntero dentro de la banda para expresar en realidad no un puntero .
Creamos todo tipo de construcciones extrañas para transmitir el mensaje de un objeto "no válido" o "no estar allí", como se ve en las otras respuestas. Un mensaje que null
puede transmitir muy bien.
- El http://en.wikipedia.org/wiki/Null_Object_pattern tiene sus desventajas, como expliqué aquí .
- Nulos específicos del dominio. Esto te obliga a verificar los números mágicos, lo cual es malo .
- Envoltorios de colección, donde una colección vacía significa "sin valor". Nullable envoltorios que Nullable serían mejores, pero eso no difiere mucho de la comprobación de
null
o el uso del patrón Objeto nulo.
Personalmente, escribiría un preprocesador de C # que me permita usar null
. Esto luego se asignaría a algún objeto dynamic
, que lanza una NullReferenceException
cada vez que se invoca un método en él.
En 1965, las referencias nulas pueden haber parecido un error. Pero hoy en día, con todo tipo de herramientas de análisis de código que nos advierten sobre referencias nulas, no tenemos que preocuparnos mucho. Desde una perspectiva de programación null
es una palabra clave muy valiosa.
Creo que se está refiriendo a esta charla: " Referencias nulas: El error de mil millones de dólares "
El problema no es nulo, es el lenguaje que le permite escribir código que accede a valores que posiblemente pueden ser nulos.
Si el idioma simplemente requiere que se verifique o convierta primero el acceso a un puntero a un tipo no anulable, el 99% de los errores relacionados con nulos desaparecerían. Ej. En C ++
void fun(foo *f)
{
f->x; // error: possibly null
if (f)
{
f->x; // ok
foo &r = *f; // ok, convert to non-nullable type
if (...) f = bar; // possibly null again
f->x; // error
r.x; // ok
}
}
Lamentablemente, esto no se puede adaptar a la mayoría de los idiomas, ya que rompería una gran cantidad de código, pero sería bastante razonable para un nuevo idioma.
Hablando de manera realista, en cualquier lenguaje de programación potente que permita punteros u referencias a objetos en primer lugar, habrá situaciones en las que el código podrá acceder a los punteros que no tengan ningún código de inicialización. Puede ser posible garantizar que dichos punteros se inicialicen a algún valor estático, pero eso no parece terriblemente útil. Si una máquina tiene un medio general de interceptar accesos a variables no inicializadas (ya sean punteros o algo más), eso es mejor que los punteros nulos de carcasas especiales, pero los errores más grandes relacionados con nulos que veo ocurren en implementaciones que permiten la aritmética con punteros nulos . Agregar 5 a (char *) 0 no debería producir un puntero a la dirección 5; debería provocar un error (si es apropiado crear punteros a direcciones absolutas, debería haber algún otro medio para hacerlo).
Haskell es un lenguaje poderoso que no tiene el concepto de nulidad. Básicamente, cada variable debe inicializarse a un valor no nulo. Si desea representar una variable "opcional" (la variable puede tener un valor pero puede no tenerlo), puede usar un tipo especial "Tal vez".
Es más fácil implementar este sistema en Haskell que en C # porque los datos son inmutables en Haskell, por lo que realmente no tiene sentido tener una referencia nula que luego rellene. Sin embargo, en C #, el último enlace de una lista vinculada puede tener un puntero nulo al siguiente enlace, que se rellena cuando la lista se expande. No sé cómo sería un lenguaje de procedimiento sin tipos nulos.
Además, tenga en cuenta que muchas de las personas anteriores parecen sugerir la sustitución de valores nulos con valores lógicos de "nada" específicos del tipo (999-999-9999, " NULL ", etc.). Estos valores realmente no resuelven nada porque el problema que tienen las personas con los nulos es que son un caso especial, pero las personas se olvidan de codificar para el caso especial. Con los valores de nada lógico específicos del tipo, las personas AÚN se olvidan de codificar para el caso especial, pero evitan los errores que detectan este error, lo cual es algo malo.
Puede adoptar una regla simple: todas las variables se inicializan (de forma predeterminada, esto se puede anular) a un valor inmutable, definido por la clase de la variable. Para los escalares, esto generalmente sería una forma de cero. Para las referencias, cada clase definiría cuál es su valor "nulo" y las referencias se inicializarán con un puntero a este valor.
Esto sería efectivamente una implementación de todo el lenguaje del patrón NullObject: http://en.wikipedia.org/wiki/Null_Object_pattern Por lo tanto, realmente no elimina los objetos nulos, solo evita que sean casos especiales que deben ser manejado como tal.
Tcl es un lenguaje que no solo no tiene el concepto de nulo, sino que el concepto de nulo en sí mismo no concuerda con el núcleo del lenguaje. En tcl decimos: ''todo es una cuerda''. Lo que realmente significa es que tcl tiene una semántica de valor estricta (que simplemente pasa de forma predeterminada a las cadenas).
Entonces, ¿qué utilizan los programadores tcl para representar "no-datos"? Sobre todo es la cadena vacía. En algunos casos donde la cadena vacía puede representar datos, entonces es típicamente uno de:
Use una cadena vacía de todos modos, la mayoría de las veces no hace ninguna diferencia para el usuario final.
Use un valor que sepa que no existirá en el flujo de datos, por ejemplo, la cadena
"_NULL_"
o el número9999999
o mi favorito el byte NUL"/0"
.Utilice una estructura de datos envuelta alrededor del valor; la más simple es una lista (lo que otros idiomas llaman matrices). Una lista de un elemento significa que el valor existe, cero elemento significa nulo.
Probar la existencia de la variable -
[info exists variable_name]
.
Es interesante observar que Tcl no es el único lenguaje con semántica de valor estricto. C también tiene una semántica de valores estricta, pero la semántica predeterminada de los valores simplemente es que son enteros en lugar de cadenas.
Oh, casi me olvido de otro:
Algunas bibliotecas usan una variación del número 2 que le permite al usuario especificar cuál es el marcador de posición para "sin datos". Básicamente, le permite especificar un valor predeterminado (y si no lo hace, el valor predeterminado normalmente es una cadena vacía).
Usamos cualquiera
Discriminadores. Un atributo o indicador o indicador adicional que dice que un valor es "nulo" y debe ignorarse.
Nulos específicos del dominio. Un valor específico, dentro del dominio permitido, que se interpreta como "ignorar este valor". Por ejemplo, un número de seguro social de 999-99-9999 podría ser un valor nulo específico del dominio que dice que el SSN es desconocido o no aplicable.
Usaríamos los tipos de opción para los (muy) pocos lugares donde permitir un valor nulo es realmente deseable, y tendríamos errores mucho menos oscuros, ya que cualquier referencia a un objeto apuntaría a una instancia válida del tipo apropiado.
Yo diría que en lugar de declarar abiertamente que los tipos anulables son malvados, la mayoría de los lenguajes incluyen la nulabilidad sobre tipos enteros de tipos, cuando los dos conceptos deberían ser realmente ortogonales .
Por ejemplo, todos los tipos de Java no primitivos (y todos los tipos de referencia de C #) son anulables. ¿Por qué? Podemos retroceder y avanzar, pero en última instancia, apuesto a que la respuesta se reduce a "fue fácil". No hay nada intrínseco al lenguaje Java que exija nulabilidad generalizada. Las referencias de C ++ ofrecieron un buen ejemplo de cómo exorcizar valores nulos en el nivel del compilador. Por supuesto, C ++ tiene una sintaxis mucho más fea que Java intentaba restringir explícitamente, por lo que algunas características buenas terminaron en la planta de corte junto con las malas.
Los tipos de valores que aceptan valores nulos en C # 2.0 ofrecieron un paso en la dirección correcta: desacoplar la capacidad de nulos de la semántica de tipos no relacionados o, peor aún, los detalles de implementación de CLR, pero aún falta una manera de hacer lo contrario con los tipos de referencia. (Los contratos de código son geniales y todos, pero no están integrados en el sistema de tipos de la forma en que lo discutimos aquí).
Muchos de los lenguajes funcionales u otros oscuros lograron que estos conceptos fueran "directos" desde el principio ... pero si fueran de uso generalizado, no tendríamos esta discusión ...
Para responder a su pregunta: prohibir los nulos de un lenguaje moderno, al por mayor, sería tan tonto como el llamado "error de mil millones de dólares". Existen construcciones de programación válidas en las que es bueno tener nulos: parámetros opcionales, cualquier tipo de cálculo predeterminado / de reserva donde el operador de coalescencia conduce a un código conciso, interacción con bases de datos relacionales, etc. Obligarse a usar valores de centinela, NaN, etc. Una "cura" mucho peor que la enfermedad.
Dicho esto, concordaré tentativamente con el sentimiento expresado en la cita, siempre que pueda elaborar para que se ajuste a mi propia experiencia:
- El número de situaciones donde los valores nulos son deseables es más pequeño de lo que la mayoría de la gente piensa.
- Una vez que introduce nulos en una biblioteca o ruta de acceso, es mucho más difícil deshacerse de ellos que agregarlos. (¡Así que no dejes que los programadores junior lo hagan por capricho!)
- Escala de errores anulables con vida útil variable
- Correspondiente a # 3: choque temprano