Si los valores nulos son malos, ¿qué se debe usar cuando un valor puede estar significativamente ausente?


26

Esta es una de las reglas que se repiten una y otra vez y que me deja perplejo.

Los nulos son malvados y deben evitarse siempre que sea posible .

Pero, pero, por mi ingenuidad, déjame gritar, ¡a veces un valor PUEDE estar significativamente ausente!

Permítanme preguntar esto en un ejemplo que proviene de este horrible código montado contra el patrón en el que estoy trabajando en este momento . Este es, en esencia, un juego multijugador basado en turnos web, donde los turnos de ambos jugadores se ejecutan simultáneamente (como en Pokémon, y opuestamente al ajedrez).

Después de cada turno, el servidor transmite una lista de actualizaciones al código JS del lado del cliente. La clase de actualización:

public class GameUpdate
{
    public Player player;
    // other stuff
}

Esta clase se serializa a JSON y se envía a los jugadores conectados.

La mayoría de las actualizaciones naturalmente tienen un jugador asociado con ellas; después de todo, es necesario saber qué jugador hizo qué movimiento en este turno. Sin embargo, algunas actualizaciones no pueden tener un reproductor significativamente asociado con ellas. Ejemplo: El juego ha sido forzado porque el límite de turno sin acciones ha sido excedido. Para tales actualizaciones, creo, es significativo que el jugador sea anulado.

Por supuesto, podría "arreglar" este código utilizando la herencia:

public class GameUpdate
{
    // stuff
}

public class GamePlayerUpdate : GameUpdate
{
    public Player player;
    // other stuff
}

Sin embargo, no veo cómo esto mejora, por dos razones:

  • El código JS ahora simplemente recibirá un objeto sin Player como una propiedad definida, que es lo mismo que si fuera nulo ya que ambos casos requieren verificar si el valor está presente o ausente;
  • En caso de que se agregue otro campo anulable a la clase GameUpdate, tendría que poder usar la herencia múltiple para continuar con este diseño, pero MI es malo en sí mismo (según los programadores más experimentados) y, lo que es más importante, C # no lo tengo para que no pueda usarlo.

Tengo la impresión de que este fragmento de este código es uno de los muchos en los que un programador experimentado y bueno gritaría con horror. Al mismo tiempo, no puedo ver cómo, en este lugar, es nulo dañar algo y qué debería hacerse en su lugar.

¿Podría explicarme este problema?


