Frage

Ich kenne zwei Ansätze zur Ausnahmebehandlung, schauen wir uns sie an.

  1. Vertragsansatz.

    Wenn eine Methode nicht das tut, was sie im Methodenheader verspricht, löst sie eine Ausnahme aus.Somit „verspricht“ die Methode, dass sie die Operation ausführen wird, und wenn sie aus irgendeinem Grund fehlschlägt, wird sie eine Ausnahme auslösen.

  2. Außergewöhnlicher Ansatz.

    Lösen Sie nur Ausnahmen aus, wenn etwas wirklich Seltsames passiert.Sie sollten keine Ausnahmen verwenden, wenn Sie die Situation mit normalem Kontrollfluss (If-Anweisungen) lösen können.Sie verwenden keine Ausnahmen für die Ablaufsteuerung, wie Sie es beim Vertragsansatz tun könnten.

Lassen Sie uns beide Ansätze in verschiedenen Fällen verwenden:

Wir haben eine Customer-Klasse mit einer Methode namens OrderProduct.

Vertragsansatz:

class Customer
{
     public void OrderProduct(Product product)
     {
           if((m_credit - product.Price) < 0)
                  throw new NoCreditException("Not enough credit!");
           // do stuff 
     }
}

außergewöhnlicher Ansatz:

class Customer
{
     public bool OrderProduct(Product product)
     {
          if((m_credit - product.Price) < 0)
                   return false;
          // do stuff
          return true;
     }
}

if !(customer.OrderProduct(product))
            Console.WriteLine("Not enough credit!");
else
   // go on with your life

Hier bevorzuge ich den außergewöhnlichen Ansatz, da es nicht wirklich außergewöhnlich ist, dass ein Kunde kein Geld hat, vorausgesetzt, er hat nicht im Lotto gewonnen.

Aber hier ist eine Situation, in der ich mich hinsichtlich des Vertragsstils irre.

Außergewöhnlich:

class CarController
{
     // returns null if car creation failed.
     public Car CreateCar(string model)
     {
         // something went wrong, wrong model
         return null;
     }
 }

Wenn ich eine Methode namens CreateCar aufrufe, erwarte ich verdammt noch mal eine Car-Instanz anstelle eines miesen Nullzeigers, der meinen laufenden Code ein Dutzend Zeilen später ruinieren kann.Daher bevorzuge ich einen Vertrag gegenüber diesem:

class CarController
{

     public Car CreateCar(string model)
     {
         // something went wrong, wrong model
         throw new CarModelNotKnownException("Model unkown");

         return new Car();
     }
 }

Welchen Stil verwenden Sie?Was ist Ihrer Meinung nach der beste allgemeine Ansatz für Ausnahmen?

War es hilfreich?

Lösung

Ich bevorzuge das, was Sie den „Vertragsansatz“ nennen.In einer Sprache, die Ausnahmen unterstützt, ist die Rückgabe von Nullen oder anderen Sonderwerten zur Anzeige von Fehlern nicht erforderlich.Ich finde es viel einfacher, Code zu verstehen, wenn er nicht eine Reihe von „if (result == NULL)“- oder „if (result == -1)“-Klauseln enthält, gemischt mit einer möglicherweise sehr einfachen, unkomplizierten Logik.

Andere Tipps

Mein üblicher Ansatz besteht darin, einen Vertrag zu verwenden, um jede Art von Fehler aufgrund eines „Client“-Aufrufs zu behandeln, d. h. aufgrund eines externen Fehlers (z. B. ArgumentNullException).

Jeder Fehler in den Argumenten wird nicht behandelt.Es wird eine Ausnahme ausgelöst und der „Client“ ist für die Bearbeitung verantwortlich.Versuchen Sie andererseits bei internen Fehlern immer, diese zu korrigieren (als ob Sie aus irgendeinem Grund keine Datenbankverbindung herstellen könnten) und lösen Sie die Ausnahme nur dann erneut aus, wenn Sie damit nicht umgehen können.

Es ist wichtig zu bedenken, dass die meisten nicht behandelten Ausnahmen auf dieser Ebene ohnehin nicht vom Client behandelt werden können, sodass sie wahrscheinlich nur an den allgemeinsten Ausnahmebehandler weitergeleitet werden. Wenn also eine solche Ausnahme auftritt, sind Sie wahrscheinlich sowieso FUBAR.

Ich glaube, dass Sie den Vertragsansatz verwenden sollten, wenn Sie eine Klasse erstellen, die von einem externen Programm verwendet (oder von anderen Programmen wiederverwendet) wird.Ein gutes Beispiel hierfür ist eine API jeglicher Art.

Wenn Sie tatsächlich an Ausnahmen interessiert sind und darüber nachdenken möchten, wie Sie sie zum Aufbau robuster Systeme verwenden können, sollten Sie die Lektüre in Betracht ziehen Erstellen zuverlässiger verteilter Systeme bei Vorhandensein von Softwarefehlern.

Beide Ansätze sind richtig.Das bedeutet, dass ein Vertrag so geschrieben sein sollte, dass für alle Fälle, die keine wirklichen Ausnahmefälle sind, ein Verhalten festgelegt wird, das keine Ausnahme erfordert.

Beachten Sie, dass einige Situationen je nach den Erwartungen des Aufrufers des Codes außergewöhnlich sein können oder auch nicht.Wenn der Aufrufer erwartet, dass ein Wörterbuch ein bestimmtes Element enthält, und das Fehlen dieses Elements auf ein schwerwiegendes Problem hinweisen würde, ist das Nichtfinden des Elements eine Ausnahmebedingung und sollte dazu führen, dass eine Ausnahme ausgelöst wird.Wenn der Aufrufer jedoch nicht wirklich weiß, ob ein Element vorhanden ist, und gleichermaßen darauf vorbereitet ist, mit seiner Anwesenheit oder Abwesenheit umzugehen, wäre das Fehlen des Elements eine erwartete Bedingung und sollte keine Ausnahme auslösen.Der beste Weg, mit solchen Schwankungen in den Erwartungen des Anrufers umzugehen, besteht darin, in einem Vertrag zwei Methoden festzulegen:eine DoSomething-Methode und eine TryDoSomething-Methode, z.B.

TValue GetValue(TKey Key);
bool TryGetValue(TKey Key, ref TValue value);

Beachten Sie, dass das standardmäßige „Try“-Muster zwar wie oben dargestellt aussieht, einige Alternativen jedoch auch hilfreich sein können, wenn man eine Schnittstelle entwirft, die Elemente erzeugt:

 // In case of failure, set ok false and return default<TValue>.
TValue TryGetResult(ref bool ok, TParam param);
// In case of failure, indicate particular problem in GetKeyErrorInfo
// and return default<TValue>.
TValue TryGetResult(ref GetKeyErrorInfo errorInfo, ref TParam param);

Beachten Sie, dass die Verwendung so etwas wie des normalen TryGetResult-Musters innerhalb einer Schnittstelle dazu führt, dass die Schnittstelle in Bezug auf den Ergebnistyp invariant wird.Durch die Verwendung eines der oben genannten Muster kann die Schnittstelle in Bezug auf den Ergebnistyp kovariant sein.Außerdem ermöglicht es die Verwendung des Ergebnisses in einer „var“-Deklaration:

  var myThingResult = myThing.TryGetSomeValue(ref ok, whatever);
  if (ok) { do_whatever }

Nicht ganz der Standardansatz, aber in manchen Fällen können die Vorteile ihn rechtfertigen.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top