10 Stimmen

Auslösen einer Ausnahme des richtigen Typs

In meinem Code habe ich oft Situationen wie diese:

public void MyMethod(string data)
{
    AnotherClass objectOfAnotherClass = GetObject(data);
    if (objectOfAnotherClass == null)
        throw new WhatExceptionType1("objectOfAnotherClass is null.");

    if (objectOfAnotherClass.SomeProperty < 0)
        throw new WhatExceptionType2("SomeProperty must not be negative.");
}

Stellen Sie sich das vor GetObject einige externe Bibliotheken verwendet, die nicht unter meiner Kontrolle stehen, und dass diese Bibliothek null wenn kein Objekt für data existiert und eine negative SomeProperty als einen gültigen Zustand und löst daher keine Ausnahme aus. Stellen Sie sich weiter vor, dass MyMethod kann nicht funktionieren ohne objectOfAnotherClass und macht keinen Sinn bei einer negativen SomeProperty .

Welche Ausnahmen sind geeignet für WhatExceptionType1/2 in dieser Situation zu werfen?

Im Grunde hatte ich vier Optionen im Kopf:

  • 1) InvalidOperationException denn MyMethod ist unter den oben beschriebenen Bedingungen nicht sinnvoll. Andererseits sagen die Richtlinien (und auch Intellisense in VS), dass eine InvalidOperationException ausgelöst werden sollte, wenn das Objekt, zu dem die Methode gehört, in einem ungültigen Zustand ist. Nun ist das Objekt selbst nicht in einem ungültigen Zustand. Stattdessen ist der Eingabeparameter data und einige andere Operationen, die auf diesem Parameter basieren, führen zu einer Situation, in der MyMethod nicht mehr funktionieren.

  • 2) ArgumentException denn es gibt Werte für data mit denen die Methode arbeiten kann, und andere Werte, mit denen die Methode nicht arbeiten kann. Aber ich kann dies nicht durch eine Inspektion überprüfen data allein, muss ich andere Operationen anrufen, bevor ich mich entscheide.

  • 3) Exception weil ich nicht weiß, welchen anderen Ausnahmetyp ich verwenden soll, und weil mir alle anderen vordefinierten Ausnahmen zu speziell und nicht passend für meine Situation erscheinen.

  • 4) MyCustomException (mein eigener Ausnahmetyp, abgeleitet von Exception ). Dies scheint immer eine Option zu sein, aber ich bin besorgt, dass ich viele spezielle Ausnahmeklassen für viele verschiedene Fehlerbedingungen definieren muss, wenn ich anfange, diesem Muster zu folgen.

Gibt es andere und bessere Möglichkeiten? Was sind die Argumente für oder gegen diese Optionen?

Vielen Dank für Ihr Feedback im Voraus!

8voto

ashes999 Punkte 9651

Wenn es eingebaute Ausnahmen gibt, die sinnvoll sind, würde ich diese nutzen. Wenn nicht, ist es sinnvoll, eine eigene Ausnahme zu erstellen - selbst wenn es sich um eine leere Klasse handelt, die Exception erweitert -, da Sie so bestimmte Ausnahmetypen erkennen können. Wenn Sie zum Beispiel gerade eine Exception geworfen haben, woher wissen Sie, dass die Exception aus folgenden Gründen entstanden ist objectOfAnotherClass null war, und dass es sich nicht um eine Ausnahme handelte, die in GetObject ?

Zusammengefasst: Spezifische Ausnahmen sind besser, weil man (potenziell) spezifische Fälle diagnostizieren und beheben kann. Verwenden Sie daher die eingebauten .NET-Ausnahmen (wenn sie ausreichen) oder erstellen Sie Ihre eigenen Ausnahmen.

Edit: Ich sollte klarstellen, dass ich nur selten bestehende Ausnahmen verwende und ihnen eine Nachricht hinzufüge. Es macht Ihren Code besser lesbar, wenn der Ausnahmetyp den Fehler angibt, anstatt dass Sie debuggen, die Ausnahme generieren und dann die Meldung untersuchen müssen, um zu sehen, was das Problem ist.

5voto

James King Punkte 6082

Seien Sie vorsichtig bei der Verwendung eingebauter Ausnahmetypen... sie haben sehr spezifische Bedeutungen für das .NET-Framework, und wenn Sie sie nicht für genau die gleiche Bedeutung haben, ist es besser, selbst zu würfeln (+1 an John Saunders für Serializeable ).

InvalidOperationException hat die Bedeutung:

Die Ausnahme, die ausgelöst wird, wenn ein Methodenaufruf für den aktuellen Zustand des Objekts ungültig ist.

Wenn Sie zum Beispiel SqlConnection.Open() erhalten Sie eine InvalidOperationException wenn Sie keine Datenquelle angegeben haben. InvalidOperationException ist für Ihr Szenario nicht geeignet.

