Donc, je ne sais pas si c'est une bonne ou une mauvaise conception de code, alors j'ai pensé que je ferais mieux de demander.
Je crée souvent des méthodes qui traitent des données impliquant des classes et je fais souvent beaucoup de vérifications dans les méthodes pour m'assurer de ne pas obtenir de références nulles ou d'autres erreurs à l'avance.
Pour un exemple très basique:
// fields and properties
private Entity _someEntity;
public Entity SomeEntity => _someEntity;
public void AssignEntity(Entity entity){
_someEntity = entity;
}
public void SetName(string name)
{
if (_someEntity == null) return; //check to avoid null ref
_someEntity.Name = name;
label.SetText(_someEntity.Name);
}
Donc, comme vous pouvez le voir, je vérifie la valeur null à chaque fois. Mais la méthode ne devrait-elle pas avoir cette vérification?
Par exemple, le code externe doit-il nettoyer les données au préalable afin que les méthodes n'aient pas besoin d'être validées comme ci-dessous:
if(entity != null) // this makes the null checks redundant in the methods
{
Manager.AssignEntity(entity);
Manager.SetName("Test");
}
En résumé, les méthodes doivent-elles "valider les données" et ensuite être traitées sur les données, ou doivent-elles être garanties avant d'appeler la méthode, et si vous ne validez pas avant d'appeler la méthode, elle devrait générer une erreur (ou intercepter le Erreur)?
la source
Réponses:
Le problème de votre exemple de base n’est pas la vérification de la valeur NULL, c’est l’échec silencieux .
Les erreurs de pointeur / référence nulles, le plus souvent, sont des erreurs de programmeur. Il est souvent préférable de traiter les erreurs de programmeur en échouant immédiatement et fort. Vous avez trois moyens généraux de traiter le problème dans cette situation:
Une troisième solution demande plus de travail mais est beaucoup plus robuste:
_someEntity
d'être dans un état non valide. Une façon de le faire est de s'en débarrasserAssignEntity
et de l'exiger comme paramètre d'instanciation. D'autres techniques d'injection de dépendance peuvent également être utiles.Dans certaines situations, il est judicieux de vérifier la validité de tous les arguments de la fonction avant de travailler, et dans d'autres, il est logique d'indiquer à l'appelant qu'il est de sa responsabilité de s'assurer que ses entrées sont valides et non vérifiées. Votre extrémité du spectre dépendra de votre domaine de problèmes. La troisième option présente un avantage important en ce sens que vous pouvez, dans une certaine mesure, demander au compilateur de faire en sorte que l'appelant effectue tout correctement.
Si la troisième option n'est pas une option, alors à mon avis, cela n'a pas vraiment d'importance tant qu'elle n'échoue pas en silence. Si le fait de ne pas vérifier la valeur null provoque l’explosion instantanée du programme, très bien, mais si au lieu de cela il corrompt discrètement les données pour causer des problèmes sur la route, il est préférable de les vérifier et de les gérer immédiatement.
la source
null
est faux ou invalide parce que dans ma bibliothèque hypothétique, j'ai défini les types de données et défini ce qui est valide et ce qui ne l'est pas. Vous appelez cela "forcer des hypothèses" mais devinez quoi: c'est ce que font toutes les bibliothèques de logiciels existantes. Il y a des moments où null est une valeur valide, mais ce n'est pas "toujours".null
correctement" - Ceci est une incompréhension de ce que signifie une manipulation correcte. Pour la plupart des programmeurs Java, cela signifie le lancement d'une exception pour toute entrée non valide. Utiliser@ParametersAreNonnullByDefault
, cela signifie aussi pour tousnull
, à moins que l'argument ne soit marqué comme@Nullable
. Faire autre chose signifie allonger le chemin jusqu'à ce qu'il souffle finalement ailleurs et ainsi allonger le temps perdu à déboguer. En ignorant les problèmes, vous obtiendrez peut-être que cela n’éclate jamais, mais un comportement incorrect est alors plutôt garanti.Exception
sont des débats totalement indépendants. (Je conviens que les deux sont gênants.) Pour cet exemple spécifique,null
cela n'a évidemment aucun sens ici si le but de la classe est d'appeler des méthodes sur l'objet.Puisqu'il
_someEntity
peut être modifié à n'importe quel stade, il est logique de le tester à chaqueSetName
appel. Après tout, cela aurait pu changer depuis le dernier appel de cette méthode. Mais sachez que le code dansSetName
n'est pas thread-safe, vous pouvez donc effectuer cette vérification dans un thread, le_someEntity
paramétrernull
par un autre, puis le code le restitueraNullReferenceException
quand même.Une approche de ce problème consiste donc à être encore plus défensif et à effectuer l’une des actions suivantes:
_someEntity
cette méthode et y faire référence,_someEntity
pour vous assurer qu'il ne change pas pendant l'exécution de la méthode,try/catch
et effectuez la même action sur tousNullReferenceException
les événements de la méthode que lors de la vérification NULL initiale (dans ce cas, unenop
action).Mais ce que vous devriez vraiment faire, c’est d’arrêter et de vous poser la question suivante: devez-vous réellement autoriser
_someEntity
le remplacement? Pourquoi ne pas le définir une fois, via le constructeur. De cette façon, il vous suffit de ne jamais avoir à faire cette vérification nulle une fois:Si possible, c'est l'approche que je vous recommande de prendre dans de telles situations. Si vous ne pouvez pas être attentif à la sécurité des threads lorsque vous choisissez comment gérer les valeurs NULL possibles.
En tant que commentaire bonus, j'estime que votre code est étrange et qu'il pourrait être amélioré. Tous:
peut être remplacé par:
Qui a les mêmes fonctionnalités, sauf pour pouvoir définir le champ via le configurateur de propriétés, plutôt que par une méthode portant un nom différent.
la source
C'est ton choix.
En créant une
public
méthode, vous offrez au public la possibilité de l'appeler. Cela vient toujours avec un contrat implicite sur la manière d'appeler cette méthode et sur ce à quoi s'attendre. Ce contrat peut inclure (ou ne pas inclure) " ouais, euhhm, si vous passeznull
comme valeur de paramètre, cela exploserait droit dans votre visage. ". D'autres parties de ce contrat pourraient être " oh, BTW: chaque fois que deux threads exécutent cette méthode en même temps, un chaton meurt ".Quel type de contrat que vous souhaitez concevoir est à vous. Les choses importantes sont à
créer une API qui est utile et cohérente et
bien documenter, en particulier pour les cas de bord.
Quels sont les cas probables comment votre méthode est appelée?
la source
Les autres réponses sont bonnes. J'aimerais les étendre en faisant quelques commentaires sur les modificateurs d'accessibilité. Tout d’abord, que faire si
null
n’est jamais valable:Les méthodes publiques et protégées sont celles pour lesquelles vous ne contrôlez pas l'appelant. Ils devraient jeter quand passé null. De cette façon, vous entraînez vos appelants à ne jamais vous transmettre un zéro, car ils voudraient que leur programme ne se bloque pas.
internes et privés méthodes sont celles où vous ne contrôlez l'appelant. Ils devraient affirmer quand passé null; ils planteront alors probablement plus tard, mais au moins vous avez d'abord l'assertion et une opportunité de pénétrer dans le débogueur. Encore une fois, vous voulez former vos appelants à vous appeler correctement en leur faisant mal quand ils ne le font pas.
Maintenant, que faites-vous s'il peut être valide pour qu'un null soit transmis? Je voudrais éviter cette situation avec le modèle d'objet null . C’est-à-dire: créez une instance spéciale du type et exigez qu’elle soit utilisée chaque fois qu’un objet non valide est nécessaire . Maintenant, vous êtes de retour dans le monde agréable de jeter / affirmer chaque utilisation de null.
Par exemple, vous ne voulez pas être dans cette situation:
et sur le site de l'appel:
Parce que (1) vous formez vos appelants que la valeur null est une bonne valeur et (2) il n’est pas clair quelle est la sémantique de cette valeur. Faites plutôt ceci:
Et maintenant, le site d'appel ressemble à ceci:
et maintenant le lecteur du code sait ce que cela signifie.
la source
if
s pour les objets null, mais utilisez plutôt un polymorphisme pour les faire se comporter correctement.EmptyQueue
crée un type qui est jeté lorsqu'il est retiré de la file d'attente, etc.Person
classe est immuable et qu'elle ne contient pas de constructeur à zéro argument, comment construiriez-vous votreApproverNotRequired
ou vosApproverUnknown
instances? En d'autres termes, quelles valeurs passeriez-vous au constructeur?Les autres réponses indiquent que votre code peut être nettoyé sans avoir besoin d'un contrôle nul là où vous l'avez, cependant, pour obtenir une réponse générale sur ce qu'un contrôle nul utile peut être pour le code exemple suivant:
Cela vous donne un avantage pour la maintenance. S'il survient un défaut dans votre code où quelque chose passe un objet null à la méthode, vous obtiendrez des informations utiles de la part de la méthode pour savoir quel paramètre était nul. Sans les contrôles, vous obtiendrez une exception NullReferenceException sur la ligne:
Et (étant donné que la propriété Something est un type de valeur), a ou b peuvent être nuls et vous n'aurez aucun moyen de savoir lequel à partir de la trace de la pile. Avec les contrôles, vous saurez exactement quel objet était nul, ce qui peut s'avérer extrêmement utile lorsque vous essayez de résoudre un défaut.
Je voudrais noter que le modèle dans votre code d'origine:
Peut avoir ses utilisations dans certaines circonstances, il peut également (éventuellement plus souvent) vous donner un très bon moyen de masquer les problèmes sous-jacents de votre base de code.
la source
Non pas du tout, mais ça dépend.
Vous ne faites une vérification de référence nulle si vous voulez y réagir.
Si vous effectuez une vérification de référence null et lève une exception vérifiée , vous forcez l'utilisateur de la méthode à réagir et à lui permettre de récupérer. Le programme fonctionne toujours comme prévu.
Si vous ne faites pas de vérification de référence NULL (ou ne lancez pas une exception non contrôlée), une commande non vérifiée pourrait
NullReferenceException
ne pas être gérée par l'utilisateur de la méthode et même arrêter l'application. Cela signifie que la fonction est évidemment complètement incomprise et donc que l'application est défectueuse.la source
Quelque chose d'une extension de la réponse de @ null, mais d'après mon expérience, effectue des vérifications nulles quoi qu'il arrive.
Une bonne programmation défensive consiste à croire que vous codez la routine actuelle de manière isolée, en supposant que tout le reste du programme tente de la bloquer. Lorsque vous écrivez du code essentiel pour une mission où l'échec n'est pas une option, c'est quasiment la seule solution.
Maintenant, la manière dont vous traitez une
null
valeur dépend beaucoup de votre cas d'utilisation.Si
null
est une valeur acceptable, par exemple un des paramètres compris entre la deuxième et la cinquième paramètres de la routine MSVC _splitpath (), le traitement correct de cette opération en mode silencieux est correct.Si cela
null
ne devrait jamais arriver lors de l'appel de la routine en question, c'est-à-dire que, dans le cas du PO, le contrat d'API nécessiteAssignEntity()
d'avoir été appelé avec une valeur valide,Entity
puis émettez une exception ou échouez, en vous assurant que vous ferez beaucoup de bruit dans le processus.Ne vous inquiétez jamais du coût d'un contrôle nul, ils sont vraiment peu coûteux s'ils se produisent, et avec les compilateurs modernes, l'analyse de code statique peut et les éludera complètement si le compilateur décide que cela ne se produira pas.
la source