web-dev-qa-db-fra.com

Meilleures pratiques: lever des exceptions à partir des propriétés

Quand est-il approprié de lever une exception depuis un getter ou un setter de propriété? Quand n'est-ce pas approprié? Pourquoi? Des liens vers des documents externes sur le sujet seraient utiles ... Google s'est révélé étonnamment peu.

102
Jon Seigel

Microsoft a ses recommandations sur la façon de concevoir des propriétés à http://msdn.Microsoft.com/en-us/library/ms229006.aspx

Essentiellement, ils recommandent que les accesseurs de propriété soient des accesseurs légers qui sont toujours sûrs d'appeler. Ils recommandent de reconcevoir les getters comme méthodes si les exceptions sont quelque chose que vous devez lever. Pour les setters, ils indiquent que les exceptions sont une stratégie de gestion des erreurs appropriée et acceptable.

Pour les indexeurs, Microsoft indique qu'il est acceptable pour les getters et les setters de lever des exceptions. Et en fait, de nombreux indexeurs de la bibliothèque .NET le font. L'exception la plus courante étant ArgumentOutOfRangeException.

Il existe de très bonnes raisons pour lesquelles vous ne voulez pas lever d'exceptions dans les accesseurs de propriété:

  • Étant donné que les propriétés "semblent" être des champs, il n'est pas toujours évident qu'elles peuvent lever une exception (par conception); tandis qu'avec les méthodes, les programmeurs sont formés pour s'attendre et rechercher si les exceptions sont une conséquence attendue de l'appel de la méthode.
  • Les accesseurs sont utilisés par de nombreuses infrastructures .NET, comme les sérialiseurs et la liaison de données (dans WinForms et WPF par exemple) - le traitement des exceptions dans de tels contextes peut rapidement devenir problématique.
  • Les accesseurs de propriétés sont automatiquement évalués par les débogueurs lorsque vous regardez ou inspectez un objet. Une exception ici peut être source de confusion et ralentir vos efforts de débogage. Il est également indésirable d'effectuer d'autres opérations coûteuses dans les propriétés (comme l'accès à une base de données) pour les mêmes raisons.
  • Les propriétés sont souvent utilisées dans une convention de chaînage: obj.PropA.AnotherProp.YetAnother - avec ce type de syntaxe, il devient problématique de décider où injecter les instructions catch d'exception.

En remarque, il faut savoir que ce n'est pas parce qu'une propriété n'est pas conçue pour lever une exception, cela ne signifie pas qu'elle ne le sera pas; il pourrait facilement appeler du code qui le fait. Même le simple fait d'allouer un nouvel objet (comme une chaîne) pourrait entraîner des exceptions. Vous devez toujours écrire votre code de manière défensive et vous attendre à des exceptions de tout ce que vous invoquez.

123
LBushkin

Il n'y a rien de mal à lever les exceptions des colons. Après tout, quelle meilleure façon d'indiquer que la valeur n'est pas valide pour une propriété donnée?

Pour les getters, il est généralement mal vu, et cela peut être expliqué assez facilement: un getter de propriété, en général, rapporte l'état actuel d'un objet; ainsi, le seul cas où il est raisonnable pour un getter de lancer est lorsque l'état est invalide. Mais il est également généralement considéré comme une bonne idée de concevoir vos classes de telle sorte qu'il ne soit tout simplement pas possible d'obtenir un objet non valide au départ, ou de le mettre dans un état non valide par des moyens normaux (c'est-à-dire, toujours assurer une initialisation complète dans les constructeurs, et essayez de rendre les méthodes sans danger pour la validité des états et les invariants de classe). Tant que vous respectez cette règle, vos accesseurs ne devraient jamais se retrouver dans une situation où ils doivent signaler un état non valide, et donc ne jamais jeter.