ArgumentException ist auch nicht angebracht. Das Versäumnis, eine objectOfAnotherClass hat möglicherweise nichts mit der Übermittlung fehlerhafter Daten zu tun. Angenommen, es ist ein Dateiname, aber GetObject() keine Berechtigung hat, die Datei zu lesen. So wie die Methode geschrieben ist, gibt es keine Möglichkeit zu wissen, warum der Aufruf von GetObject() fehlgeschlagen, und Sie können höchstens sagen, dass das zurückgegebene Objekt null oder ungültig war.

Exception ist einfach eine schlechte Idee, im Allgemeinen ... es gibt dem Aufrufer absolut keine Ahnung, warum die Methode das Objekt erstellen fehlgeschlagen. (Im Übrigen, wenn man nur eine catch (Exception ex) {..} ist auch eine schlechte Idee)

Sie wollen Ausnahmen, die klar erkennen lassen, was schief gelaufen ist, und das bedeutet, dass Sie Ihre eigenen Ausnahmen erstellen müssen. Versuchen Sie, diese allgemein zu halten, um 1.000 benutzerdefinierte Ausnahmen zu vermeiden. Ich schlage vor:

ObjectCreateException:   // The call to GetObject() returned null<br />
InvalidObjectException:  // The object returned by GetObject() is invalid 
                         // (because the property < 0)

Danke für das Votum ~ ich dachte, ich füge ein Beispiel für einige benutzerdefinierte Ausnahmen hinzu, die wir geschrieben haben.

Beachten Sie, dass Sie den Methoden keinen Code hinzufügen müssen, da die benutzerdefinierten Ausnahmen nicht wirklich etwas anderes tun als ihre Basisklassen; sie stellen nur etwas anderes dar. Das zweite Beispiel fügt der Ausnahme eine Eigenschaft hinzu, so dass es Code gibt, um sie zu unterstützen (einschließlich Konstruktoren).

Die erste ist eine generische Basis für alle unsere Ausnahmen; es gilt die Regel "Don't catch general Exceptions" (obwohl wir es trotzdem tun... es erlaubt uns, zwischen den von uns generierten Ausnahmen und den vom Framework ausgelösten Ausnahmen zu unterscheiden). Die zweite ist eine spezifischere Ausnahme, die sich ableitet von Gs3Exception und serialisiert eine benutzerdefinierte Eigenschaft.

Das .NET-Entwicklungsteam beschloss ApplicationException hatte keinen realen Wert und wurde abgeschafft, aber der Purist in mir mochte es schon immer, also wird es in meinem Code beibehalten. In diesem Fall bringt es jedoch wirklich keinen Mehrwert und erhöht nur die Tiefe der Vererbungshierarchie. Es steht Ihnen also frei, direkt zu erben von Exception stattdessen.

/// <summary>
/// A general, base error for GS3 applications </summary>
[Serializable]
public class Gs3Exception : ApplicationException {

    /// <summary>
    /// Initializes a new instance of the <see cref="Gs3Exception"/> class </summary>
    public Gs3Exception() {}

    /// <summary>
    /// Initializes a new instance of the <see cref="Gs3Exception"/> class </summary>
    /// <param name="message">A brief, descriptive message about the error </param>
    public Gs3Exception(string message) : base(message) {}

    /// <summary>
    /// Initializes a new instance of the <see cref="Gs3Exception"/> class 
    /// when deserializing </summary>
    /// <param name="info">The object that holds the serialized object data </param>
    /// <param name="context">The contextual information about the source or
    ///  destination.</param>
    public Gs3Exception(SerializationInfo info, StreamingContext context) : base(info, context) { }

    /// <summary>
    /// Initializes a new instance of the <see cref="Gs3Exception"/> class
    /// with a message and inner exception </summary>
    /// <param name="Message">A brief, descriptive message about the error </param>
    /// <param name="exc">The exception that triggered the failure </param>
    public Gs3Exception(string Message, Exception exc) : base(Message, exc) { }

}

/// <summary>
/// An object queried in an request was not found </summary>
[Serializable]
public class ObjectNotFoundException : Gs3Application {

    private string objectName = string.Empty;

    /// <summary>
    /// Initializes a new instance of the <see cref="ObjectNotFoundException"/> class </summary>
    public ObjectNotFoundException() {}

    /// <summary>
    /// Initializes a new instance of the <see cref="ObjectNotFoundException"/> class </summary>
    /// <param name="message">A brief, descriptive message about the error</param>
    public ObjectNotFoundException(string message) : base(message) {}

    /// <summary>
    /// Initializes a new instance of the <see cref="ObjectNotFoundException"/> class </summary>
    /// <param name="ObjectName">Name of the object not found </param>
    /// <param name="message">A brief, descriptive message about the error </param>
    public ObjectNotFoundException(string ObjectName, string message) : this(message) {
        this.ObjectName = ObjectName;
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="ObjectNotFoundException"/> class.
    /// This method is used during deserialization to retrieve properties from 
    /// the serialized data. </summary>
    /// <param name="info">The object that holds the serialized object data.</param>
    /// <param name="context">The contextual information about the source or
    /// destination.</param>
    public ObjectNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) {
        if (null != info) {
            this.objectName = info.GetString("objectName");
        }
    }

