Je me demande si je devrais me défendre contre la valeur de retour d'un appel de méthode en validant qu'il répond à mes attentes même si je sais que la méthode que j'appelle répondra à ces attentes.
DONNÉ
User getUser(Int id)
{
User temp = new User(id);
temp.setName("John");
return temp;
}
DEVRAIS-JE
void myMethod()
{
User user = getUser(1234);
System.out.println(user.getName());
}
OU
void myMethod()
{
User user = getUser(1234);
// Validating
Preconditions.checkNotNull(user, "User can not be null.");
Preconditions.checkNotNull(user.getName(), "User's name can not be null.");
System.out.println(user.getName());
}
Je pose cette question au niveau conceptuel. Si je connais le fonctionnement interne de la méthode que j'appelle. Soit parce que je l'ai écrit ou que je l'ai inspecté. Et la logique des valeurs possibles qu'elle renvoie répond à mes conditions préalables. Est-il «préférable» ou «plus approprié» d'ignorer la validation, ou devrais-je tout de même me défendre contre de mauvaises valeurs avant de poursuivre avec la méthode que j'implémente actuellement, même si elle doit toujours réussir.
Ma conclusion de toutes les réponses (n'hésitez pas à venir à la vôtre):
Affirmez quand- La méthode a montré un mauvais comportement dans le passé
- La méthode provient d'une source non fiable
- La méthode est utilisée depuis d'autres endroits et n'indique pas explicitement ses post-conditions
- La méthode est proche de la vôtre (voir la réponse choisie pour plus de détails)
- La méthode définit explicitement son contrat avec quelque chose comme un bon document, une sécurité de type, un test unitaire ou une vérification post-condition
- Les performances sont essentielles (dans ce cas, une assertion de mode de débogage pourrait fonctionner comme une approche hybride)
java
validation
null
return-type
defensive-programming
Didier A.
la source
la source
Null
)? C'est probablement aussi problématique si le nom est""
(pas nul mais la chaîne vide) ou"N/A"
. Soit vous pouvez faire confiance au résultat, soit vous devez être paranoïaque de manière appropriée.Réponses:
Cela dépend de la probabilité que getUser et myMethod changent, et plus important encore, de leur probabilité de changer indépendamment l'un de l'autre .
Si vous savez d'une manière ou d'une autre que getUser ne changera jamais, jamais, jamais à l'avenir, alors oui, c'est une perte de temps de le valider, autant que de perdre du temps à valider qui
i
a une valeur de 3 immédiatement après unei = 3;
déclaration. En réalité, vous ne le savez pas. Mais vous savez certaines choses:Ces deux fonctions "cohabitent-elles"? En d'autres termes, ont-ils les mêmes personnes qui les gèrent, font-ils partie du même fichier et sont-ils donc susceptibles de rester «synchronisés» les uns avec les autres? Dans ce cas, il est probablement exagéré d'ajouter des vérifications de validation, car cela signifie simplement plus de code qui doit changer (et potentiellement générer des bogues) chaque fois que les deux fonctions changent ou sont refactorisées en un nombre différent de fonctions.
La fonction getUser fait-elle partie d'une API documentée avec un contrat spécifique, et myMethod est-il simplement un client de ladite API dans une autre base de code? Si c'est le cas, vous pouvez lire cette documentation pour savoir si vous devez valider les valeurs de retour (ou pré-valider les paramètres d'entrée!) Ou s'il est vraiment sûr de suivre aveuglément le chemin heureux. Si la documentation ne le dit pas clairement, demandez au responsable de corriger cela.
Enfin, si cette fonction particulière a soudainement et de manière inattendue changé son comportement dans le passé, d'une manière qui a cassé votre code, vous avez le droit d'être paranoïaque à son sujet. Les bugs ont tendance à se regrouper.
Notez que tout ce qui précède s'applique même si vous êtes l'auteur original des deux fonctions. Nous ne savons pas si ces deux fonctions devraient "vivre ensemble" pour le reste de leur vie, ou si elles se sépareront lentement en modules séparés, ou si vous vous êtes tiré une balle dans le pied avec un bug dans les anciennes versions. versions de getUser. Mais vous pouvez probablement faire une supposition assez décente.
la source
getUser
plus tard, afin qu'il puisse retourner null, la vérification explicite vous donnerait-elle des informations que vous ne pourriez pas obtenir de "NullPointerException" et du numéro de ligne?La réponse d'Ixrec est bonne, mais je vais adopter une approche différente car je pense que cela vaut la peine d'être considéré. Aux fins de cette discussion, je parlerai d'affirmations, puisque c'est le nom sous lequel votre nom
Preconditions.checkNotNull()
est traditionnellement connu.Ce que je voudrais suggérer, c'est que les programmeurs surestiment souvent le degré auquel ils sont certains que quelque chose se comportera d'une certaine manière, et sous-estimeront les conséquences de ne pas se comporter de cette façon. De plus, les programmeurs ne réalisent souvent pas la puissance des assertions comme documentation. Par la suite, d'après mon expérience, les programmeurs affirment les choses beaucoup moins souvent qu'ils ne le devraient.
Ma devise est:
Naturellement, si vous êtes absolument sûr que quelque chose se comporte d'une certaine manière, vous vous abstiendrez d'affirmer qu'il s'est effectivement comporté de cette façon, et c'est pour la plupart raisonnable. Il n'est pas vraiment possible d'écrire un logiciel si vous ne pouvez faire confiance à rien.
Mais il existe même des exceptions à cette règle. Curieusement, pour prendre l'exemple d'Ixrec, il existe un type de situation où il est parfaitement souhaitable de suivre
int i = 3;
avec une assertion qui sei
situe effectivement dans une certaine fourchette. C'est la situation oùi
est susceptible d'être modifié par quelqu'un qui essaie divers scénarios de simulation, et le code qui suit repose suri
une valeur dans une certaine plage, et il n'est pas immédiatement évident en regardant rapidement le code suivant ce la plage acceptable est. Donc, celaint i = 3; assert i >= 0 && i < 5;
peut sembler à première vue insensé, mais si vous y réfléchissez, cela vous indique que vous pouvez jouer aveci
, et cela vous indique également la plage dans laquelle vous devez rester. Une assertion est le meilleur type de documentation, caril est appliqué par la machine , c'est donc une garantie parfaite, et il est refactorisé avec le code , il reste donc toujours pertinent.Votre
getUser(int id)
exemple n'est pas un parent conceptuel très éloigné de l'assign-constant-and-assert-in-range
exemple. Vous voyez, par définition, des bugs se produisent lorsque les choses présentent un comportement différent de celui que nous attendions. Certes, nous ne pouvons pas tout affirmer, donc parfois il y aura un débogage. Mais c'est un fait bien établi dans l'industrie que plus nous détectons rapidement une erreur, moins cela coûte cher. Les questions que vous devez poser ne sont pas seulement de savoir à quel point vous êtes sûr degetUser()
ne jamais retourner null (et de ne jamais renvoyer un utilisateur avec un nom nul), mais aussi, quels types de mauvaises choses se produiront avec le reste du code si c'est le cas dans un jour, retournez quelque chose d'inattendu, et combien c'est facile en regardant rapidement le reste du code pour savoir précisément ce qu'on attend de luigetUser()
.Si le comportement inattendu entraînerait la corruption de votre base de données, vous devriez peut-être affirmer même si vous êtes sûr à 101% que cela ne se produira pas. Si un
null
nom d'utilisateur provoque une erreur cryptique étrange introuvable, des milliers de lignes plus bas et des milliards de cycles d'horloge plus tard, il est peut-être préférable d'échouer le plus tôt possible.Donc, même si je ne suis pas en désaccord avec la réponse d'Ixrec, je vous suggère de considérer sérieusement le fait que les affirmations ne coûtent rien, donc il n'y a vraiment rien à perdre, seulement à gagner, en les utilisant généreusement.
la source
... && sureness < 1)
.Un non catégorique.
Un appelant ne doit jamais vérifier si la fonction qu'il appelle respecte son contrat. La raison est simple: il existe potentiellement de très nombreux appelants et il est peu pratique et sans doute même erroné d'un point de vue conceptuel pour chaque appelant de dupliquer la logique de vérification des résultats d'autres fonctions.
Si des contrôles doivent être effectués, chaque fonction doit vérifier ses propres post-conditions. Les post-conditions vous donnent l'assurance que vous avez correctement mis en œuvre la fonction et les utilisateurs pourront se fier au contrat de votre fonction.
Si une certaine fonction n'est pas censée retourner null, alors cela devrait être documenté et faire partie du contrat de cette fonction. C'est le but de ces contrats: offrir aux utilisateurs des invariants sur lesquels ils peuvent compter pour qu'ils rencontrent moins d'obstacles lors de l'écriture de leurs propres fonctions.
la source
Si null est une valeur de retour valide de la méthode getUser, votre code doit gérer cela avec un If, pas une exception - ce n'est pas un cas exceptionnel.
Si null n'est pas une valeur de retour valide de la méthode getUser, alors il y a un bogue dans getUser - la méthode getUser doit lever une exception ou sinon être corrigée.
Si la méthode getUser fait partie d'une bibliothèque externe ou ne peut pas être modifiée et que vous souhaitez que des exceptions soient levées dans le cas d'un retour nul, encapsulez la classe et la méthode pour effectuer les vérifications et lever l'exception afin que chaque instance de l'appel à getUser est cohérent dans votre code.
la source
Je dirais que la prémisse de votre question est fausse: pourquoi utiliser une référence nulle pour commencer?
Le modèle d'objet null est un moyen de renvoyer des objets qui ne font essentiellement rien: au lieu de renvoyer null pour votre utilisateur, renvoyez un objet qui représente «aucun utilisateur».
Cet utilisateur serait inactif, aurait un nom vide et d'autres valeurs non nulles indiquant «rien ici». Le principal avantage est que vous n'avez pas besoin de salir votre programme pour annuler les vérifications, la journalisation, etc. vous utilisez simplement vos objets.
Pourquoi un algorithme devrait-il se soucier d'une valeur nulle? Les étapes doivent être les mêmes. Il est tout aussi facile et beaucoup plus clair d'avoir un objet nul qui renvoie zéro, une chaîne vide, etc.
Voici un exemple de vérification de code pour null:
Voici un exemple de code utilisant un objet nul:
Qu'est-ce qui est plus clair? Je crois que le deuxième est.
Bien que la question soit java balisée , il convient de noter que le modèle d'objet nul est vraiment utile en C ++ lors de l'utilisation de références. Les références Java ressemblent davantage à des pointeurs C ++ et autorisent null. Les références C ++ ne peuvent pas tenir
nullptr
. Si l'on souhaite avoir quelque chose d'analogue à une référence null en C ++, le modèle d'objet null est le seul moyen que je connaisse pour y parvenir.la source
getCount()
documents garantissent qu'il ne retourne pas maintenant 0, et ne le feront pas à l'avenir, sauf dans les cas où quelqu'un décide d'apporter une modification de rupture et cherche à mettre à jour tout ce qui l'appelle pour y faire face. la nouvelle interface. Voir ce que le code fait actuellement n'est pas utile, mais si vous allez inspecter le code, le code à inspecter est le test unitairegetCount()
. S'ils le testent pour ne pas retourner 0, alors vous savez que tout changement futur pour retourner 0 sera considéré comme un changement de rupture car les tests échoueront.En général, je dirais que cela dépend principalement des trois aspects suivants:
robustesse: la méthode appelante peut-elle supporter la
null
valeur? Si unenull
valeur peut entraîner unRuntimeException
je recommanderais une vérification - à moins que la complexité soit faible et que les méthodes appelées / appelantes soient créées par le même auteur etnull
ne soient pas attendues (par exemple les deuxprivate
dans le même package).responsabilité du code: si le développeur A est responsable de la
getUser()
méthode et que le développeur B l'utilise (par exemple dans le cadre d'une bibliothèque), je recommande fortement de valider sa valeur. Tout simplement parce que le développeur B peut ne pas être au courant d'un changement qui entraîne unenull
valeur de retour potentielle .complexité: plus la complexité globale du programme ou de l'environnement est élevée, plus je recommanderais de valider la valeur de retour. Même si vous êtes sûr que cela ne peut pas être
null
aujourd'hui dans le contexte de la méthode d'appel, il se peut que vous deviez changergetUser()
pour un autre cas d'utilisation. Une fois que certains mois ou années ont disparu et que des milliers de lignes de codes ont été ajoutées, cela peut être un piège.De plus, je recommande de documenter les
null
valeurs de retour potentielles dans un commentaire JavaDoc. En raison de la mise en évidence des descriptions JavaDoc dans la plupart des IDE, cela peut être un avertissement utile pour quiconque l'utilisegetUser()
.la source
Vous n'avez certainement pas à le faire.
Par exemple, si vous savez déjà qu'un retour ne peut jamais être nul - Pourquoi voudriez-vous ajouter un chèque nul? L'ajout d'une vérification nulle ne va rien casser, mais c'est simplement redondant. Et mieux vaut ne pas coder la logique redondante tant que vous pouvez l'éviter.
la source