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 GameUpdate
objeto, generalmente no debería importar diferenciar entre GameUpdate
, PlayerGameUpdate
o 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 GameUpdate
objeto tenga o no un jugador. Si está intentando acceder a la Player
propiedad de a GameUpdate
, está haciendo algo ilógico.
Los lenguajes mecanografiados como C # ni siquiera le permitirán intentar acceder a la Player
propiedad de a GameUpdate
porque GameUpdate
simplemente 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.