Comment dois-je fournir des informations supplémentaires sur une exception?

20

Chaque fois que je dois fournir des informations supplémentaires sur une exception, je me demande quelle est la bonne façon de procéder.


Pour cette question, j'ai écrit un exemple. Supposons qu'il y ait une classe où nous voulons mettre à jour leAbbreviation propriété. Du point de vue SOLIDE, cela pourrait ne pas être parfait, mais même si nous passions la méthode de travail via DI avec un certain service, la même situation se produirait - une exception se produit et il n'y a pas de contexte. Retour à l'exemple ...

class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Abbreviation { get; set; }
}

Ensuite, il y a quelques instances de la classe et une boucle où la méthode de travail est appelée. Il peut jeter leStringTooShortException .

var persons =
{
    new Person { Id = 1, Name = "Fo" },
    new Person { Id = 2, Name = "Barbaz" },
}

public IEnumerable<Person> GenerateAbbreviation(IEnumerable<Person> persons)
{
    foreach (var person in persons)
    {
        try
        {
            person.Abbreviation = GenerateAbbreviation(person.Name);
        }
        catch(Exception ex)
        {
            // ?
        }
    }
    // throw AggregateException...
}

public IEnumerable<string> GenerateAbbreviation(string value)
{
    if (value.Length < 5)
    {
        throw new StringTooShortException(value);
    }

    // generate abbreviation
}

La question est: comment ajouter le Person ou son Id(ou autre chose)?


Je connais les trois techniques suivantes:


1 - Utiliser la Datapropriété

Avantages:

  • informations supplémentaires faciles à définir
  • ne nécessite pas de créer encore plus d'exceptions
  • ne nécessite pas de supplément try/catch

Les inconvénients:

  • ne peut pas être facilement intégré dans le Message
  • les bûcherons ignorent ce champ et ne le jettent pas
  • nécessite des clés et une conversion car les valeurs sont object
  • non immuable

Exemple:

public IEnumerable<Person> GenerateAbbreviation(IEnumerable<Person> persons)
{
    foreach (var person in persons)
    {
        try
        {
            person.Abbreviation = GenerateAbbreviation(person.Name);
        }
        catch(Exception ex)
        {
            ex.Data["PersonId"] = person.Id;
            // collect ex
        }
    }
    // throw AggregateException...
}

2 - Utiliser des propriétés personnalisées

Avantages:

  • similaire à la Datapropriété mais fortement typé
  • plus facile à intégrer dans le Message

Les inconvénients:

  • nécessite des exceptions personnalisées
  • l'enregistreur les ignorera
  • non immuable

Exemple:

public IEnumerable<Person> GenerateAbbreviation(IEnumerable<Person> persons)
{
    foreach (var person in persons)
    {
        try
        {
            person.Abbreviation = GenerateAbbreviation(person.Name);
        }
        catch(Exception ex)
        {
            // not suitable for this exception because 
            // it doesn't have anything in common with the Person
        }
    }
    // throw AggregateException...
}

3 - Envelopper l'exception avec une autre exception

Avantages:

  • Message peut être formaté de manière prévisible
  • les enregistreurs déchargent les exceptions internes
  • immuable

Les inconvénients:

  • nécessite supplémentaire try/catch
  • nidification increses
  • augmente la profondeur des exonérations

Exemple:

public IEnumerable<Person> GenerateAbbreviation(IEnumerable<Person> persons)
{
    foreach (var person in persons)
    {
        try
        {
            try
            {
                person.Abbreviation = GenerateAbbreviation(person.Name);
            }
            catch(Exception ex)
            {
                throw new InvalidPersonDataException(person.Id, ex);
            }
        }
        catch(Exception ex)
        {
            // collect ex
        }
    }
    // throw AggregateException...
}

  • Existe-t-il d'autres modèles?
  • Y a-t-il de meilleurs modèles?
  • Pouvez-vous suggérer les meilleures pratiques pour chacun d'entre eux?
