J'ai entendu dire qu'il est recommandé de valider les arguments des méthodes publiques:
- Doit-on vérifier null s'il n'attend pas null?
- Une méthode doit-elle valider ses paramètres?
- MSDN - CA1062: Validez les arguments des méthodes publiques (j'ai un arrière-plan .NET mais la question n'est pas spécifique à C #)
La motivation est compréhensible. Si un module est utilisé de manière incorrecte, nous voulons lever immédiatement une exception au lieu de tout comportement imprévisible.
Ce qui me dérange, c'est que les mauvais arguments ne sont pas la seule erreur qui peut être faite lors de l'utilisation d'un module. Voici quelques scénarios d'erreur dans lesquels nous devons ajouter une logique de vérification si nous suivons les recommandations et ne voulons pas d'escalade d'erreur:
- Appel entrant - arguments inattendus
- Appel entrant - le module est dans un mauvais état
- Appel externe - résultats inattendus renvoyés
- Appel externe - effets secondaires inattendus (double entrée vers un module appelant, rupture d'autres états de dépendances)
J'ai essayé de prendre en compte toutes ces conditions et d'écrire un module simple avec une méthode (désolé, les gars non-C #):
public sealed class Room
{
private readonly IDoorFactory _doorFactory;
private bool _entered;
private IDoor _door;
public Room(IDoorFactory doorFactory)
{
if (doorFactory == null)
throw new ArgumentNullException("doorFactory");
_doorFactory = doorFactory;
}
public void Open()
{
if (_door != null)
throw new InvalidOperationException("Room is already opened");
if (_entered)
throw new InvalidOperationException("Double entry is not allowed");
_entered = true;
_door = _doorFactory.Create();
if (_door == null)
throw new IncompatibleDependencyException("doorFactory");
_door.Open();
_entered = false;
}
}
Maintenant c'est sûr =)
C'est assez flippant. Mais imaginez à quel point il peut être effrayant dans un vrai module avec des dizaines de méthodes, un état complexe et beaucoup d'appels externes (salut, amateurs d'injection de dépendances!). Notez que si vous appelez un module dont le comportement peut être remplacé (classe non scellée en C #), vous effectuez un appel externe et les conséquences ne sont pas prévisibles au niveau de la portée de l'appelant.
En résumé, quelle est la bonne façon et pourquoi? Si vous pouvez choisir parmi les options ci-dessous, répondez aux questions supplémentaires, s'il vous plaît.
Vérifiez l'utilisation complète du module. Avons-nous besoin de tests unitaires? Existe-t-il des exemples d'un tel code? L'injection de dépendances doit-elle être limitée dans son utilisation (car elle entraînera davantage de logique de vérification)? N'est-il pas pratique de déplacer ces vérifications au moment du débogage (ne pas inclure dans la version)?
Vérifiez uniquement les arguments. D'après mon expérience, la vérification des arguments - en particulier la vérification des valeurs nulles - est la vérification la moins efficace, car l'erreur d'argument conduit rarement à des erreurs complexes et à des escalades d'erreurs. La plupart du temps, vous obtenez un NullReferenceException
à la ligne suivante. Alors pourquoi les vérifications d'arguments sont-elles si spéciales?
Ne vérifiez pas l'utilisation du module. C'est une opinion assez impopulaire, pouvez-vous expliquer pourquoi?
Réponses:
TL; DR: valider le changement d'état, s'appuyer sur [la validité de] l'état actuel.
Ci-dessous, je ne considère que les vérifications activées pour les versions. Les assertions actives de débogage uniquement sont une forme de documentation, utile à sa manière et hors de portée pour cette question.
Tenez compte des principes suivants:
Définitions
État mutable
Problème
Dans les langues impératives, le symptôme d'erreur et sa cause peuvent être séparés par des heures de levage lourd. La corruption d'État peut se cacher et muter pour aboutir à un échec inexplicable, car l'inspection de l'état actuel ne peut pas révéler le processus complet de corruption et, par conséquent, l'origine de l'erreur.
Solution
Chaque changement d'état doit être soigneusement conçu et vérifié. Une façon de gérer l'état mutable est de le maintenir à son minimum. Ceci est réalisé par:
Lorsque vous étendez l'état d'un composant, envisagez de le faire en laissant le compilateur imposer l'immuabilité des nouvelles données. De plus, appliquez toutes les contraintes d'exécution sensibles, en limitant les états résultants potentiels à un ensemble bien défini le plus petit possible.
Exemple
Répétition et cohésion de responsabilité
Problème
La vérification des conditions préalables et des post-conditions des opérations entraîne la duplication du code de vérification à la fois dans le client et dans le composant. La validation de l'appel de composants oblige souvent le client à assumer certaines des responsabilités des composants.
Solution
Comptez sur le composant pour effectuer la vérification de l'état lorsque cela est possible. Les composants doivent fournir une API qui ne nécessite pas de vérification particulière de l'utilisation (vérification des arguments ou application de la séquence d'opérations, par exemple) pour garder l'état du composant bien défini. Ils obligent à vérifier les arguments d'invocation d'API selon les besoins, à signaler les échecs par les moyens nécessaires et à lutter contre leur corruption d'état.
Les clients doivent s'appuyer sur des composants pour vérifier l'utilisation de leur API. Non seulement la répétition est évitée, mais le client ne dépend plus des détails d'implémentation supplémentaires du composant. Considérez le cadre comme un composant. N'écrivez un code de vérification personnalisé que lorsque les invariants des composants ne sont pas assez stricts ou pour encapsuler l'exception des composants comme détail d'implémentation.
Si une opération ne change pas d'état et n'est pas couverte par des vérifications de changement d'état, vérifiez chaque argument au niveau le plus profond possible.
Exemple
Réponse
Lorsque les principes décrits sont appliqués à l'exemple en question, nous obtenons:
Sommaire
L'état du client se compose de valeurs de champs propres et de parties de l'état du composant qui ne sont pas couvertes par ses propres invariants. La vérification ne doit être effectuée qu'avant le changement d'état réel d'un client.
la source
Une classe est responsable de son propre état. Validez donc dans la mesure où il garde ou met les choses dans un état acceptable.
Non, ne lancez pas d'exception, mais fournissez plutôt un comportement prévisible. Le corollaire de la responsabilité de l'État est de rendre la classe / application aussi tolérante que possible. Par exemple, passer
null
àaCollection.Add()
? N'ajoutez pas et continuez. Vous obteneznull
des informations pour créer un objet? Créez un objet nul ou un objet par défaut. Ci-dessus, l'door
est déjàopen
? Alors quoi, continuez.DoorFactory
est nul? Créez-en un nouveau. Quand je crée un,enum
j'ai toujours unUndefined
membre. J'utilise libéralementDictionary
s etenums
pour définir les choses explicitement; et cela va un long chemin vers la fourniture d'un comportement prévisible.Oui, même si je marche dans l'ombre de la vallée des paramètres, je ne crains aucun argument. Pour ce qui précède, j'utilise autant que possible les paramètres par défaut et facultatifs.
Tout ce qui précède permet au traitement interne de continuer. Dans une application particulière, j'ai des dizaines de méthodes dans plusieurs classes avec un seul endroit où une exception est levée. Même alors, ce n'est pas à cause d'arguments nuls ou que je n'ai pas pu continuer le traitement c'est parce que le code a fini par créer un objet "non fonctionnel" / "null".
Éditer
citant mon commentaire dans son intégralité. Je pense que le design ne doit pas simplement "abandonner" lors de la rencontre avec "null". Surtout en utilisant un objet composite.
terminer l'édition
la source
encapsulation
&single responsibility
. Il n'y a pratiquement aucunenull
vérification après la première couche interagissant avec le client. Le code est <strike> tolérant </strike> robuste. Les classes sont conçues avec des états par défaut et fonctionnent donc sans être écrites comme si le code interagissant était indésirable et indésirable. Un parent composite n'a pas besoin de descendre dans les couches enfants pour évaluer la validité (et implicitement, vérifiernull
tous les coins et recoins). Le parent sait ce que signifie l'état par défaut d'un enfant