2
¿Es esta pregunta específica de JavaScript? Muchos idiomas tienen un tipo opcional para este propósito, pero no sé si js tiene uno (aunque podría hacer uno) o si funcionaría con json (aunque esa es una parte de las comprobaciones nulas desagradables en lugar de todo su código
Richard Tingle

99
Esa regla es simplemente falsa. Estoy un poco asombrado de que alguien se suscriba a esa noción. Hay algunas buenas alternativas a nulo en algunos casos. nulo es realmente bueno para representar un valor perdido. No permita que reglas aleatorias de Internet como esa le impidan confiar en su propio juicio razonado.
usr

1
@ usr - Estoy totalmente de acuerdo. Null es tu amigo. En mi humilde opinión, no hay una manera más clara de representar un valor perdido. Puede ayudarlo a encontrar errores, puede ayudarlo a manejar las condiciones de error, puede ayudarlo a pedir perdón (intentar / atrapar). ¡¿Que es no gustar?!
Buceo Steve

44
Una de las primeras reglas de diseño de software es "Verificar siempre si es nulo". Por qué esto es incluso un problema es alucinante para mí.
MATE

1
@MattE - Absolutamente. ¿Por qué debería diseñar un patrón de diseño cuando realmente, Null es algo bueno en tu caja de herramientas básica? Lo que Graham estaba sugiriendo, en mi humilde opinión, me parece una ingeniería excesiva.
Buceo Steve

Respuestas:


20

Es mejor devolver muchas cosas que anularlas.

  • Una cadena vacía ("")
  • Una colección vacía
  • Una mónada "opcional" o "tal vez"
  • Una función que silenciosamente no hace nada.
  • Un objeto lleno de métodos que silenciosamente no hacen nada.
  • Un valor significativo que rechaza la premisa incorrecta de la pregunta
    (que es lo que te hizo considerar nulo en primer lugar)

El truco es darse cuenta de cuándo hacer esto. Null es realmente bueno para explotar en tu cara, pero solo cuando lo salvas sin verificarlo. No es bueno para explicar por qué.

Cuando no necesite hacer estallar, y no quiera verificar si es nulo, use una de estas alternativas. Puede parecer extraño devolver una colección si solo tiene 0 o 1 elementos, pero es realmente bueno para permitirle tratar silenciosamente los valores perdidos.

Las mónadas suenan elegantes, pero aquí todo lo que están haciendo es dejar que sea obvio que los tamaños válidos para la colección son 0 y 1. Si los tiene, considérelos.

Cualquiera de estos hace un mejor trabajo al dejar su intención clara que la nula. Esa es la diferencia más importante.

Puede ser útil entender por qué regresa en lugar de simplemente lanzar una excepción. Las excepciones son esencialmente una forma de rechazar una suposición (no son la única forma). Cuando pregunta por el punto donde se cruzan dos líneas, asume que se cruzan. Pueden ser paralelos. ¿Deberías lanzar una excepción de "líneas paralelas"? Bueno, podría, pero ahora tiene que manejar esa excepción en otro lugar o dejar que detenga el sistema.

Si prefiere quedarse donde está, puede convertir el rechazo de esa suposición en un tipo de valor que puede expresar resultados o rechazos. No es tan raro. Lo hacemos en álgebra todo el tiempo. El truco es hacer que ese valor sea significativo para que podamos entender lo que sucedió.

Si el valor devuelto puede expresar resultados y rechazos, debe enviarse a un código que pueda manejar ambos. El polimorfismo es realmente poderoso para usar aquí. En lugar de simplemente intercambiar cheques nulos por isPresent()cheques, puede escribir código que se comporte bien en cualquier caso. Ya sea reemplazando valores vacíos con valores predeterminados o haciendo silenciosamente nada.

El problema con nulo es que puede significar demasiadas cosas. Entonces, simplemente termina destruyendo información.


En mi experimento de pensamiento nulo favorito, le pido que imagine una máquina Rube Goldbergian compleja que envía mensajes codificados utilizando luz de colores recogiendo bombillas de colores de una cinta transportadora, atornillándolas en un enchufe y encendiéndolas. La señal es controlada por los diferentes colores de los bulbos que se colocan en la cinta transportadora.

Se le ha pedido que haga que esta cosa horriblemente costosa cumpla con RFC3.14159 que establece que debería haber un marcador entre los mensajes. El marcador no debe ser luz en absoluto. Debe durar exactamente un pulso. La misma cantidad de tiempo que normalmente brilla una luz de un solo color.

No puede dejar espacios entre los mensajes porque el artilugio activa las alarmas y se detiene si no puede encontrar una bombilla para enchufar.

Todos los que están familiarizados con este proyecto se estremecen al pensar en tocar la maquinaria o su código de control. No es fácil cambiar. ¿Qué haces? Bueno, puedes sumergirte y comenzar a romper cosas. Tal vez actualizar este diseño horrible cosas. Sí, podrías hacerlo mucho mejor. O podría hablar con el conserje y comenzar a recolectar bombillas quemadas.

Esa es la solución polimórfica. El sistema ni siquiera necesita saber que algo ha cambiado.


Es realmente agradable si puedes lograrlo. Al codificar un rechazo significativo de una suposición en un valor, no tiene que forzar el cambio de la pregunta.

¿Cuántas manzanas me deberás si te doy 1 manzana? -1. Porque te debía 2 manzanas de ayer. Aquí la suposición de la pregunta, "me deberás", se rechaza con un número negativo. Aunque la pregunta era incorrecta, en esta respuesta se codifica información significativa. Al rechazarlo de manera significativa, la pregunta no se ve obligada a cambiar.

Sin embargo, a veces cambiar la pregunta en realidad es el camino a seguir. Si la suposición se puede hacer más confiable, aunque aún sea útil, considere hacerlo.

Su problema es que, normalmente, las actualizaciones provienen de los jugadores. Pero ha descubierto la necesidad de una actualización que no provenga del jugador 1 o del jugador 2. Necesita una actualización que diga que el tiempo ha expirado. Ninguno de los jugadores está diciendo esto, ¿qué debes hacer? Dejar al jugador nulo parece tan tentador. Pero destruye la información. Estás tratando de codificar el conocimiento en un agujero negro.

El campo de jugador no te dice quién se acaba de mudar. Crees que sí, pero este problema demuestra que es una mentira. El campo del jugador te dice de dónde vino la actualización. La actualización de tiempo expirado proviene del reloj del juego. Mi recomendación es darle al reloj el crédito que merece y cambiar el nombre del playercampo a algo así source.

De esta manera, si el servidor se está cerrando y tiene que suspender el juego, puede enviar una actualización que informa lo que está sucediendo y se enumera como la fuente de la actualización.

¿Esto significa que nunca deberías dejar algo afuera? No. Pero debe considerar qué tan bien se puede suponer que nada significa realmente un solo valor predeterminado bien conocido. Nada es realmente fácil de usar en exceso. Así que ten cuidado cuando lo uses. Hay una escuela de pensamiento que abarca sin favorecer nada . Se llama convención sobre la configuración .

A mí me gusta, pero solo cuando la convención se comunica claramente de alguna manera. No debería ser una forma de enturbiar a los novatos. Pero incluso si está usando eso, nulo aún no es la mejor manera de hacerlo. Nulo es una nada peligrosa . Null pretende ser algo que puedes usar hasta que lo uses, luego hace un agujero en el universo (rompe tu modelo semántico). A menos que hacer un agujero en el universo sea el comportamiento que necesitas, ¿por qué estás jugando con esto? Usa algo más.


99
"devolver una colección si solo tiene 0 o 1 elementos" - Lo siento, pero no creo que la obtenga :( Desde mi POV, hacer esto (a) agrega estados inválidos innecesarios a la clase (b) requiere documentar que la colección debe tener como máximo 1 elemento (c) no parece resolver el problema de todos modos, ya que el chequeo si la colección contiene su único elemento antes de acceder es necesario de todos modos, o de lo contrario se lanzará una excepción
gaazkam

2
@gaazkam ves? Te dije que molesta a la gente. Sin embargo, es exactamente lo que has estado haciendo cada vez que usas la cadena vacía.
candied_orange

16
¿Qué sucede cuando hay un valor válido que resulta ser una cadena o conjunto vacío?
Blrfl

55
De todos modos @gaazkam, no verificas explícitamente si la colección tiene elementos. El bucle (o mapeo) sobre una lista vacía es una acción segura que no tiene ningún efecto.
RubberDuck

3
Sé que está respondiendo la pregunta sobre qué debería hacer alguien en lugar de devolver nulo, pero realmente no estoy de acuerdo con muchos de estos puntos. Sin embargo, como no somos específicos del idioma y hay una razón para romper todas las reglas, me niego a votar negativamente
Liath

15

nullNo es realmente el problema aquí. El problema es cómo la mayoría de los lenguajes de programación actuales manejan la información que falta; no toman en serio este problema (o al menos no brindan el apoyo y la seguridad que la mayoría de los terrícolas necesitan para evitar las trampas) Esto lleva a todos los problemas que hemos aprendido a temer (Javas NullPointerException, Segfaults, ...).

Veamos cómo lidiar con ese problema de una mejor manera.

Otros sugirieron el patrón de objeto nulo . Si bien creo que a veces es aplicable, hay muchos escenarios en los que simplemente no hay un valor predeterminado de talla única. En esos casos, los consumidores de la información posiblemente ausente deben poder decidir por sí mismos qué hacer si falta esa información.

Hay lenguajes de programación que proporcionan seguridad contra punteros nulos (llamada seguridad nula) en tiempo de compilación. Para dar algunos ejemplos modernos: Kotlin, Rust, Swift. En esos idiomas, el problema de la falta de información se ha tomado en serio y el uso de nulo es totalmente indoloro: el compilador le impedirá desreferenciar los puntos nulos.
En Java, se han realizado esfuerzos para establecer las anotaciones @ Nullable / @ NotNull junto con los complementos del compilador + IDE para alcanzar el mismo efecto. No fue realmente exitoso, pero eso está fuera del alcance de esta respuesta.

Un tipo genérico explícito que denota una posible ausencia es otra forma de tratarlo. Por ejemplo, en Java hay java.util.Optional. Puede funcionar:
a nivel de proyecto o empresa, puede establecer reglas / pautas

  • use solo bibliotecas de terceros de alta calidad
  • siempre use en java.util.Optionallugar denull

Su comunidad / ecosistema de idiomas puede haber encontrado otras formas de abordar este problema mejor que el compilador / intérprete.


¿Podría explicar amablemente cómo son los opcionales de Java mejores que los nulos? Mientras leo la documentación , intentar obtener un valor de lo opcional produce una excepción si el valor está ausente; entonces parece que estamos en el mismo lugar: ¿es necesario un cheque y si olvidamos uno, estamos jodidos?
gaazkam

3
@gaazkam Tienes razón, no proporciona seguridad en tiempo de compilación. Además, el Opcional en sí podría ser nulo, otro problema. Pero es explícito (en comparación con las variables que posiblemente sean comentarios nulos / doc). Esto solo proporciona un beneficio real si, para toda la base de código, se configura una regla de codificación. No puedes poner las cosas nulas. Si la información puede estar ausente, use Opcional. Eso obliga a lo explícito. Los cheques nulos habituales se convierten en llamadas aOptional.isPresent
marstato

8
@marstato reemplazar cheques nulos por isPresent() no es el uso recomendado de Opcional
candied_orange

10

No veo ningún mérito en la declaración de que nulo es malo. Revisé las discusiones al respecto y solo me ponen impaciente e irritado.

Nulo puede usarse incorrectamente, como un "valor mágico", cuando en realidad significa algo. Pero tendrías que hacer una programación bastante retorcida para llegar allí.

Nulo debe significar "no existe", que no se creó (todavía) o (ya) desapareció o no se proporciona si es un argumento. No es un estado, todo falta. En un entorno OO donde las cosas se crean y destruyen todo el tiempo, es una condición válida y significativa diferente de cualquier estado.

Con null, te golpean en tiempo de ejecución, donde te golpean en tiempo de compilación con algún tipo de alternativa nula de tipo seguro.

No del todo cierto, puedo obtener una advertencia por no verificar nulo antes de usar la variable. Y no es lo mismo. Es posible que no quiera ahorrar los recursos de un objeto que no tiene ningún propósito. Y lo más importante, tendré que buscar la alternativa de la misma manera para obtener el comportamiento deseado. Si me olvido de hacer eso, preferiría obtener una NullReferenceException en tiempo de ejecución que algún comportamiento indefinido / no intencionado.

Hay un problema a tener en cuenta. En un contexto de subprocesos múltiples, debe protegerse contra las condiciones de carrera asignando primero a una variable local antes de realizar la verificación de nulo.

El problema con nulo es que puede significar mucho para muchas cosas. Entonces, simplemente termina destruyendo información.

No, significa / no debe significar nada, por lo que no hay nada que destruir. El nombre y el tipo de la variable / argumento siempre dirá lo que falta, eso es todo lo que hay que saber.

Editar:

Estoy a medio camino del video candied_orange vinculado a una de sus otras respuestas sobre programación funcional y es muy interesante. Puedo ver su utilidad en ciertos escenarios. El enfoque funcional que he visto hasta ahora, con trozos de código volando, generalmente me da vergüenza cuando se usa en un entorno OO. Pero supongo que tiene su lugar. Algun lado. Y supongo que habrá idiomas que harán un buen trabajo ocultando lo que percibo como un desastre confuso cada vez que lo encuentre en C #, lenguajes que proporcionan un modelo limpio y viable.

Si ese modelo funcional es su mentalidad / marco, realmente no hay alternativa para impulsar cualquier percance como descripciones documentadas de errores y, en última instancia, dejar que la persona que llama se ocupe de la basura acumulada que fue arrastrada hasta el punto de inicio. Aparentemente, así es como funciona la programación funcional. Llámalo una limitación, llámalo un nuevo paradigma. Puede considerar que es un truco para los discípulos funcionales que les permite continuar un poco más en el camino impuro, puede estar encantado con esta nueva forma de programación que desata nuevas y emocionantes posibilidades. Lo que sea, me parece inútil entrar en ese mundo, volver a la forma tradicional de hacer las cosas (sí, podemos lanzar excepciones, lo siento), elegir algún concepto de él,

Cualquier concepto puede ser usado de manera incorrecta. Desde mi punto de vista, las "soluciones" presentadas no son alternativas para un uso apropiado de nulo. Son conceptos de otro mundo.


99
Null destruye el conocimiento de qué tipo de nada representa. No hay nada que deba ignorarse en silencio. El tipo de nada que necesita detener el sistema para evitar un estado no válido. El tipo de nada que significa que el programador se equivocó y necesita arreglar el código. El tipo de nada que significa que el usuario solicitó algo que no existe y que debe ser educado. Lo siento, no. Todos estos han sido codificados como nulos. Por eso, si codifica cualquiera de estos como nulo, no debería sorprenderse si nadie sabe a qué se refiere. Nos estás obligando a adivinar por contexto.
candied_orange

3
@candied_orange Puede hacer exactamente el mismo argumento sobre cualquier tipo opcional: Nonerepresenta ...
Deduplicador

3
@candied_orange Todavía no entiendo lo que quieres decir. ¿Diferentes tipos de nada? Si usa nulo, ¿cuándo debería haber usado una enumeración quizás? Solo hay un tipo de nada. La razón de que algo no sea nada puede variar, pero eso no cambia la nada ni la respuesta adecuada a algo que no es nada. Si espera un valor en una tienda que no está allí y que es digno de mención, arroja o inicia sesión en el momento de la consulta y no obtiene nada, no pasa nulo como argumento a un método y luego en ese método intenta averiguar por qué te quedaste nulo Nulo no sería el error.
Martin Maat

2
El error es nulo porque tuviste la oportunidad de codificar el significado de la nada. Su comprensión de la nada no exige un manejo inmediato de la nada. 0, nulo, NaN, cadena vacía, conjunto vacío, nulo, objeto nulo, no aplicable, excepción no implementada, nada viene en muchas, muchas formas. No tiene que olvidar lo que sabe ahora solo porque no quiere lidiar con lo que sabe ahora. Si le pido que saque la raíz cuadrada de -1, puede lanzar una excepción para hacerme saber que es imposible y olvidar todo o inventar números imaginarios y preservar lo que sabe.
candied_orange

1
@MartinMaat hace que parezca que una función debe lanzar una excepción cada vez que se violan sus expectativas. Esto simplemente no es cierto. A menudo hay casos de esquina para tratar. Si realmente no puede ver eso, lea esto
candied_orange

7

Tu pregunta.

Pero, pero, por mi ingenuidad, déjame gritar, ¡a veces un valor PUEDE estar significativamente ausente!

Totalmente a bordo con usted allí. Sin embargo, hay algunas advertencias que abordaré en un segundo. Pero primero:

Los nulos son malvados y deben evitarse siempre que sea posible.

Creo que esta es una declaración bien intencionada pero demasiado generalizada.

Los nulos no son malos. No son antipatrones. No son algo que deba evitarse a toda costa. Sin embargo, los valores nulos son propensos a errores (por no comprobar los valores nulos).

Pero no entiendo cómo el hecho de no verificar nulo es de alguna manera una prueba de que nulo es inherentemente incorrecto de usar.

Si nulo es malo, también lo son los índices de matriz y los punteros de C ++. Ellos no están. Son fáciles de disparar en el pie, pero se supone que no deben evitarse a toda costa.

Para el resto de esta respuesta, voy a adaptar la intención de "los nulos son malvados" a otros "los nulos deben ser manejados de manera responsable ".


Tu solución.

Si bien estoy de acuerdo con su opinión sobre el uso de nulo como regla global; No estoy de acuerdo con su uso de nulo en este caso particular.

Para resumir los comentarios a continuación: estás malinterpretando el propósito de la herencia y el polimorfismo. Si bien su argumento para usar nulo tiene validez, su "prueba" adicional de por qué la otra forma es mala se basa en el mal uso de la herencia, lo que hace que sus puntos sean algo irrelevantes para el problema nulo.

La mayoría de las actualizaciones naturalmente tienen un jugador asociado con ellas; después de todo, es necesario saber qué jugador hizo qué movimiento en este turno. Sin embargo, algunas actualizaciones no pueden tener un reproductor significativamente asociado con ellas.

Si estas actualizaciones son significativamente diferentes (que lo son), entonces necesita dos tipos de actualizaciones separadas.

Considere los mensajes, por ejemplo. Tiene mensajes de error y mensajes de chat de mensajería instantánea. Pero si bien pueden contener datos muy similares (un mensaje de cadena y un remitente), no se comportan de la misma manera. Deben usarse por separado, como clases diferentes.

Lo que está haciendo ahora es diferenciar efectivamente entre dos tipos de actualización en función de la nulidad de la propiedad Player. Eso es equivalente a decidir entre un mensaje de MI y un mensaje de error basado en la existencia de un código de error. Funciona, pero no es el mejor enfoque.

  • El código JS ahora simplemente recibirá un objeto sin Player como una propiedad definida, que es lo mismo que si fuera nulo ya que ambos casos requieren verificar si el valor está presente o ausente;

Su argumento se basa en errores del polimorfismo. Si tiene un método que devuelve un GameUpdateobjeto, generalmente no debería importar diferenciar entre GameUpdate, PlayerGameUpdateo NewlyDevelopedGameUpdate.

Una clase base debe tener un contrato que funcione completamente, es decir, sus propiedades se definen en la clase base y no en la clase derivada (dicho esto, el valor de estas propiedades base, por supuesto, puede anularse en las clases derivadas).

Esto hace que su argumento sea discutible. En una buena práctica, nunca debe preocuparte que tu GameUpdateobjeto tenga o no un jugador. Si está intentando acceder a la Playerpropiedad de a GameUpdate, está haciendo algo ilógico.

Los lenguajes mecanografiados como C # ni siquiera le permitirán intentar acceder a la Playerpropiedad de a GameUpdateporque GameUpdatesimplemente no tiene la propiedad. Javascript es considerablemente más laxo en su enfoque (y no requiere precompilación), por lo que no se molesta en corregirlo y, en su lugar, hace que explote en tiempo de ejecución.

  • En caso de que se agregue otro campo anulable a la clase GameUpdate, tendría que poder usar la herencia múltiple para continuar con este diseño, pero MI es malo en sí mismo (según los programadores más experimentados) y, lo que es más importante, C # no lo tengo para que no pueda usarlo.

No debería crear clases basadas en la adición de propiedades anulables. Deberías crear clases basadas en tipos de actualizaciones de juegos funcionalmente únicos .


¿Cómo evitar los problemas con nulo en general?

Creo que es relevante elaborar cómo puede expresar significativamente la ausencia de un valor. Esta es una colección de soluciones (o malos enfoques) sin ningún orden en particular.

1. Utilice nulo de todos modos.

Este es el enfoque más fácil. Sin embargo, se está registrando para un montón de comprobaciones nulas y (si no lo hace) para solucionar las excepciones de referencia nula.

2. Use un valor sin sentido no nulo

Un ejemplo simple aquí es indexOf():

"abcde".indexOf("b"); // 1
"abcde".indexOf("f"); // -1

En lugar de null, obtienes -1. A nivel técnico, este es un valor entero válido. Sin embargo, un valor negativo es una respuesta sin sentido ya que se espera que los índices sean enteros positivos.

Lógicamente, esto solo puede usarse para casos en los que el tipo de retorno permite más valores posibles de los que pueden devolverse significativamente (indexOf = enteros positivos; int = enteros negativos y positivos)

Para los tipos de referencia, aún puede aplicar este principio. Por ejemplo, si tiene una entidad con una ID entera, podría devolver un objeto ficticio con su ID establecida en -1, en lugar de nulo.
Simplemente tiene que definir un "estado ficticio" mediante el cual puede medir si un objeto es realmente utilizable o no.

Tenga en cuenta que no debe confiar en valores de cadena predeterminados si no controla el valor de la cadena. Puede considerar establecer el nombre de un usuario en "nulo" cuando desee devolver un objeto vacío, pero podría encontrar problemas cuando Christopher Null se suscriba a su servicio .

3. Lanzar una excepción en su lugar.

No simplemente no. Las excepciones no deben manejarse para el flujo lógico.

Dicho esto, hay algunos casos en los que no desea ocultar la excepción (nula referencia), sino que muestra una excepción apropiada. Por ejemplo:

var existingPerson = personRepository.GetById(123);

Esto puede arrojar un PersonNotFoundException(o similar) cuando no hay una persona con ID 123.

Obviamente, esto solo es aceptable para los casos en que no encontrar un artículo hace que sea imposible realizar un procesamiento adicional. Si no es posible encontrar un elemento y la aplicación puede continuar, NUNCA debe lanzar una excepción . No puedo enfatizar esto lo suficiente.


5

El punto de por qué los nulos son malos no es que el valor faltante se codifique como nulo, sino que cualquier valor de referencia puede ser nulo. Si tiene C #, probablemente esté perdido de todos modos. No veo un punto para usar algunos Opcionales allí porque un tipo de referencia ya es opcional. Pero para Java hay anotaciones @Notnull, que parece que puede configurar a nivel de paquete , luego, para los valores que se pueden perder, puede usar la anotación @Nullable.


¡Sí! El problema es un defecto sin sentido.
Deduplicador

Estoy en desacuerdo. La programación en un estilo que evita nulos conduce a que menos variables de referencia sean nulas, lo que lleva a menos variables de referencia que no deberían ser nulas. No es 100%, pero tal vez sea 90%, lo que vale la pena en mi opinión.
Vuelva a instalar Mónica

1

Los nulos no son malos, no existe una forma realista de implementar ninguna de las alternativas sin tratar en algún momento algo que parece un nulo.

Los nulos son sin embargo peligrosos . Al igual que cualquier otra construcción de bajo nivel, si te equivocas, no hay nada debajo para atraparte.

Creo que hay muchos argumentos válidos contra nulo:

  • Que si ya está en un dominio de alto nivel, hay patrones más seguros que usar el modelo de bajo nivel.

  • A menos que necesite las ventajas de un sistema de bajo nivel y esté preparado para ser cauteloso, tal vez no debería usar un lenguaje de bajo nivel.

  • etc ...

Todos estos son válidos y además no tener que hacer el esfuerzo de escribir getters y setters, etc. se considera una razón común para no haber seguido ese consejo. No es realmente sorprendente que muchos programadores hayan llegado a la conclusión de que los nulos son peligrosos y perezosos (¡una mala combinación!), Pero como muchos otros consejos "universales", no son tan universales como están redactados.

Las buenas personas que escribieron el idioma pensaron que serían útiles para alguien. Si comprende las objeciones y aún cree que son adecuadas para usted, nadie más lo sabrá mejor.


La cuestión es que el "consejo universal" generalmente no está redactado como "universalmente" como la gente dice. Si encuentra la fuente original de tales consejos, generalmente hablan de las advertencias que los programadores experimentados conocen. Lo que sucede es que, cuando alguien más lee el consejo, tienden a ignorar esas advertencias y recuerdan solo la parte del "consejo universal", por lo que cuando relacionan ese consejo con otros, resulta mucho más "universal" de lo que el creador pretendía. .
Nicol Bolas

1
@NicolBolas, tiene razón, pero la fuente original no es el consejo que se cita a la mayoría de las personas, es el mensaje transmitido. El punto que quería destacar era simplemente que a muchos programadores se les había dicho 'nunca hagas X, X es malo / malo / lo que sea' y, exactamente como dices, faltan muchas advertencias. Tal vez se dejaron caer, tal vez nunca estuvieron presentes, el resultado final es el mismo.
drjpizzle

0

Java tiene una Optionalclase con un ifPresent+ lambda explícito para acceder a cualquier valor de forma segura. Esto también se puede hacer en JS:

Vea esta pregunta de StackOverflow .

Por cierto: muchos puristas encontrarán este uso dudoso, pero no lo creo. Existen características opcionales.


¿No puede una referencia a un java.lang.Optionalser nulltambién?
Deduplicador

@Deduplicator No, Optional.of(null)lanzaría una excepción, Optional.ofNullable(null)daría Optional.empty()el valor no presente.
Joop Eggen

Y Optional<Type> a = null?
Deduplicador

@Deduplicator verdadero, pero allí debe usar Optional<Optional<Type>> a = Optional.empty();;) - En otras palabras, en JS (y otros idiomas) tales cosas no serán factibles. Scala con valores predeterminados haría. Java tal vez con una enumeración. JS dudo: Optional<Type> a = Optional.empty();.
Joop Eggen

Entonces, hemos pasado de una indirección y potencial nullo vacío, a dos más algunos métodos adicionales en el objeto interpuesto. Eso es todo un logro.
Deduplicador

0

CAR Hoare llamó a nulos su "error de mil millones de dólares". Sin embargo, es importante tener en cuenta que pensó que la solución no era "no nulos en ninguna parte", sino "punteros no anulables como predeterminados y nulos solo cuando son semánticamente necesarios".

El problema con nulo en los idiomas que repiten su error es que no se puede decir que una función espera datos no anulables; Es fundamentalmente inexpresable en el idioma. Eso obliga a las funciones a incluir una gran cantidad de repeticiones extrañas inútiles, y olvidar una comprobación nula es una fuente común de errores.

Para hackear esa falta fundamental de expresividad, algunas comunidades (por ejemplo, la comunidad Scala) no usan nulo. En Scala, si desea un valor de tipo anulable T, toma un argumento de tipo Option[T], y si desea un valor no anulable, simplemente toma un T. Si alguien pasa un valor nulo a su código, su código explotará. Sin embargo, se considera que el código de llamada es el código con errores. OptionSin embargo, es un buen reemplazo para nulo, porque los patrones comunes de manejo de nulos se extraen en funciones de biblioteca como .map(f)o .getOrElse(someDefault).

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.