Ta question.
Mais, mais - de ma naïveté, permettez-moi de crier - parfois une valeur PEUT être significativement absente!
Entièrement à bord avec vous là-bas. Cependant, il y a quelques mises en garde que j'entrerai dans une seconde. Mais d'abord:
Les nuls sont mauvais et doivent être évités autant que possible.
Je pense que c'est une déclaration bien intentionnée mais trop généralisée.
Les nuls ne sont pas mauvais. Ils ne sont pas un contre-modèle. Ils ne doivent pas être évités à tout prix. Cependant, les valeurs nulles sont sujettes à erreur (faute de vérifier la valeur nulle).
Mais je ne comprends pas comment le fait de ne pas vérifier null est en quelque sorte la preuve que null est intrinsèquement mauvais à utiliser.
Si null est mauvais, les index de tableau et les pointeurs C ++ le sont aussi. Ils ne sont pas. Ils sont faciles à tirer avec le pied, mais ils ne sont pas censés être évités à tout prix.
Pour le reste de cette réponse, je vais adapter l'intention de "les nuls sont mauvais" à un plus nuancé "les nuls devraient être traités de manière responsable ".
Votre solution.
Bien que je partage votre opinion sur l'utilisation de null comme règle globale; Je ne suis pas d'accord avec votre utilisation de null dans ce cas particulier.
Pour résumer les commentaires ci-dessous: vous comprenez mal le but de l'héritage et du polymorphisme. Bien que votre argument pour utiliser null soit valide, votre "preuve" supplémentaire de la raison pour laquelle l'autre manière est mauvaise est basée sur une mauvaise utilisation de l'héritage, rendant vos points quelque peu hors de propos pour le problème nul.
La plupart des mises à jour ont naturellement un joueur associé - après tout, il est nécessaire de savoir quel joueur a fait quel mouvement ce tour-ci. Cependant, certaines mises à jour ne peuvent pas être associées de manière significative à un lecteur.
Si ces mises à jour sont significativement différentes (ce qu'elles sont), vous avez besoin de deux types de mise à jour distincts.
Prenons l'exemple des messages. Vous avez des messages d'erreur et des messages de chat IM. Mais bien qu'ils puissent contenir des données très similaires (un message de chaîne et un expéditeur), ils ne se comportent pas de la même manière. Ils doivent être utilisés séparément, en tant que classes différentes.
Ce que vous faites maintenant, c'est différencier efficacement deux types de mise à jour en fonction de la nullité de la propriété Player. Cela revient à décider entre un message instantané et un message d'erreur basé sur l'existence d'un code d'erreur. Cela fonctionne, mais ce n'est pas la meilleure approche.
- Le code JS recevra désormais simplement un objet sans Player en tant que propriété définie, ce qui est le même que s'il était nul puisque les deux cas nécessitent de vérifier si la valeur est présente ou absente;
Votre argument repose sur des erreurs de polymorphisme. Si vous avez une méthode qui renvoie un GameUpdate
objet, elle ne devrait généralement pas se soucier de faire la différence entre GameUpdate
, PlayerGameUpdate
ou NewlyDevelopedGameUpdate
.
Une classe de base doit avoir un contrat pleinement fonctionnel, c'est-à-dire que ses propriétés sont définies sur la classe de base et non sur la classe dérivée (cela étant dit, la valeur de ces propriétés de base peut bien sûr être remplacée dans les classes dérivées).
Cela rend votre argument théorique. Dans la bonne pratique, vous ne devriez jamais vous soucier que votre GameUpdate
objet ait ou non un joueur. Si vous essayez d'accéder à la Player
propriété d'un GameUpdate
, vous faites quelque chose d'illogique.
Les langages typés tels que C # ne vous permettraient même pas d'essayer d'accéder à la Player
propriété d'un GameUpdate
car GameUpdate
il n'a tout simplement pas la propriété. Javascript est considérablement plus laxiste dans son approche (et il ne nécessite pas de précompilation), il ne prend donc pas la peine de vous corriger et le fait exploser au moment de l'exécution.
- Si un autre champ nullable était ajouté à la classe GameUpdate, je devrais être en mesure d'utiliser l'héritage multiple pour continuer avec cette conception - mais MI est mauvais en soi (selon des programmeurs plus expérimentés) et plus important encore, C # ne le fait pas l'avoir donc je ne peux pas l'utiliser.
Vous ne devriez pas créer de classes basées sur l'ajout de propriétés nullables. Vous devez créer des classes basées sur des types de mises à jour de jeu fonctionnellement uniques .
Comment éviter les problèmes avec null en général?
Je pense qu'il est pertinent de préciser comment vous pouvez exprimer de manière significative l'absence de valeur. Il s'agit d'un ensemble de solutions (ou de mauvaises approches) sans ordre particulier.
1. Utilisez null de toute façon.
C'est l'approche la plus simple. Cependant, vous vous inscrivez pour une tonne de vérifications nulles et (en cas d'échec) de dépannage des exceptions de référence null.
2. Utilisez une valeur vide de sens non nulle
Voici un exemple simple indexOf()
:
"abcde".indexOf("b"); // 1
"abcde".indexOf("f"); // -1
Au lieu de null
, vous obtenez -1
. Sur le plan technique, il s'agit d'une valeur entière valide. Cependant, une valeur négative est une réponse absurde car les indices devraient être des entiers positifs.
Logiquement, cela ne peut être utilisé que dans les cas où le type de retour autorise plus de valeurs possibles que celles qui peuvent être renvoyées de manière significative (indexOf = entiers positifs; int = nombres négatifs et positifs)
Pour les types de référence, vous pouvez toujours appliquer ce principe. Par exemple, si vous avez une entité avec un ID entier, vous pouvez renvoyer un objet factice avec son ID défini sur -1, par opposition à null.
Vous devez simplement définir un "état factice" par lequel vous pouvez mesurer si un objet est réellement utilisable ou non.
Notez que vous ne devez pas vous fier à des valeurs de chaîne prédéterminées si vous ne contrôlez pas la valeur de chaîne. Vous pouvez envisager de définir le nom d'un utilisateur sur "null" lorsque vous souhaitez renvoyer un objet vide, mais vous rencontrez des problèmes lorsque Christopher Null s'inscrit à votre service .
3. Lancez une exception à la place.
Non, juste non. Les exceptions ne doivent pas être gérées pour le flux logique.
Cela étant dit, dans certains cas, vous ne souhaitez pas masquer l'exception (nullreference), mais plutôt afficher une exception appropriée. Par exemple:
var existingPerson = personRepository.GetById(123);
Cela peut lancer un PersonNotFoundException
(ou similaire) quand il n'y a personne avec ID 123.
De manière assez évidente, cela n'est acceptable que dans les cas où le fait de ne pas trouver un article rend impossible tout autre traitement. S'il est impossible de trouver un élément et que l'application peut continuer, vous ne devez JAMAIS lever une exception . Je ne peux insister assez sur ce point.