J'utilise des exceptions pour détecter les problèmes tôt. Par exemple:
public int getAverageAge(Person p1, Person p2){
if(p1 == null || p2 == null)
throw new IllegalArgumentException("One or more of input persons is null").
return (p1.getAge() + p2.getAge()) / 2;
}
Mon programme ne doit jamais passer null
dans cette fonction. Je n'en ai jamais l'intention. Cependant, comme nous le savons tous, des choses imprévues se produisent dans la programmation.
Lancer une exception si ce problème se produit, me permet de le repérer et de le corriger, avant qu'il ne cause plus de problèmes à d'autres endroits du programme. L'exception arrête le programme et me dit "de mauvaises choses sont arrivées ici, corrigez-le". Au lieu de cela, le null
déplacement du programme provoque des problèmes ailleurs.
Maintenant, vous avez raison, dans ce cas, null
cela provoquerait tout de suite un problème NullPointerException
, donc ce n'est peut-être pas le meilleur exemple.
Mais considérons une méthode comme celle-ci par exemple:
public void registerPerson(Person person){
persons.add(person);
notifyRegisterObservers(person); // sends the person object to all kinds of objects.
}
Dans ce cas, un null
paramètre as serait transmis au programme et pourrait provoquer des erreurs beaucoup plus tard, qui seront difficiles à retracer jusqu'à leur origine.
Changer la fonction comme ceci:
public void registerPerson(Person person){
if(person == null) throw new IllegalArgumentException("Input person is null.");
persons.add(person);
notifyRegisterObservers(person); // sends the person object to all kinds of objects.
}
Me permet de repérer le problème bien avant qu'il ne provoque des erreurs étranges dans d'autres endroits.
De plus, une null
référence en tant que paramètre n'est qu'un exemple. Il peut y avoir plusieurs types de problèmes, des arguments invalides à autre chose. Il vaut toujours mieux les repérer tôt.
Ma question est donc simplement: est-ce une bonne pratique? Mon utilisation des exceptions comme outils de prévention des problèmes est-elle bonne? Est-ce une application légitime des exceptions ou est-ce problématique?
Réponses:
Oui, "échouer tôt" est un très bon principe, et c'est simplement une façon possible de le mettre en œuvre. Et dans les méthodes qui doivent renvoyer une valeur spécifique, il n'y a pas grand-chose d'autre pouvez faire échouer délibérément - il est soit lancer des exceptions, ou le déclenchement d' assertions. Les exceptions sont censées signaler des conditions «exceptionnelles», et la détection d'une erreur de programmation est certainement exceptionnelle.
la source
Oui, lever des exceptions est une bonne idée. Jetez-les tôt, jetez-les souvent, jetez-les avec impatience.
Je sais qu'il y a un débat "exceptions vs assertions", avec certains types de comportements exceptionnels (en particulier, ceux qui reflètent des erreurs de programmation) gérés par des assertions qui peuvent être "compilées" pour l'exécution plutôt que de déboguer / tester des builds. Mais la quantité de performances consommée dans quelques vérifications de correction supplémentaires est minime sur le matériel moderne, et tout coût supplémentaire est largement compensé par la valeur d'avoir des résultats corrects et non corrompus. Je n'ai jamais rencontré de base de code d'application pour laquelle je souhaiterais que la plupart des vérifications soient supprimées lors de l'exécution.
Je suis tenté de dire que je ne voudrais pas beaucoup de vérifications et de conditions supplémentaires dans les boucles serrées du code numériquement intensif ... mais c'est en fait là que beaucoup d'erreurs numériques sont générées, et si elles ne sont pas détectées, elles se propageront vers l'extérieur effectuer tous les résultats. Même là-bas, les contrôles valent la peine. En fait, certains des meilleurs algorithmes numériques les plus efficaces sont basés sur l'évaluation des erreurs.
Un dernier endroit pour être très conscient du code supplémentaire est le code très sensible à la latence, où des conditions supplémentaires peuvent provoquer des blocages de pipeline. Ainsi, au milieu du système d'exploitation, du SGBD et d'autres noyaux de middleware, et de la communication de bas niveau / gestion des protocoles. Mais là encore, ce sont certains des endroits où les erreurs sont les plus susceptibles d'être observées, et leurs effets (sécurité, exactitude et intégrité des données) sont les plus dommageables.
Une amélioration que j'ai trouvée est de ne pas lever uniquement les exceptions de niveau de base.
IllegalArgumentException
c'est bien, mais ça peut venir de pratiquement n'importe où. Il ne faut pas grand-chose dans la plupart des langues pour ajouter des exceptions personnalisées. Pour votre module de gestion des personnes, dites:Ensuite, quand quelqu'un voit un
PersonArgumentException
, c'est clair d'où ça vient. Il y a un équilibre entre le nombre d'exceptions personnalisées que vous souhaitez ajouter, car vous ne voulez pas multiplier les entités inutilement (Rasoir d'Occam). Souvent, quelques exceptions personnalisées suffisent pour signaler "ce module n'obtient pas les bonnes données!" ou "ce module ne peut pas faire ce qu'il est censé faire!" d'une manière qui est spécifique et adaptée, mais pas trop précise que vous devez réimplémenter la hiérarchie d'exceptions entière. J'arrive souvent à ce petit ensemble d'exceptions personnalisées en commençant par les exceptions de stock, puis en analysant le code et en réalisant que "ces N endroits soulèvent des exceptions de stock, mais ils se résument à l'idée de niveau supérieur qu'ils n'obtiennent pas les données ils ont besoin; laissez 'la source
I've never actually met an application codebase for which I'd want (most) checks removed at runtime.
Ensuite, vous n'avez pas fait de code critique pour les performances. Je travaille actuellement sur quelque chose qui fait 37 millions d'opérations par seconde avec des assertions compilées sur et 42 millions sans elles. Les assertions ne valident pas l'entrée extérieure, elles sont là pour s'assurer que le code est correct. Mes clients sont plus qu'heureux de profiter de l'augmentation de 13% une fois que je suis convaincu que mes affaires ne sont pas cassées.PersonArgumentException
n'est pas aussi clair queIllegalArgumentException
. Ce dernier est universellement connu pour être jeté lorsqu'un argument illégal est passé. Je m'attendrais en fait à ce que le premier soit jeté si aPerson
était dans un état invalide pour un appel (similaire àInvalidOperationException
C #).Échouer dès que possible est formidable lors du débogage d'une application. Je me souviens d'une erreur de segmentation particulière dans un programme C ++ hérité: l'endroit où le bogue a été détecté n'avait rien à voir avec l'endroit où il a été introduit (le pointeur nul a été heureusement déplacé d'un endroit à un autre en mémoire avant de finalement causer un problème ). Les traces de pile ne peuvent pas vous aider dans ces cas.
Alors oui, la programmation défensive est une approche vraiment efficace pour détecter et corriger rapidement les bugs. En revanche, il peut être exagéré, notamment avec des références nulles.
Dans votre cas spécifique, par exemple: si l'une des références est nulle, le
NullReferenceException
sera jeté à la prochaine instruction, lorsque vous tenterez d'obtenir l'âge d'une personne. Vous n'avez pas vraiment besoin de vérifier les choses par vous-même ici: laissez le système sous-jacent intercepter ces erreurs et lever des exceptions, c'est pourquoi elles existent .Pour un exemple plus réaliste, vous pouvez utiliser des
assert
instructions qui:Sont plus courts à écrire et à lire:
Sont spécialement conçus pour votre approche. Dans un monde où vous avez à la fois des assertions et des exceptions, vous pouvez les distinguer comme suit:
Ainsi, exposer vos hypothèses sur les entrées et / ou l'état de votre application avec des assertions permet au développeur suivant de comprendre un peu plus le but de votre code.
Un analyseur statique (par exemple le compilateur) pourrait aussi être plus heureux.
Enfin, les assertions peuvent être supprimées de l'application déployée à l'aide d'un seul commutateur. Mais d'une manière générale, ne vous attendez pas à améliorer l'efficacité avec cela: les vérifications d'assertions à l'exécution sont négligeables.
la source
Autant que je sache, différents programmeurs préfèrent une solution ou l'autre.
La première solution est généralement préférée car elle est plus concise, en particulier, vous n'avez pas à vérifier encore et encore la même condition dans différentes fonctions.
Je trouve la deuxième solution, par exemple
plus solide, car
registerPerson()
est appelé, et non lorsqu'une exception de pointeur nul est levée quelque part dans la pile des appels. Le débogage devient beaucoup plus facile: nous savons tous dans quelle mesure une valeur non valide peut parcourir le code avant de se manifester comme un bogue.registerPerson()
ne fait aucune hypothèse sur les autres fonctions qui finiront par utiliser l'person
argument et comment elles vont l'utiliser: la décision quinull
est une erreur est prise et implémentée localement.Donc, surtout si le code est assez complexe, j'ai tendance à préférer cette seconde approche.
la source
En général, oui, c'est une bonne idée d'échouer tôt. Cependant, dans votre exemple spécifique, l'explicite
IllegalArgumentException
n'apporte pas d'amélioration significative par rapport à aNullReferenceException
- car les deux objets opérés sont déjà fournis comme arguments à la fonction.Mais regardons un exemple légèrement différent.
S'il n'y avait pas d'argument archivant le constructeur, vous obtiendrez un
NullReferenceException
lors de l'appelCalculate
.Mais le morceau de code cassé n'était ni la
Calculate
fonction, ni le consommateur de laCalculate
fonction. Le morceau de code cassé était le code qui tente de construire lePersonCalculator
avec un nullPerson
- c'est donc là que nous voulons que l'exception se produise.Si nous supprimons cette vérification d'argument explicite, vous devrez comprendre pourquoi un
NullReferenceException
s'est produit lorsque le aCalculate
été appelé. Etnull
trouver pourquoi l'objet a été construit avec une personne peut devenir délicat, surtout si le code qui construit la calculatrice n'est pas proche du code qui appelle réellement laCalculate
fonction.la source
Pas dans les exemples que vous donnez.
Comme vous le dites, lancer explicitement ne vous rapporte pas grand-chose lorsque vous allez obtenir une exception peu de temps après. Beaucoup diront qu'il est préférable d'avoir l'exception explicite avec un bon message, même si je ne suis pas d'accord. Dans les scénarios pré-publiés, la trace de la pile est suffisamment bonne. Dans les scénarios post-version, le site d'appel peut souvent fournir de meilleurs messages qu'à l'intérieur de la fonction.
Le deuxième formulaire fournit trop d'informations à la fonction. Cette fonction ne doit pas nécessairement savoir que les autres fonctions lanceront une entrée nulle. Même s'ils lancent maintenant une entrée nulle , il devient très gênant de refactoriser si cela cesse d'être le cas puisque la vérification nulle est répartie dans tout le code.
Mais en général, vous devriez lancer tôt une fois que vous avez déterminé que quelque chose s'est mal passé (tout en respectant SEC). Ce ne sont peut-être pas de bons exemples de cela.
la source
Dans votre exemple de fonction, je préférerais que vous ne fassiez aucun contrôle et que vous
NullReferenceException
cela se produire.D'une part, cela n'a aucun sens de passer un null de toute façon, donc je vais résoudre le problème immédiatement en fonction du lancement d'un
NullReferenceException
.Deuxièmement, si chaque fonction génère des exceptions légèrement différentes en fonction du type d'entrées manifestement erronées fournies, alors très vite, vous avez des fonctions qui peuvent générer 18 types d'exceptions différents, et très vite vous vous dites que c'est trop beaucoup de travail à faire, et juste supprimer toutes les exceptions de toute façon.
Il n'y a rien que vous puissiez vraiment faire pour résoudre la situation d'une erreur de conception dans votre fonction, alors laissez simplement l'échec se produire sans le changer.
la source
RuntimeException
une hypothèse non valable.