    /// <summary>
    /// When serializing, sets the <see cref="T:System.Runtime.Serialization.SerializationInfo"/> 
    /// with information about the exception. </summary>
    /// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> that holds 
    /// the serialized object data about the exception being thrown.</param>
    /// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext"/> that contains contextual information about the source or destination.</param>
    /// <exception cref="T:System.ArgumentNullException">
    /// The <paramref name="info"/> parameter is a null reference (Nothing in Visual Basic) </exception>
    /// <PermissionSet>
    ///     <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Read="*AllFiles*" PathDiscovery="*AllFiles*"/>
    ///     <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="SerializationFormatter"/>
    /// </PermissionSet>
    [SecurityPermissionAttribute(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.SerializationFormatter)]
    public override void GetObjectData(SerializationInfo info, StreamingContext context) {

        base.GetObjectData(info, context);

        //  'info' guaranteed to be non-null (base.GetObjectData() will throw an ArugmentNullException if it is)
        info.AddValue("objectName", this.objectName);

    }

    /// <summary>
    /// Gets or sets the name of the object not found </summary>
    /// <value>The name of the object </value>
    public string ObjectName {
        get { return objectName; }
        set { objectName = value; }
    }

}

PS: Bevor mich jemand darauf anspricht: Der Grund für eine Basis Gs3Exception die nicht mehr Wert hat als die ApplicationException ist der Enterprise Library Exception Handling Application Block... indem wir eine Basisausnahme auf Anwendungsebene haben, können wir allgemeine Protokollierungsrichtlinien für Ausnahmen erstellen, die direkt von unserem Code ausgelöst werden.

4voto

KeithS Punkte 67713

Meine Stimme würde lauten ArgumentException zumindest im ersten Fall, wenn nicht in beiden; ArgumentExceptions sollten ausgelöst werden, Zitat, "wenn eines der Argumente, die einer Methode übergeben werden, nicht gültig ist". Wenn MyMethod das Argument nicht verwenden kann data um eine gültige Instanz von AnotherClass wie von MyMethod erwartet, ist das Argument für die Verwendung in MyMethod ungültig.

Solange Sie nicht vorhaben, Ausnahmen verschiedener Typen abzufangen und unterschiedlich zu behandeln, spielt es keine Rolle, welche Ausnahme genau ausgelöst wird. Einige Ausnahmen (wie ArgumentNullException) erstellen eine benutzerdefinierte Nachricht auf der Grundlage von sehr wenigen Informationen und sind daher leicht einzurichten und zu lokalisieren, andere (wie SqlExceptions) haben spezifischere Daten darüber, was schief gelaufen ist, aber all das ist überflüssig, wenn alles, was Sie wollen, ist eine Ausnahme zu werfen, die sagt "Oops!".

4voto

Hans Passant Punkte 894572

Hier ist es angebracht, Folgendes zu tun no eine Ausnahme auslösen, wenn Sie es vermeiden können. Es gibt zwei grundlegende Informationen, die fehlen, um das Richtige zu tun. Erstens ist da das String-Argument, anscheinend hat der Aufrufer dieser Methode ein geheimes Wissen darüber, wie GetObject() funktioniert, um zu wissen, was der richtige String sein sollte. Sie müssen also den Aufrufer als Autorität behandeln, er weiß viel mehr über GetObject() als Sie.

Das zweite ist das GetObject()-Verhalten. Anscheinend hat der Autor es so entworfen, dass es nicht außergewöhnlich und erwartet, dass es eine Null zurückgibt. Ein viel stärkerer Vertrag wären zwei Methoden, TryGetObject() und GetObject(), wobei die letztere einen Wurf auslöst. Oder allgemeiner, ein zusätzliches Argument für GetObject() namens throwOnFailure . Aber das ist nicht passiert, null ist zurück und normal.

Sie brechen diesen Vertrag hier. Sie versuchen, ihn von nicht außergewöhnlich in außergewöhnlich zu verwandeln, ohne jedoch zu wissen, was falsch ist. Am besten ist es, den Vertrag nicht zu ändern und es dem Aufrufer Ihrer Methode zu überlassen, damit umzugehen. Schließlich ist er derjenige, der weiß, was GetObject() tut. Ändern Sie den Namen der Methode, verwenden Sie das Wort "Try", und geben Sie ein bool zurück.

Dies alles setzt voraus, dass der Autor von GetObject() wusste, was er tat. Wenn das nicht der Fall ist, können Sie wenig tun, um die Situation zu verbessern. Werfen Sie ArgumentException, wenn Sie Grund zu der Annahme haben, dass der Aufrufer es vermasselt haben könnte, NullReferenceException, wenn Sie glauben, dass der Autor von GetObject() einen Fehler in seinem Code haben könnte.

3voto

Steve Punkte 48892

Wie wäre es mit der Verwendung der ObjectNotFoundException. Das würde die Situation korrekt beschreiben.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X