Wie soll ich zusätzliche Informationen zu einer Ausnahme bereitstellen?

20

Jedes Mal, wenn ich zusätzliche Informationen zu einer Ausnahme bereitstellen muss, frage ich mich, auf welche Weise dies tatsächlich richtig ist .


Um dieser Frage willen habe ich ein Beispiel geschrieben. Angenommen, es gibt eine Klasse, in der die AbbreviationEigenschaft aktualisiert werden soll . Aus der Sicht von SOLID ist es vielleicht nicht perfekt, aber selbst wenn wir die Worker-Methode über DI mit einem Service übergeben würden, würde die gleiche Situation eintreten - eine Ausnahme tritt auf und es gibt keinen Kontext dazu. Zurück zum Beispiel ...

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

Dann gibt es einige Instanzen der Klasse und eine Schleife, in der die Worker-Methode aufgerufen wird. Es kann das werfen StringTooShortException.

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
}

Die Frage ist: Wie fügt man das Personoder sein Id(oder irgendetwas anderes) hinzu?


Ich kenne die folgenden drei Techniken:


1 - Verwenden Sie die DataEigenschaft

Vorteile:

  • einfach, zusätzliche Informationen einzustellen
  • Es müssen keine weiteren Ausnahmen erstellt werden
  • erfordert keine zusätzlichen try/catch

Nachteile:

  • kann nicht einfach in die integriert werden Message
  • Logger ignorieren dieses Feld und geben es nicht aus
  • benötigt Schlüssel und Casting weil Werte sind object
  • nicht unveränderlich

Beispiel:

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 - Verwenden Sie benutzerdefinierte Eigenschaften

Vorteile:

  • ähnlich wie das DataEigentum, aber stark getippt
  • einfacher zu integrieren in die Message

Nachteile:

  • erfordert benutzerdefinierte Ausnahmen
  • Logger ignoriert sie
  • nicht unveränderlich

Beispiel:

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 - Schließen Sie die Ausnahme mit einer anderen Ausnahme ab

Vorteile:

  • Message kann vorhersehbar formatiert werden
  • Logger geben innere Ausnahmen aus
  • unveränderlich

Nachteile:

  • erfordert zusätzliche try/catch
  • Inkrementelle Verschachtelung
  • erhöht die Tiefe der Ausnahmen

Beispiel:

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...
}

  • Gibt es noch andere Muster?
  • Gibt es bessere Muster?
  • Können Sie für alle Best Practices vorschlagen?
t3chb0t
quelle
Nicht vertraut mit Ausnahmen in C #, aber ich würde normalerweise erwarten, dass die Person-Instanz noch gültig ist, wenn die Ausnahme ausgelöst wird. Hast du das versucht?
John Kouraklis
1
@JohnKouraklis darum geht es in der Frage nicht ;-) Es ist nur ein extrem einfaches Beispiel, um zu demonstrieren, was ich unter zusätzlichen Informationen verstehe. Wenn ich hier ein ganzes Framework posten würde, in dem mehrere Methoden Ausnahmen auslösen können und mehrere Ebenen von Kontextinformationen bereitgestellt werden sollten, würde dies wahrscheinlich niemand lesen, und es fiel mir wirklich schwer, es zu erklären.
t3chb0t
@ JohnKouraklis Ich habe es gerade zu Demonstrationszwecken erfunden.
t3chb0t
@ t3chb0t Ich denke, du hast deine eigene Frage hier beantwortet. Ziehen Sie in Betracht, 1, 2 und 3 in eine Antwort einzufügen und Ihre Frage so anzupassen, dass ich nicht aufgefordert werde, einen Stil auszuwählen, der auf meiner Meinung basiert.
candied_orange
Was stimmt nicht mit benutzerdefinierten Ausnahmen? Bei ordnungsgemäßer Ausführung sind sie Teil Ihrer Domänensprache und tragen dazu bei, eine Abstraktion von den Implementierungsdetails zu erzielen.
RubberDuck

Antworten:

6

Data FTW .

Ihre "Contra":

  • "Kann nicht einfach in die Nachricht integriert werden"

-> Für Ihre Ausnahmetypen, sollte es einfach genug sein , außer Kraft zu setzen , Messageso dass es tut incorporate Data.. obwohl ich dies nur in Betracht ziehen würde , wenn die Datadie Botschaft ist .

  • "Logger ignorieren dieses Feld und geben es nicht aus"

Googling für Nlog als Beispiel Ausbeuten :

Ausnahmelayout-Renderer

(...)

format - Format der Ausgabe. Muss eine durch Kommata getrennte Liste von Ausnahme Eigenschaften sein: Message, Type, ShortType, ToString, Method, StackTraceund Data. Bei diesem Parameter wird die Groß- und Kleinschreibung nicht berücksichtigt. Standard:message

Das scheint also einfach zu konfigurieren zu sein.

  • erfordert Schlüssel und Casting, da Werte Objekt sind

Huh? Legen Sie die Objekte einfach dort ab und stellen Sie sicher, dass sie eine verwendbare ToString()Methode haben.

Außerdem sehe ich keine Probleme mit den Schlüsseln. Verwenden Sie einfach eine milde Einzigartigkeit und Sie sind gut.


Haftungsausschluss: Dies ist, was ich sofort aus der Frage ersehen konnte und worauf ich Datain 15 Minuten gegoogelt habe . Ich dachte, es ist milde hilfreich, also habe ich es als Antwort ausgegeben, aber ich habe Datamich selbst nie benutzt , also kann es gut sein, dass der Fragesteller hier viel mehr darüber weiß als ich.

Martin Ba
quelle
Ich bin zu dem Schluss gekommen, dass es nur zwei nützliche Dinge an einer Ausnahme gibt, ihren Namen und ihre Botschaft. Alles andere ist nur unnützes Rauschen, das ignoriert werden kann und sollte, weil es einfach zu zerbrechlich ist, darauf zu vertrauen.
t3chb0t
2

Warum wirfst du Ausnahmen? Damit sie gefangen und gehandhabt werden.

Wie funktioniert der Fang Code Arbeit aus , wie die Ausnahme behandeln? Verwenden Sie die Eigenschaften, die Sie für das Exception-Objekt definieren.

Verwenden Sie die Message-Eigenschaft niemals, um die Ausnahme zu identifizieren oder "Informationen" bereitzustellen, auf die sich ein potenzieller Handler verlassen sollte. Es ist einfach zu flüchtig und unzuverlässig.

Ich habe die "Data" -Eigenschaft noch nie verwendet, aber sie klingt für mich zu allgemein.

Wenn Sie nicht viele Ausnahmeklassen erstellen , von denen jede einen bestimmten Ausnahmefall identifiziert , woher wissen Sie, wann Sie die Ausnahme abfangen, für die "Daten" stehen? (Siehe vorherigen Kommentar zu "Nachricht").

Phill W.
quelle
1
Ich würde sagen, Dataist nutzlos für die Handhabung, aber wertvoll für die Protokollierung, um die MessageFormatierung der Hölle zu vermeiden .
Martin Ba
-1

Ich mag Ihr drittes Beispiel, aber es gibt eine andere Möglichkeit, wie es codiert werden kann, um die meisten Ihrer "Nachteile" zu beseitigen.

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
quelle