Il y a une exception que je connais, et elle est en fait assez importante: tout objet implémentant IDisposable. Dispose est spécifiquement conçu comme un moyen de mettre un objet dans un état invalide, et il y a même une classe d'exception spéciale, ObjectDisposedException, à utiliser dans ce cas. Il est parfaitement normal de lancer ObjectDisposedException à partir de n'importe quel membre de classe, y compris les accesseurs de propriété (et à l'exclusion de Dispose lui-même), après que l'objet a été supprimé.

34
Pavel Minaev

Il n'est presque jamais approprié sur un getter, et parfois approprié sur un setter.

La meilleure ressource pour ce genre de questions est "Framework Design Guidelines" de Cwalina et Abrams; il est disponible sous forme de livre relié, et de grandes parties sont également disponibles en ligne.

De la section 5.2: Conception de la propriété

ÉVITEZ de lever des exceptions des accesseurs de propriété. Les accesseurs de propriété doivent être des opérations simples et ne doivent pas avoir de conditions préalables. Si un getter peut lever une exception, elle devrait probablement être repensée pour être une méthode. Notez que cette règle ne s'applique pas aux indexeurs, où nous nous attendons à des exceptions à la suite de la validation des arguments.

Notez que cette directive s'applique uniquement aux accesseurs de propriété. Il est correct de lever une exception dans un configurateur de propriétés.

24
Eric Lippert

Une bonne approche des exceptions consiste à les utiliser pour documenter le code pour vous et les autres développeurs comme suit:

Les exceptions devraient être pour les états de programme exceptionnels. Cela signifie que c'est bien de les écrire où vous voulez!

Une raison pour laquelle vous voudrez peut-être les mettre dans des getters est de documenter l'API d'une classe - si le logiciel lève une exception dès qu'un programmeur essaie de l'utiliser mal alors il ne l'utilisera pas mal! Par exemple, si vous avez une validation pendant un processus de lecture de données, il peut ne pas être logique de pouvoir continuer et accéder aux résultats du processus s'il y a eu des erreurs fatales dans les données. Dans ce cas, vous souhaiterez peut-être obtenir le lancer de sortie s'il y a des erreurs pour vous assurer qu'un autre programmeur vérifie cette condition.

Ils sont un moyen de documenter les hypothèses et les limites d'un sous-système/méthode/quoi que ce soit. Dans le cas général, il ne faut pas les attraper! C'est aussi parce qu'ils ne sont jamais levés si le système fonctionne ensemble de la manière attendue: si une exception se produit, cela montre que les hypothèses d'un morceau de code ne sont pas respectées - par exemple, il n'interagit pas avec le monde qui l'entoure de la manière il était initialement prévu. Si vous détectez une exception qui a été écrite à cet effet, cela signifie probablement que le système est entré dans un état imprévisible/incohérent - cela peut finalement conduire à un plantage ou à une corruption des données ou similaire, ce qui est probablement beaucoup plus difficile à détecter/déboguer.

Les messages d'exception sont un moyen très grossier de signaler des erreurs - ils ne peuvent pas être collectés en masse et ne contiennent vraiment qu'une chaîne. Cela les rend impropres à signaler des problèmes dans les données d'entrée. En fonctionnement normal, le système lui-même ne doit pas entrer dans un état d'erreur. En conséquence, les messages qu'ils contiennent doivent être conçus pour les programmeurs et non pour les utilisateurs - les éléments incorrects dans les données d'entrée peuvent être découverts et relayés aux utilisateurs dans des formats (personnalisés) plus adaptés.

L'exception (haha!) À cette règle est des choses comme IO où les exceptions ne sont pas sous votre contrôle et ne peuvent pas être vérifiées à l'avance.

2
JonnyRaa

Tout cela est documenté dans MSDN (comme lié à dans d'autres réponses) mais voici une règle générale ...

Dans le setter, si votre propriété doit être validée au-delà du type. Par exemple, une propriété appelée PhoneNumber devrait probablement avoir une validation d'expression régulière et devrait générer une erreur si le format n'est pas valide.

Pour les getters, éventuellement lorsque la valeur est nulle, mais c'est probablement quelque chose que vous voudrez gérer sur le code appelant (selon les directives de conception).

1
David

MSDN: capture et levée des types d'exception standard

http://msdn.Microsoft.com/en-us/library/ms229007.aspx

0
smack0007

J'avais ce code où je ne savais pas quelle exception lancer.

public Person
{
    public string Name { get; set; }
    public boolean HasPets { get; set; }
}

public void Foo(Person person)
{
    if (person.Name == null) {
        throw new Exception("Name of person is null.");
        // I was unsure of which exception to throw here.
    }

    Console.WriteLine("Name is: " + person.Name);
}

J'ai empêché le modèle d'avoir la propriété nulle en premier lieu en le forçant comme argument dans le constructeur.

public Person
{
    public Person(string name)
    {
        if (name == null) {
            throw new ArgumentNullException(nameof(name));
        }
        Name = name;
    }

    public string Name { get; private set; }
    public boolean HasPets { get; set; }
}

public void Foo(Person person)
{
    Console.WriteLine("Name is: " + person.Name);
}
0
Fred

Il s'agit d'une question très complexe et la réponse dépend de la façon dont votre objet est utilisé. En règle générale, les getters et setters de propriété qui sont "à liaison tardive" ne devraient pas lever d'exceptions, tandis que les propriétés avec exclusivement "liaison anticipée" devraient lever des exceptions lorsque le besoin s'en fait sentir. BTW, l'outil d'analyse de code de Microsoft définit trop étroitement l'utilisation des propriétés à mon avis.

"liaison tardive" signifie que les propriétés sont trouvées par réflexion. Par exemple, l'attribut Serializeable "est utilisé pour sérialiser/désérialiser un objet via ses propriétés. Le fait de lever une exception dans ce type de situation casse les choses de manière catastrophique et n'est pas un bon moyen d'utiliser des exceptions pour rendre le code plus robuste.

"liaison anticipée" signifie qu'une utilisation de propriété est liée dans le code par le compilateur. Par exemple, lorsqu'un code que vous écrivez fait référence à un getter de propriété. Dans ce cas, il est acceptable de lever des exceptions lorsqu'elles ont un sens.

Un objet avec des attributs internes a un état déterminé par les valeurs de ces attributs. Les propriétés exprimant des attributs conscients et sensibles à l'état interne de l'objet ne doivent pas être utilisées pour une liaison tardive. Par exemple, supposons que vous ayez un objet qui doit être ouvert, consulté, puis fermé. Dans ce cas, l'accès aux propriétés sans appeler open en premier devrait entraîner une exception. Supposons, dans ce cas, que nous ne lançons pas d'exception et que nous permettions au code d'accéder à une valeur sans lever d'exception? Le code semblera heureux même s'il a obtenu une valeur d'un getter qui n'est pas logique. Maintenant, nous avons mis le code qui a appelé le getter dans une mauvaise situation car il doit savoir comment vérifier la valeur pour voir si elle n'a pas de sens. Cela signifie que le code doit faire des hypothèses sur la valeur qu'il a obtenue du getter de propriété afin de le valider. C'est ainsi que le mauvais code est écrit.

0
Jack D Menendez