Ich denke, eines der wichtigsten Probleme in diesem Thema ist das Erstellen einer Architektur für die Ausnahmebehandlung in Ihrer Anwendung. Das ist aus vielen Gründen interessant. Und der Hauptgrund, denke ich, ist eine offensichtliche Einfachheit, mit der man nicht immer umgehen kann. Alle grundlegenden Konstrukte wie IEnumerable
, IDisposable
, IObservable
usw. habe diese Eigenschaft und benutze sie überall. Einerseits versucht ihre Einfachheit, diese Konstrukte in verschiedenen Situationen zu verwenden. Andererseits sind sie voller Fallen, die Sie möglicherweise nicht herausholen. Wenn Sie sich die Menge an Informationen ansehen, die wir behandeln, haben Sie möglicherweise eine Frage: Was ist das Besondere an Ausnahmesituationen?
Um jedoch Schlussfolgerungen zum Aufbau der Architektur von Ausnahmeklassen zu ziehen, sollten wir einige Details zu deren Klassifizierung erfahren. Denn bevor ein Programmiersystem erstellt wird, das für den Codebenutzer klar ist, sollte ein Programmierer festlegen, wann der Fehlertyp ausgewählt und wann Ausnahmen abgefangen oder übersprungen werden sollen. Lassen Sie uns also die Ausnahmesituationen (nicht die Arten von Ausnahmen) anhand verschiedener Funktionen klassifizieren.
Basierend auf einer theoretischen Möglichkeit, eine zukünftige Ausnahme zu erwischen.
Basierend auf dieser Funktion können wir Ausnahmen in solche unterteilen, die definitiv gefangen werden, und solche, die höchstwahrscheinlich nicht gefangen werden. Warum sage ich sehr wahrscheinlich ? Weil es immer jemanden gibt, der versucht, eine Ausnahme abzufangen, während dies nicht notwendig ist.
Beschreiben wir zunächst die erste Gruppe von Ausnahmen - diejenigen, die abgefangen werden sollten.
Im Falle solcher Ausnahmen sagen wir einerseits unserem Subsystem, dass wir in einen Zustand geraten sind, in dem es keinen Sinn macht, mit unseren Daten weiter zu handeln. Auf der anderen Seite meinen wir, dass nichts Katastrophales passiert ist und wir den Ausweg aus der Situation finden können, indem wir einfach die Ausnahme abfangen. Diese Eigenschaft ist sehr wichtig, da sie die Kritikalität eines Fehlers definiert und die Gewissheit gibt, dass wir, wenn wir eine Ausnahme abfangen und Ressourcen löschen, einfach mit dem Code fortfahren können.
Die zweite Gruppe befasst sich mit Ausnahmen, die zwar seltsam klingen mögen, aber nicht abgefangen werden müssen. Sie können nur zur Fehlerprotokollierung verwendet werden, nicht jedoch zur Korrektur einer Situation. Das einfachste Beispiel ist ArgumentException
und NullReferenceException
. In einer normalen Situation müssen Sie beispielsweise ArgumentNullException
nicht abfangen, da in diesem Fall genau Sie die Fehlerquelle sind. Wenn Sie eine solche Ausnahme feststellen, geben Sie zu, dass Sie einen Fehler gemacht und etwas Unannehmbares an eine Methode übergeben haben:
void SomeMethod(object argument) { try { AnotherMethod(argument); } catch (ArgumentNullException exception) { // Log it } }
In dieser Methode versuchen wir, ArgumentNullException
. Aber ich denke, das ist seltsam, da es ganz unser Anliegen ist, richtige Argumente an eine Methode weiterzugeben. Nach dem Ereignis zu reagieren wäre falsch: Das Beste, was Sie in einer solchen Situation tun können, ist, die übergebenen Daten vor dem Aufrufen einer Methode im Voraus zu überprüfen oder sogar solchen Code zu erstellen, bei dem es unmöglich ist, falsche Parameter zu erhalten.
Eine weitere Gruppe von Ausnahmesituationen sind schwerwiegende Fehler. Wenn ein Cache fehlerhaft ist und die Arbeit eines Subsystems ohnehin nicht korrekt ist, handelt es sich um einen schwerwiegenden Fehler, und der nächste Code auf dem Stapel erkennt ihn nicht mit Sicherheit:
T GetFromCacheOrCalculate() { try { if(_cache.TryGetValue(Key, out var result)) { return result; } else { T res = Strategy(Key); _cache[Key] = res; return res; } } cache (CacheCorruptedException exception) { RecreateCache(); return GetFromCacheOrCalculate(); } }
CacheCorruptedException
ist eine Ausnahme, die bedeutet, dass "der Festplatten-Cache inkonsistent ist". Wenn die Ursache eines solchen Fehlers für das Cache-Subsystem schwerwiegend ist (z. B. gibt es keine Zugriffsrechte für Cache-Dateien), kann der folgende Code den Cache nicht mit der Anweisung RecreateCache
neu erstellen. Daher ist das Abfangen dieser Ausnahme selbst ein Fehler.
Basierend auf dem Gebiet, in dem tatsächlich eine Ausnahmesituation erfasst wird
Ein weiteres Problem ist, ob wir einige Ausnahmen abfangen oder an jemanden weitergeben sollten, der die Situation besser versteht. Mit anderen Worten, wir sollten Verantwortungsbereiche festlegen. Lassen Sie uns den folgenden Code untersuchen:
namespace JetFinance.Strategies { public class WildStrategy : StrategyBase { private Random random = new Random(); public void PlayRussianRoulette() { if(DateTime.Now.Second == (random.Next() % 60)) { throw new StrategyException(); } } } public class StrategyException : Exception { /* .. */ } } namespace JetFinance.Investments { public class WildInvestment { WildStrategy _strategy; public WildInvestment(WildStrategy strategy) { _strategy = strategy; } public void DoSomethingWild() { ?try? { _strategy.PlayRussianRoulette(); } catch(StrategyException exception) { } } } } using JetFinance.Strategies; using JetFinance.Investments; void Main() { var foo = new WildStrategy(); var boo = new WildInvestment(foo); ?try? { boo.DoSomethingWild(); } catch(StrategyException exception) { } }
Welche der beiden Strategien ist besser geeignet? Der Verantwortungsbereich ist sehr wichtig. Zunächst scheint es, dass die Arbeit und Konsistenz von WildInvestment
vollständig von WildStrategy
. Wenn WildInvestment
diese Ausnahme einfach ignoriert, wird sie in die obere Ebene WildInvestment
und wir sollten nichts tun. Beachten Sie jedoch, dass die Main
Methode in Bezug auf die Architektur eine Ausnahme von einer Ebene abfängt, während die Methode von einer anderen aufgerufen wird. Wie sieht es in Bezug auf die Nutzung aus? So sieht es aus:
- Die Verantwortung für diese Ausnahme wurde uns übertragen.
- Der Benutzer dieser Klasse ist sich nicht sicher, ob diese Ausnahme zuvor absichtlich durch eine Reihe von Methoden übergeben wurde.
- Wir beginnen neue Abhängigkeiten zu erstellen, die wir durch Aufrufen einer Zwischenschicht beseitigt haben.
Daraus ergibt sich jedoch eine andere Schlussfolgerung: Wir sollten catch
in der DoSomethingWild
Methode verwenden. Und das ist für uns etwas seltsam: WildInvestment
ist kaum von etwas abhängig. Ich meine, wenn PlayRussianRoulette
nicht funktioniert hat, passiert dasselbe mit DoSomethingWild
: Es hat keine Rückkehrcodes, aber es muss Roulette spielen. Was können wir also in einer scheinbar hoffnungslosen Situation tun? Die Antwort ist eigentlich einfach: Auf einer anderen Ebene zu sein DoSomethingWild
sollte eine eigene Ausnahme DoSomethingWild
, die zu dieser Ebene gehört, und sie in InnerException
als ursprüngliche InnerException
eines Problems InnerException
:
namespace JetFinance.Strategies { pubilc class WildStrategy { private Random random = new Random(); public void PlayRussianRoulette() { if(DateTime.Now.Second == (random.Next() % 60)) { throw new StrategyException(); } } } public class StrategyException : Exception { /* .. */ } } namespace JetFinance.Investments { public class WildInvestment { WildStrategy _strategy; public WildInvestment(WildStrategy strategy) { _strategy = strategy; } public void DoSomethingWild() { try { _strategy.PlayRussianRoulette(); } catch(StrategyException exception) { throw new FailedInvestmentException("Oops", exception); } } } public class InvestmentException : Exception { /* .. */ } public class FailedInvestmentException : Exception { /* .. */ } } using JetFinance.Investments; void Main() { var foo = new WildStrategy(); var boo = new WildInvestment(foo); try { boo.DoSomethingWild(); } catch(FailedInvestmentException exception) { } }
Indem wir eine Ausnahme in eine andere einschließen, übertragen wir das Problem von einer Anwendungsebene auf eine andere und machen seine Arbeit für einen Verbraucher dieser Klasse vorhersehbarer: die Main
Methode.
Basierend auf Wiederverwendungsproblemen
Oft fühlen wir uns zu faul, um eine neue Art von Ausnahme zu erstellen, aber wenn wir uns dazu entschließen, ist nicht immer klar, auf welche Art wir uns stützen sollen. Aber genau diese Entscheidungen bestimmen die gesamte Architektur von Ausnahmesituationen. Schauen wir uns einige beliebte Lösungen an und ziehen wir einige Schlussfolgerungen.
Bei der Auswahl des Ausnahmetyps können wir eine zuvor erstellte Lösung verwenden, dh eine Ausnahme mit dem Namen suchen, der einen ähnlichen Sinn enthält, und diese verwenden. Wenn wir beispielsweise eine Entität über einen Parameter erhalten haben und diese Entität nicht gefällt, können wir eine InvalidArgumentException
, die die Ursache eines Fehlers in der Nachricht angibt. Dieses Szenario sieht gut aus, insbesondere da sich InvalidArgumentException
in der Gruppe der Ausnahmen befindet, die möglicherweise nicht abgefangen werden. Die Auswahl von InvalidDataException
ist jedoch falsch, wenn Sie mit einigen Datentypen arbeiten. Dies liegt daran, dass sich dieser Typ im Bereich System.IO
befindet, mit dem Sie wahrscheinlich nicht zu tun haben. Daher ist es fast immer falsch, nach einem vorhandenen Typ zu suchen, anstatt selbst einen zu entwickeln. Es gibt fast keine Ausnahmen für ein allgemeines Aufgabenspektrum. Praktisch alle sind für bestimmte Situationen vorgesehen. Wenn Sie sie in anderen Fällen wiederverwenden, wird die Architektur von Ausnahmesituationen erheblich verletzt. Darüber hinaus kann eine Ausnahme eines bestimmten Typs (z. B. System.IO.InvalidDataException
) einen Benutzer verwirren: Einerseits wird er sehen, dass die Ausnahme zum System.IO
Namespace gehört, andererseits wird sie ausgelöst ein völlig anderer Namespace. Wenn dieser Benutzer über die Regeln zum Auslösen dieser Ausnahme nachdenkt, kann er unter referenceource.microsoft.com alle Stellen finden, an denen sie ausgelöst wird :
internal class System.IO.Compression.Inflater
Der Benutzer wird das verstehen Jemand ist alle Daumen Diese Art von Ausnahme verwirrte ihn, da die Methode, die diese Ausnahme auslöste, sich nicht mit Komprimierung befasste.
In Bezug auf die Wiederverwendung können Sie einfach eine Ausnahme erstellen und das Feld ErrorCode
deklarieren. Das scheint eine gute Idee zu sein. Sie lösen einfach dieselbe Ausnahme aus, legen den Code fest und verwenden nur einen catch
, um Ausnahmen zu behandeln, wodurch die Stabilität einer Anwendung erhöht wird. Ich glaube jedoch, dass Sie diese Position überdenken sollten. Natürlich erleichtert dieser Ansatz einerseits das Leben. Auf der anderen Seite schließen Sie jedoch die Möglichkeit aus, eine Untergruppe von Ausnahmen abzufangen, die einige gemeinsame Merkmale aufweisen. Beispiel: ArgumentException
, die eine Reihe von Ausnahmen durch Vererbung vereint. Ein weiterer schwerwiegender Nachteil ist ein übermäßig großer und nicht lesbarer Code, der eine fehlercodebasierte Filterung veranlassen muss. Die Einführung eines umfassenden Typs mit einem Fehlercode ist jedoch besser geeignet, wenn sich ein Benutzer nicht um die Angabe eines Fehlers kümmern muss.
public class ParserException { public ParserError ErrorCode { get; } public ParserException(ParserError errorCode) { ErrorCode = errorCode; } public override string Message { get { return Resources.GetResource($"{nameof(ParserException)}{Enum.GetName(typeof(ParserError), ErrorCode)}"); } } } public enum ParserError { MissingModifier, MissingBracket, // ... } // Usage throw new ParserException(ParserError.MissingModifier);
Dem Code, der den Parser-Aufruf schützt, ist es egal, warum das Parsen fehlgeschlagen ist: Er ist an dem Fehler als solchem interessiert. Wenn jedoch die Fehlerursache wichtig wird, kann ein Benutzer den Fehlercode immer von der ErrorCode
Eigenschaft ErrorCode
. Und Sie müssen wirklich nicht nach den erforderlichen Wörtern in einer Teilzeichenfolge von Message
suchen.
Wenn wir uns nicht für eine Wiederverwendung entscheiden, können wir für jede Situation eine Art Ausnahme erstellen. Einerseits klingt es logisch: eine Art von Fehler - eine Art von Ausnahme. Übertreiben Sie jedoch nicht: Wenn zu viele Arten von Ausnahmen vorhanden sind, tritt das Problem auf, dass sie abgefangen werden, da der Code einer aufrufenden Methode mit catch
Blöcken überladen wird. Weil es alle Arten von Ausnahmen verarbeiten muss, die Sie an es übergeben möchten. Ein weiterer Nachteil ist rein architektonisch. Wenn Sie keine Ausnahmen verwenden, verwirren Sie diejenigen, die diese Ausnahmen verwenden: Sie haben möglicherweise viele Gemeinsamkeiten, werden jedoch separat abgefangen.
Es gibt jedoch großartige Szenarien, um separate Typen für bestimmte Situationen einzuführen. Zum Beispiel, wenn der Fehler nicht eine ganze Entität betrifft, sondern eine bestimmte Methode. Dann sollte dieser Fehlertyp einen solchen Platz in der Hierarchie der Vererbung einnehmen, dass niemand jemals daran denken würde, ihn zusammen mit etwas anderem zu erfassen: zum Beispiel durch einen separaten Vererbungszweig.
Wenn Sie beide Ansätze kombinieren, erhalten Sie eine Reihe leistungsstarker Instrumente, mit denen Sie mit einer Gruppe von Fehlern arbeiten können: Sie können einen gemeinsamen abstrakten Typ einführen und bestimmte Fälle davon erben. Die Basisklasse (unser allgemeiner Typ) muss eine abstrakte Eigenschaft erhalten, die zum Speichern eines Fehlercodes ausgelegt ist, während Erben diesen Code durch Überschreiben dieser Eigenschaft angeben.
public abstract class ParserException { public abstract ParserError ErrorCode { get; } public override string Message { get { return Resources.GetResource($"{nameof(ParserException)}{Enum.GetName(typeof(ParserError), ErrorCode)}"); } } } public enum ParserError { MissingModifier, MissingBracket } public class MissingModifierParserException : ParserException { public override ParserError ErrorCode { get; } => ParserError.MissingModifier; } public class MissingBracketParserException : ParserException { public override ParserError ErrorCode { get; } => ParserError.MissingBracket; } // Usage throw new MissingModifierParserException(ParserError.MissingModifier);
Mit diesem Ansatz erhalten wir einige wunderbare Eigenschaften:
- Einerseits fangen wir immer wieder Ausnahmen mit einem Basistyp (allgemein) ab.
- Auf der anderen Seite können wir selbst bei Ausnahmen mit diesem Basistyp eine bestimmte Situation identifizieren.
- Außerdem können wir Ausnahmen über einen bestimmten Typ anstelle eines Basistyps abfangen, ohne die flache Struktur von Klassen zu verwenden.
Ich finde es sehr praktisch.
Basierend auf der Zugehörigkeit zu einer bestimmten Gruppe von Verhaltenssituationen
Welche Schlussfolgerungen können wir aus den vorherigen Überlegungen ziehen? Versuchen wir, sie zu definieren.
Lassen Sie uns zunächst entscheiden, was eine Situation bedeutet. Normalerweise sprechen wir über Klassen und Objekte in Bezug auf Entitäten mit einem internen Status und können Aktionen für diese Entitäten ausführen. Daher umfasst die erste Art von Verhaltenssituation Aktionen für eine Entität. Wenn wir als nächstes einen Objektgraphen von außen betrachten, werden wir sehen, dass er logisch als eine Kombination von Funktionsgruppen dargestellt wird: Die erste Gruppe befasst sich mit dem Caching, die zweite mit Datenbanken, die dritte führt mathematische Berechnungen durch. Verschiedene Ebenen können verschiedene diese Gruppen durchlaufen, z. B. Ebenen der internen Statusprotokollierung, Prozessprotokollierung und Ablaufverfolgung von Methodenaufrufen. Schichten können mehrere Funktionsgruppen umfassen. Beispielsweise kann es eine Ebene eines Modells, eine Ebene von Controllern und eine Präsentationsebene geben. Diese Gruppen können sich in einer Baugruppe oder in verschiedenen befinden, aber jede Gruppe kann ihre eigenen Ausnahmesituationen erstellen.
Auf diese Weise können wir eine Hierarchie für die Arten von Ausnahmesituationen erstellen, die auf der Zugehörigkeit dieser Typen zu der einen oder anderen Gruppe oder Schicht basiert. Daher erlauben wir einem abfangenden Code, einfach zwischen diesen Typen in der Hierarchie zu navigieren.
Lassen Sie uns den folgenden Code untersuchen:
namespace JetFinance { namespace FinancialPipe { namespace Services { namespace XmlParserService { } namespace JsonCompilerService { } namespace TransactionalPostman { } } } namespace Accounting { /* ... */ } }
Wie ist es Ich denke, der Namespace ist eine perfekte Möglichkeit, die Arten von Ausnahmen basierend auf den Verhaltenssituationen auf natürliche Weise zu gruppieren: Alles, was zu bestimmten Gruppen gehört, sollte dort bleiben, einschließlich Ausnahmen. Wenn Sie eine bestimmte Ausnahme erhalten, sehen Sie außerdem den Namen ihres Typs und auch den Namespace, der eine Gruppe angibt, zu der sie gehört. Erinnern Sie sich an die fehlerhafte Wiederverwendung von InvalidDataException
die tatsächlich im System.IO
Namespace definiert ist? Die Tatsache, dass es zu diesem Namespace gehört, bedeutet, dass diese Art von Ausnahme von Klassen ausgelöst werden kann, die sich im System.IO
Namespace oder in einem verschachtelten befinden. Die eigentliche Ausnahme wurde jedoch aus einem völlig anderen Bereich geworfen, was eine Person verwirrte, die das Problem behandelt. Wenn Sie jedoch die Ausnahmetypen und die Typen, die diese Ausnahmen auslösen, in dieselben Namespaces einfügen, behalten Sie die Architektur der Typen bei und erleichtern Entwicklern das Verständnis der Gründe für das, was passiert.
Was ist der zweite Weg zur Gruppierung auf Codeebene? Vererbung:
public abstract class LoggerExceptionBase : Exception { protected LoggerException(..); } public class IOLoggerException : LoggerExceptionBase { internal IOLoggerException(..); } public class ConfigLoggerException : LoggerExceptionBase { internal ConfigLoggerException(..); }
Beachten Sie, dass normale Anwendungsentitäten sowohl Verhalten als auch Daten und Gruppentypen erben, die zu einer einzelnen Gruppe von Entitäten gehören . Für Ausnahmen erben sie jedoch und werden basierend auf einer einzelnen Gruppe von Situationen gruppiert, da das Wesentliche einer Ausnahme keine Entität, sondern ein Problem ist.
Wenn wir diese beiden Gruppierungsmethoden kombinieren, können wir die folgenden Schlussfolgerungen ziehen:
- Innerhalb der
Assembly
sollte es einen Basistyp von Ausnahmen geben, die von dieser Assembly ausgelöst werden. Diese Art von Ausnahmen sollte sich in einem Root-Namespace der Assembly befinden. Dies ist die erste Gruppierungsebene. - Darüber hinaus können sich in einer Assembly ein oder mehrere Namespaces befinden. Jeder von ihnen unterteilt die Baugruppe in Funktionszonen und definiert die Gruppen von Situationen, die in dieser Baugruppe angezeigt werden. Dies können Zonen von Controllern, Datenbankentitäten, Datenverarbeitungsalgorithmen usw. sein. Für uns bedeuten diese Namespaces Gruppierungstypen basierend auf ihrer Funktion. In Bezug auf Ausnahmen werden sie jedoch basierend auf Problemen innerhalb derselben Baugruppe gruppiert.
- Ausnahmen müssen von Typen im selben Namespace der oberen Ebene geerbt werden. Dies stellt sicher, dass der Endbenutzer Situationen eindeutig versteht und keine falschen typbasierten Ausnahmen abfängt. Zugegeben, es wäre seltsam,
global::Finiki.Logistics.OhMyException
durch catch(global::Legacy.LoggerExeption exception)
, während der folgende Code absolut angemessen aussieht:
namespace JetFinance.FinancialPipe { namespace Services.XmlParserService { public class XmlParserServiceException : FinancialPipeExceptionBase { // .. } public class Parser { public void Parse(string input) { // .. } } } public abstract class FinancialPipeExceptionBase : Exception { } } using JetFinance.FinancialPipe; using JetFinance.FinancialPipe.Services.XmlParserService; var parser = new Parser(); try { parser.Parse(); } catch (XmlParserServiceException exception) { // Something is wrong in the parser } catch (FinancialPipeExceptionBase exception) { // Something else is wrong. Looks critical because we don't know the real reason }
Hier ruft der Benutzercode eine Bibliotheksmethode auf, die XmlParserServiceException
in bestimmten Situationen eine XmlParserServiceException
kann. Und wie wir wissen, bezieht sich diese Ausnahme auf den geerbten Namespace JetFinance.FinancialPipe.FinancialPipeExceptionBase
bedeutet, dass es möglicherweise einige andere Ausnahmen gibt - diesmal erstellt der XmlParserService
Mikroservice nur eine Ausnahme, andere Ausnahmen können jedoch in Zukunft auftreten. Da wir eine Konvention zum Erstellen von Ausnahmetypen haben, wissen wir, von welcher Entität diese neue Ausnahme geerbt wird, und setzen im Voraus einen umfassenden catch
. Dadurch können wir alle für uns irrelevanten Dinge überspringen.
Wie baut man eine solche Hierarchie von Typen auf?
- Zunächst sollten wir eine Basisklasse für eine Domäne erstellen. Nennen wir es eine Domain-Basisklasse. In diesem Fall ist eine Domäne ein Wort, das eine Reihe von Assemblys umfasst und diese basierend auf einer Funktion kombiniert: Protokollierung, Geschäftslogik, Benutzeroberfläche. Ich meine Funktionszonen einer Anwendung, die so groß wie möglich sind.
- Als nächstes sollten wir eine zusätzliche Basisklasse für Ausnahmen einführen, die abgefangen werden müssen: Alle Ausnahmen, die mit dem Schlüsselwort
catch
abgefangen werden, werden von dieser Basisklasse geerbt. - Alle Ausnahmen, die auf schwerwiegende Fehler hinweisen, sollten direkt von einer Domänenbasisklasse geerbt werden. So werden wir sie von denen trennen, die auf der Architekturebene gefangen wurden;
- Teilen Sie die Domäne basierend auf Namespaces in Funktionsbereiche auf und deklarieren Sie den Basistyp der Ausnahmen, die aus jedem Bereich ausgelöst werden. Hier muss der gesunde Menschenverstand verwendet werden: Wenn eine Anwendung einen hohen Grad an Namespace-Verschachtelung aufweist, sollten Sie nicht für jede Verschachtelungsebene einen Basistyp erstellen. Wenn jedoch eine Verzweigung auf Verschachtelungsebene erfolgt, wenn eine Gruppe von Ausnahmen in einen Namespace und eine andere Gruppe in einen anderen Namespace wechselt, müssen für jede Untergruppe zwei Basistypen verwendet werden. - Besondere Ausnahmen sollten von den Arten von Ausnahmen geerbt werden, die zu Funktionsbereichen gehören
- Wenn eine Gruppe von speziellen Ausnahmen kombiniert werden kann, ist es erforderlich, dies in einem weiteren Basistyp zu tun: So können Sie sie leichter abfangen;
- Wenn Sie annehmen, dass die Gruppe häufiger mit einer Basisklasse abgefangen wird, führen Sie den gemischten Modus mit ErrorCode ein.
Basierend auf der Fehlerquelle
Die Fehlerquelle kann eine weitere Grundlage sein, um Ausnahmen in einer Gruppe zu kombinieren. Wenn Sie beispielsweise eine Klassenbibliothek entwerfen, können die folgenden Dinge Gruppen von Quellen bilden:
- unsicherer Code-Aufruf mit einem Fehler. Diese Situation kann behoben werden, indem eine Ausnahme oder ein Fehlercode in einen eigenen Ausnahmetyp eingeschlossen wird, während zurückgegebene Daten (z. B. der ursprüngliche Fehlercode) in einer öffentlichen Eigenschaft der Ausnahme gespeichert werden.
- Ein Code-Aufruf durch externe Abhängigkeiten, der Ausnahmen ausgelöst hat, die von unserer Bibliothek nicht abgefangen werden können, da sie außerhalb ihres Verantwortungsbereichs liegen. Diese Gruppe kann Ausnahmen von den Methoden der Entitäten enthalten, die als Parameter einer aktuellen Methode akzeptiert wurden, oder Ausnahmen vom Konstruktor einer Klasse, deren Methode eine externe Abhängigkeit genannt hat. Beispielsweise hat eine Methode unserer Klasse eine Methode einer anderen Klasse aufgerufen, deren Instanz über Parameter einer anderen Methode zurückgegeben wurde. Wenn eine Ausnahme anzeigt, dass wir die
InnerExcepton
eines Problems sind, sollten wir unsere eigene Ausnahme generieren und dabei die ursprüngliche in InnerExcepton
. Wenn wir jedoch verstehen, dass das Problem durch eine externe Abhängigkeit verursacht wurde, ignorieren wir diese Ausnahme als zu einer Gruppe externer Abhängigkeiten gehörend, die außerhalb unserer Kontrolle liegen. - unser eigener Code, der versehentlich in einen inkonsistenten Zustand versetzt wurde. Ein gutes Beispiel ist das Parsen von Text - keine externen Abhängigkeiten, keine Übertragung in eine
unsafe
Welt, aber es tritt ein Problem beim Parsen auf.
Dieses Kapitel wurde vom Autor und von professionellen Übersetzern gemeinsam aus dem Russischen übersetzt . Sie können uns bei der Übersetzung von Russisch oder Englisch in eine andere Sprache helfen, hauptsächlich ins Chinesische oder Deutsche.
Wenn Sie sich bei uns bedanken möchten, können Sie dies am besten tun, indem Sie uns einen Stern auf Github geben oder das Repository teilen
github / sidristij / dotnetbook .