t3chb0t
la source
Pas familier avec les exceptions en C # mais je m'attendrais normalement à ce que l'instance Person soit toujours valide lorsque l'exception est levée. As-tu essayé ça?
John Kouraklis
1
@JohnKouraklis ce n'est pas de cela qu'il s'agit ;-) C'est juste un exemple extrêmement simple pour démontrer ce que j'entends par informations supplémentaires. Si je postais ici un cadre entier où plusieurs méthodes peuvent lever des exceptions et plusieurs niveaux d'informations de contexte devraient être fournis, personne ne lirait probablement cela et j'ai eu beaucoup de mal à l'expliquer.
t3chb0t
@JohnKouraklis Je viens de l'inventer à des fins de démonstration.
t3chb0t
@ t3chb0t Je pense que vous avez répondu à votre propre question ici. Envisagez de déplacer 1, 2 et 3 dans une réponse et d'ajuster votre question afin qu'il ne me demande pas de choisir un style basé sur mon opinion.
candied_orange
Quel est le problème avec les exceptions personnalisées? Fait correctement, ils font partie de la langue de votre domaine et contribuent à l'abstraction loin des détails de mise en œuvre.
RubberDuck

Réponses:

6

Data FTW .

Votre "contra":

  • "ne peut pas être facilement intégré dans le Message"

-> Pour vos types d'exception, il devrait être assez facile de passer outre Messagepour qu'il ne incorporer Data.. bien que je ne considère cela si l' Dataest le message .

  • "les enregistreurs ignorent ce champ et ne le videront pas"

Googler pour Nlog comme exemple donne :

Rendu de mise en page d'exception

(...)

format - Format de la sortie. Doit être une liste séparée par des virgules des propriétés d'exception: Message, Type, ShortType, ToString, Method, StackTraceet Data. Cette valeur de paramètre est insensible à la casse. Défaut:message

Il semble donc que ce soit facilement configurable.

  • nécessite des clés et une conversion car les valeurs sont des objets

Hein? Videz simplement les objets et assurez-vous qu'ils ont une ToString()méthode utilisable .

De plus, je ne vois aucun problème avec les touches. Utilisez simplement un caractère unique et vous êtes bon.


Avis de non-responsabilité: c'est ce que j'ai pu voir immédiatement à partir de la question et sur quoi j'ai fait une recherche sur Google Dataen 15 minutes. Je pensais que c'était légèrement utile, alors je l'ai présenté comme une réponse, mais je ne Datame suis jamais utilisé , donc il se pourrait bien que le questionneur en sache beaucoup plus à ce sujet que moi.

Martin Ba
la source
J'en suis arrivé à la conclusion qu'il n'y a que deux choses utiles à une exception, son nom et son message. Tout le reste n'est qu'un bruit inutile qui peut et doit être ignoré car il est tout simplement trop fragile.
t3chb0t
2

Pourquoi jetez-vous des exceptions? Pour les faire attraper et manipuler.

Comment le code de capture détermine-t-il comment gérer l'exception? Utilisation des propriétés que vous définissez sur l'objet Exception.

N'utilisez jamais la propriété Message pour identifier l'exception, ni pour fournir des "informations" sur lesquelles tout gestionnaire potentiel doit s'appuyer. C'est tout simplement trop volatil et peu fiable.

Je n'ai jamais utilisé la propriété "Data" auparavant, mais cela me semble trop générique.

À moins que vous ne créiez de nombreuses classes d'exception, chacune identifiant un cas exceptionnel spécifique , comment savoir quand vous interceptez l'exception ce que les «données» représentent? (Voir commentaire précédent sur "Message").

Phill W.
la source
1
Je dirais, Dataest inutile pour la manipulation, mais précieux pour la journalisation pour éviter un Messageformatage infernal.
Martin Ba
-1

J'aime votre troisième exemple, mais il existe un autre moyen de le coder pour éliminer la plupart de vos "con".

public IEnumerable<Person> GenerateAbbreviation(IEnumerable<Person> persons)
{
    var exceptions = new List<InvalidPersonDataException>();

    foreach (var person in persons)
    {
        try
        {
            person.Abbreviation = GenerateAbbreviation(person.Name);
        }
        catch(Exception ex)
        {
            exceptions.Add(new InvalidPersonDataException(person.Id, ex));
        }
    }

    if (exceptions.Any())
    {
        throw new AggregateException(exceptions);
    }
}
krillgar
la source