Warum nicht Ausnahmen als regelmäßigen Kontrollfluss verwenden?

192

Um alle Standardantworten zu vermeiden, auf die ich hätte googeln können, werde ich ein Beispiel geben, das Sie alle nach Belieben angreifen können.

C # und Java (und zu viele andere) haben mit vielen Typen ein Überlaufverhalten, das ich überhaupt nicht mag (zB type.MaxValue + type.SmallestValue == type.MinValuezum Beispiel :) int.MaxValue + 1 == int.MinValue.

Aber angesichts meiner bösartigen Natur werde ich diese Verletzung etwas beleidigen, indem ich dieses Verhalten auf einen überschriebenen DateTimeTyp ausdehne . (Ich weiß, dass DateTimein .NET versiegelt ist, aber für dieses Beispiel verwende ich eine Pseudosprache, die genau wie C # ist, mit Ausnahme der Tatsache, dass DateTime nicht versiegelt ist.)

Die überschriebene AddMethode:

/// <summary>
/// Increments this date with a timespan, but loops when
/// the maximum value for datetime is exceeded.
/// </summary>
/// <param name="ts">The timespan to (try to) add</param>
/// <returns>The Date, incremented with the given timespan. 
/// If DateTime.MaxValue is exceeded, the sum wil 'overflow' and 
/// continue from DateTime.MinValue. 
/// </returns>
public DateTime override Add(TimeSpan ts) 
{
    try
    {                
        return base.Add(ts);
    }
    catch (ArgumentOutOfRangeException nb)
    {
        // calculate how much the MaxValue is exceeded
        // regular program flow
        TimeSpan saldo = ts - (base.MaxValue - this);
        return DateTime.MinValue.Add(saldo)                         
    }
    catch(Exception anyOther) 
    {
        // 'real' exception handling.
    }
}

Natürlich könnte ein Wenn dies genauso einfach lösen, aber die Tatsache bleibt, dass ich nicht verstehe, warum Sie keine Ausnahmen verwenden konnten (logischerweise kann ich sehen, dass Ausnahmen in bestimmten Fällen vermieden werden sollten, wenn Leistung ein Problem ist ).

Ich denke, in vielen Fällen sind sie klarer als Wenn-Strukturen und brechen keinen Vertrag, den die Methode macht.

IMHO ist die Reaktion „Niemals für den regulären Programmfluss verwenden“, die jeder zu haben scheint, nicht so gut unterbaut, wie es die Stärke dieser Reaktion rechtfertigen kann.

Oder irre ich mich?

Ich habe andere Beiträge gelesen, die sich mit allen Arten von Sonderfällen befassen, aber mein Punkt ist, dass daran nichts falsch ist, wenn Sie beide sind:

  1. klar
  2. Halten Sie den Vertrag Ihrer Methode ein

Erschieß mich.

Peter
quelle
2
+1 Mir geht es genauso. Neben der Leistung ist der einzige gute Grund, Ausnahmen für den Kontrollfluss zu vermeiden, wenn der Aufrufercode mit Rückgabewerten viel besser lesbar ist.
4
ist das: return -1, wenn etwas passiert ist, return -2, wenn etwas anderes passiert, etc ... wirklich besser lesbar als Ausnahmen?
Kender
1
Es ist traurig, dass man einen negativen Ruf dafür bekommt, die Wahrheit zu sagen: Dass Ihr Beispiel nicht mit if-Aussagen hätte geschrieben werden können. (Dies soll nicht heißen, dass es korrekt / vollständig ist.)
Ingo
8
Ich würde argumentieren, dass das Auslösen einer Ausnahme manchmal Ihre einzige Option sein könnte. Ich habe zum Beispiel eine Geschäftskomponente, die ihren internen Status innerhalb ihres Konstruktors durch Abfragen der Datenbank initialisiert. Es gibt Zeiten, in denen keine geeigneten Daten in der Datenbank verfügbar sind. Das Auslösen einer Ausnahme innerhalb des Konstruktors ist die einzige Möglichkeit, die Konstruktion des Objekts effektiv abzubrechen. Dies ist im Vertrag (in meinem Fall Javadoc) der Klasse klar angegeben, sodass ich kein Problem damit habe, dass Client-Code diese Ausnahme beim Erstellen der Komponente abfangen kann (und sollte) und von dort aus fortfährt.
Stefan Haberl
1
Da Sie eine Hypothese formuliert haben, müssen Sie auch bestätigende Beweise / Gründe anführen. Nennen Sie zunächst einen Grund, warum Ihr Code einer viel kürzeren, selbstdokumentierenden ifAussage überlegen ist . Sie werden das sehr schwer finden. Mit anderen Worten: Ihre Prämisse ist fehlerhaft, und die Schlussfolgerungen, die Sie daraus ziehen, sind daher falsch.
Konrad Rudolph

Antworten:

169

Haben Sie jemals versucht, ein Programm zu debuggen, das im normalen Betriebsverlauf fünf Ausnahmen pro Sekunde auslöst?

Ich habe.

Das Programm war ziemlich komplex (es war ein verteilter Berechnungsserver), und eine geringfügige Änderung an einer Seite des Programms konnte leicht etwas an einer völlig anderen Stelle beschädigen.

Ich wünschte, ich hätte das Programm einfach starten und auf Ausnahmen warten können, aber es gab während des Starts im normalen Betriebsverlauf rund 200 Ausnahmen

Mein Punkt: Wenn Sie Ausnahmen für normale Situationen verwenden, wie finden Sie ungewöhnliche (dh außergewöhnliche ) Situationen?

Natürlich gibt es andere starke Gründe, Ausnahmen nicht zu häufig zu verwenden, insbesondere in Bezug auf die Leistung

Brann
quelle
13
Beispiel: Wenn ich ein .net-Programm debugge, starte ich es von Visual Studio aus und fordere VS auf, alle Ausnahmen zu unterbrechen. Wenn Sie sich auf Ausnahmen als erwartetes Verhalten verlassen, kann ich das nicht mehr tun (da es 5 Mal / Sek. Unterbrechen würde), und es ist weitaus komplizierter, den problematischen Teil des Codes zu lokalisieren.
Brann
15
+1 für den Hinweis, dass Sie keinen Ausnahme-Heuhaufen erstellen möchten, in dem Sie eine tatsächliche außergewöhnliche Nadel finden.
Grant Wagner
16
bekomme diese Antwort überhaupt nicht, ich denke, die Leute verstehen das hier falsch. Es hat überhaupt nichts mit Debuggen zu tun, sondern mit Design. Das ist Zirkelschluss in seiner reinen Form, fürchte ich. Und Ihr Punkt ist wirklich neben der Frage wie zuvor angegeben
Peter
11
@Peter: Das Debuggen ohne Unterbrechung von Ausnahmen ist schwierig, und das Abfangen aller Ausnahmen ist schmerzhaft, wenn viele von ihnen beabsichtigt sind. Ich denke, ein Design, das das Debuggen schwierig macht, ist fast teilweise kaputt (mit anderen Worten, Design hat etwas mit Debugging zu tun, IMO)
Brann
7
Selbst wenn ich die Tatsache ignoriere, dass die meisten Situationen, die ich debuggen möchte, nicht den ausgelösten Ausnahmen entsprechen, lautet die Antwort auf Ihre Frage: "nach Typ", z. B. werde ich meinem Debugger sagen, dass er nur AssertionError oder StandardError abfangen soll oder etwas, das dies tut korrespondieren mit schlechten Dingen, die passieren. Wenn Sie Probleme damit haben, wie protokollieren Sie dann - protokollieren Sie nicht nach Ebene und Klasse, damit Sie nach diesen filtern können? Denkst du, das ist auch eine schlechte Idee?
Ken
157

Ausnahmen sind grundsätzlich nicht lokale gotoAussagen mit allen Konsequenzen der letzteren. Die Verwendung von Ausnahmen für die Flusskontrolle verstößt gegen das Prinzip des geringsten Erstaunens . Machen Sie Programme schwer lesbar (denken Sie daran, dass Programme zuerst für Programmierer geschrieben werden).

Darüber hinaus erwarten Compiler-Anbieter dies nicht. Sie erwarten, dass Ausnahmen selten ausgelöst werden, und lassen den throwCode normalerweise recht ineffizient sein. Das Auslösen von Ausnahmen ist eine der teuersten Operationen in .NET.

Einige Sprachen (insbesondere Python) verwenden jedoch Ausnahmen als Flusssteuerungskonstrukte. Beispielsweise lösen Iteratoren eine StopIterationAusnahme aus, wenn keine weiteren Elemente vorhanden sind. Sogar Standardsprachenkonstrukte (wie for) stützen sich darauf.

Anton Gogolev
quelle
11
Hey, Ausnahmen sind nicht erstaunlich! Und Sie widersprechen sich irgendwie, wenn Sie sagen "es ist eine schlechte Idee" und dann sagen "aber es ist eine gute Idee in Python".
hasen
6
Ich bin immer noch überhaupt nicht überzeugt: 1) Effizienz war neben der Frage, viele Nicht-Bacht-Programme könnten sich nicht weniger interessieren (zB Benutzeroberfläche) 2) Erstaunlich: Wie ich schon sagte, es ist nur erstaunlich, weil es nicht verwendet wird, aber Die Frage bleibt: Warum nicht zuerst die ID verwenden? Aber da dies die Antwort ist
Peter
4
+1 Eigentlich bin ich froh, dass Sie auf den Unterschied zwischen Python und C # hingewiesen haben. Ich denke nicht, dass es ein Widerspruch ist. Python ist viel dynamischer und die Erwartung, Ausnahmen auf diese Weise zu verwenden, ist in die Sprache eingebettet. Es ist auch Teil der EAFP-Kultur von Python. Ich weiß nicht, welcher Ansatz konzeptionell reiner oder selbstkonsistenter ist, aber ich mag die Idee, Code zu schreiben, der das tut, was andere von ihm erwarten , was unterschiedliche Stile in verschiedenen Sprachen bedeutet.
Mark E. Haase
13
Im Gegensatz zu gotonatürlich interagieren Ausnahmen korrekt mit Ihrem Aufrufstapel und dem lexikalischen Gültigkeitsbereich und lassen den Stapel oder die Bereiche nicht durcheinander.
Lukas Eder
4
Tatsächlich erwarten die meisten VM-Anbieter Ausnahmen und behandeln sie effizient. Wie @LukasEder feststellt, sind Ausnahmen insofern völlig anders als goto, als sie strukturiert sind.
Marcin
29

Meine Faustregel lautet:

  • Wenn Sie irgendetwas tun können, um einen Fehler zu beheben, fangen Sie Ausnahmen ab
  • Wenn der Fehler sehr häufig auftritt (z. B. Benutzer hat versucht, sich mit dem falschen Kennwort anzumelden), verwenden Sie Rückgabewerte
  • Wenn Sie nichts tun können, um einen Fehler zu beheben, lassen Sie ihn nicht abgefangen (oder fangen Sie ihn in Ihrem Hauptfänger ab, um die Anwendung halbwegs ordnungsgemäß herunterzufahren).

Das Problem, das ich mit Ausnahmen sehe, ist aus rein syntaktischer Sicht (ich bin mir ziemlich sicher, dass der Leistungsaufwand minimal ist). Ich mag keine Try-Blocks überall.

Nehmen Sie dieses Beispiel:

try
{
   DoSomeMethod();  //Can throw Exception1
   DoSomeOtherMethod();  //Can throw Exception1 and Exception2
}
catch(Exception1)
{
   //Okay something messed up, but is it SomeMethod or SomeOtherMethod?
}

Ein anderes Beispiel könnte sein, wenn Sie einem Handle mithilfe einer Factory etwas zuweisen müssen und diese Factory eine Ausnahme auslösen könnte:

Class1 myInstance;
try
{
   myInstance = Class1Factory.Build();
}
catch(SomeException)
{
   // Couldn't instantiate class, do something else..
}
myInstance.BestMethodEver();   // Will throw a compile-time error, saying that myInstance is uninitalized, which it potentially is.. :(

Ich persönlich denke, Sie sollten Ausnahmen für seltene Fehlerzustände (nicht genügend Speicher usw.) beibehalten und stattdessen Rückgabewerte (Werteklassen, Strukturen oder Aufzählungen) verwenden, um Ihre Fehlerprüfung durchzuführen.

Hoffe ich habe deine Frage richtig verstanden :)

cwap
quelle
4
Betreff: Ihr zweites Beispiel - warum nicht einfach nach Build den Aufruf von BestMethodEver im try-Block platzieren? Wenn Build () eine Ausnahme auslöst, wird diese nicht ausgeführt, und der Compiler ist zufrieden.
Blorgbeard ist am
2
Ja, das wäre wahrscheinlich das, was Sie am Ende haben werden, aber betrachten Sie ein komplexeres Beispiel, bei dem der myInstance-Typ selbst Ausnahmen auslösen kann. Und andere isntances im Methodenbereich können dies auch. Sie werden mit vielen verschachtelten Try / Catch-Blöcken
enden
Sie sollten in Ihrem catch-Block eine Exception-Übersetzung (in einen Exception-Typ, der der Abstraktionsebene entspricht) durchführen. Zu
Ihrer Information
Zu Ihrer Information: In C ++ können Sie mehrere Fänge setzen, nachdem Sie versucht haben, verschiedene Ausnahmen zu fangen.
RobH
2
Für Shrinkwrap-Software müssen Sie alle Ausnahmen abfangen. Richten Sie zumindest ein Dialogfeld ein, in dem erklärt wird, dass das Programm heruntergefahren werden muss, und hier ist etwas Unverständliches, das Sie in einem Fehlerbericht senden können.
David Thornley
25

Eine erste Reaktion auf viele Antworten:

Sie schreiben für die Programmierer und das Prinzip des geringsten Erstaunens

Natürlich! Aber ein Wenn ist einfach nicht immer klarer.

Es sollte nicht erstaunlich sein, zB: dividieren (1 / x) Fang (DivisionByZero) ist klarer als jeder andere, wenn für mich (bei Conrad und anderen). Die Tatsache, dass diese Art der Programmierung nicht erwartet wird, ist rein konventionell und in der Tat immer noch relevant. Vielleicht wäre in meinem Beispiel ein Wenn klarer.

Aber DivisionByZero und FileNotFound sind klarer als wenn.

Natürlich, wenn es weniger performant ist und eine Zillion Zeit pro Sekunde benötigt, sollten Sie es natürlich vermeiden, aber ich habe immer noch keinen guten Grund gelesen, das Gesamtdesign zu vermeiden.

Was das Prinzip des geringsten Erstaunens angeht: Hier besteht die Gefahr von Zirkelschluss: Angenommen, eine ganze Community verwendet ein schlechtes Design, dieses Design wird erwartet! Daher kann das Prinzip kein Gral sein und sollte sorgfältig überlegt werden.

Wie finden Sie ungewöhnliche (dh außergewöhnliche) Situationen?

In vielen Reaktionen etw. so scheint durch. Fang sie einfach, nein? Ihre Methode sollte klar und gut dokumentiert sein und den Vertrag erfüllen. Ich verstehe diese Frage nicht, die ich zugeben muss.

Debuggen aller Ausnahmen: Das Gleiche gilt manchmal nur, weil das Design, keine Ausnahmen zu verwenden, üblich ist. Meine Frage war: Warum ist das überhaupt üblich?

Peter
quelle
1
1) Prüfen Sie immer, xbevor Sie anrufen 1/x? 2) Wickeln Sie jede Divisionsoperation in einen Try-Catch-Block, um sie zu fangen DivideByZeroException? 3) Welche Logik setzen Sie in den Catch-Block, um sich zu erholen DivideByZeroException?
Lightman
1
Außer DivisionByZero und FileNotFound sind schlechte Beispiele, da es sich um Ausnahmefälle handelt, die als Ausnahmen behandelt werden sollten.
0x6C38
Es gibt nichts "übermäßig Außergewöhnliches" an einer Datei, die nicht auf diese Weise gefunden wurde, als "Anti-Ausnahme" -Personen, die hier angepriesen werden. openConfigFile();kann von einer abgefangenen FileNotFound gefolgt werden, wobei die { createDefaultConfigFile(); setFirstAppRun(); }FileNotFound-Ausnahme ordnungsgemäß behandelt wird; Kein Absturz, machen wir die Erfahrung des Endbenutzers besser, nicht schlechter. Sie können sagen "Aber was ist, wenn dies nicht wirklich der erste Lauf war und sie das jedes Mal bekommen?" Zumindest läuft die App jedes Mal und stürzt nicht bei jedem Start ab! Auf einem 1-zu-10 "das ist schrecklich": "
Erststart
Ihre Beispiele sind Ausnahmen. Nein, Sie überprüfen nicht immer, xbevor Sie anrufen 1/x, da dies normalerweise in Ordnung ist. Der Ausnahmefall ist der Fall, in dem es nicht in Ordnung ist. Wir sprechen hier nicht von xerderschütterndem Material , aber zum Beispiel würde bei einer zufälligen Ganzzahl, die zufällig angegeben wird , nur 1 von 4294967296 die Division nicht durchführen. Das ist außergewöhnlich und Ausnahmen sind ein guter Weg, um damit umzugehen. Sie könnten jedoch Ausnahmen verwenden, um das Äquivalent einer switchAnweisung zu implementieren , aber es wäre ziemlich dumm.
Thor84no
16

Vor Ausnahmen gab es in C setjmpund longjmpdas konnte verwendet werden, um ein ähnliches Abrollen des Stapelrahmens zu erreichen.

Dann wurde dem gleichen Konstrukt ein Name gegeben: "Ausnahme". Und die meisten Antworten stützen sich auf die Bedeutung dieses Namens, um über seine Verwendung zu streiten, und behaupten, dass Ausnahmen unter außergewöhnlichen Bedingungen verwendet werden sollen. Das war im Original nie die Absicht longjmp. Es gab nur Situationen, in denen Sie den Kontrollfluss über viele Stapelrahmen hinweg unterbrechen mussten.

Ausnahmen sind etwas allgemeiner, da Sie sie auch innerhalb desselben Stapelrahmens verwenden können. Dies wirft Analogien auf goto, von denen ich glaube, dass sie falsch sind. Gotos sind ein eng gekoppeltes Paar (und so sind setjmpund longjmp). Ausnahmen folgen einem lose gekoppelten Publish / Subscribe, das viel sauberer ist! Daher ist die Verwendung innerhalb desselben Stapelrahmens kaum dasselbe wie die Verwendung von gotos.

Die dritte Quelle der Verwirrung betrifft, ob es sich um aktivierte oder nicht aktivierte Ausnahmen handelt. Natürlich scheinen ungeprüfte Ausnahmen besonders schrecklich für den Kontrollfluss und vielleicht viele andere Dinge zu sein.

Überprüfte Ausnahmen eignen sich jedoch hervorragend für den Kontrollfluss, sobald Sie alle viktorianischen Probleme überwunden haben und ein wenig leben.

Meine Lieblingsverwendung ist eine Sequenz throw new Success()in einem langen Codefragment, die eine Sache nach der anderen versucht, bis sie findet, wonach sie sucht. Jedes Ding - jedes Stück Logik - kann eine willkürliche Verschachtelung haben, so dass breakes auch keine Art von Bedingungstests gibt. Das if-elseMuster ist spröde. Wenn ich eine herausschneideelse Syntax oder auf andere Weise durcheinander bringe, liegt ein haariger Fehler vor.

Die Verwendung von throw new Success() linearisiert den Codefluss. Ich verwende lokal definierte SuccessKlassen - natürlich aktiviert - damit der Code nicht kompiliert wird, wenn ich vergesse, ihn abzufangen. Und ich fange keine anderen Methoden Success.

Manchmal prüft mein Code nach einer Sache nach der anderen und ist nur dann erfolgreich, wenn alles in Ordnung ist. In diesem Fall habe ich eine ähnliche Linearisierung mit throw new Failure().

Die Verwendung einer separaten Funktion beeinträchtigt den natürlichen Grad der Unterteilung. Die returnLösung ist also nicht optimal. Aus kognitiven Gründen bevorzuge ich ein oder zwei Seiten Code an einem Ort. Ich glaube nicht an ultrafeinen Code.

Was JVMs oder Compiler tun, ist für mich weniger relevant, es sei denn, es gibt einen Hotspot. Ich kann nicht glauben, dass es einen fundamentalen Grund für Compiler gibt, lokal ausgelöste und abgefangene Ausnahmen nicht zu erkennen und sie einfach als sehr effizient gotoauf der Ebene des Maschinencodes zu behandeln.

In Bezug auf die funktionsübergreifende Verwendung für den Kontrollfluss - dh für häufige und nicht für außergewöhnliche Fälle - kann ich nicht erkennen, wie sie weniger effizient sind als mehrere Unterbrechungen, Bedingungstests und die Rückkehr zum Durchlaufen von drei Stapelrahmen im Gegensatz zur einfachen Wiederherstellung der Stapelzeiger.

Ich persönlich verwende das Muster nicht für Stapelrahmen und ich kann sehen, wie es Design-Raffinesse erfordern würde, um dies elegant zu tun. Aber sparsam eingesetzt sollte es in Ordnung sein.

In Bezug auf überraschende jungfräuliche Programmierer ist dies kein zwingender Grund. Wenn Sie sie sanft in die Praxis einführen, werden sie lernen, sie zu lieben. Ich erinnere mich, dass C ++ C-Programmierer überrascht und erschreckt hat.

Nekromant
quelle
3
Mit diesem Muster haben die meisten meiner Grobfunktionen am Ende zwei kleine Fänge - einen für Erfolg und einen für Fehler. Dort fasst die Funktion Dinge wie die Vorbereitung der richtigen Servlet-Antwort oder die Vorbereitung von Rückgabewerten zusammen. Es ist schön, einen einzigen Ort für die Zusammenfassung zu haben. Die returnMuster-Alternative würde zwei Funktionen für jede solche Funktion erfordern. Eine äußere, um die Servlet-Antwort oder andere solche Aktionen vorzubereiten, und eine innere, um die Berechnung durchzuführen. PS: Ein englischer Professor würde wahrscheinlich vorschlagen, dass ich im letzten Absatz "erstaunlich" statt "überraschend" verwende :-)
Nekromant
11

Die Standardantwort lautet, dass Ausnahmen nicht regelmäßig sind und in Ausnahmefällen verwendet werden sollten.

Ein für mich wichtiger Grund ist, dass ich beim Lesen einer try-catchKontrollstruktur in einer von mir verwalteten oder debuggenden Software versuche herauszufinden, warum der ursprüngliche Codierer eine Ausnahmebehandlung anstelle einer if-elseStruktur verwendet hat. Und ich erwarte eine gute Antwort.

Denken Sie daran, dass Sie Code nicht nur für den Computer, sondern auch für andere Codierer schreiben. Einem Ausnahmebehandler ist eine Semantik zugeordnet, die Sie nicht wegwerfen können, nur weil es der Maschine nichts ausmacht.

Mouviciel
quelle
Eine unterschätzte Antwort, denke ich. Der Computer wird möglicherweise nicht viel langsamer, wenn eine Ausnahme verschluckt wird. Wenn ich jedoch an einem anderen Code arbeite und darauf stoße, bleibt ich auf meinen Spuren stehen, während ich trainiere, wenn ich etwas Wichtiges verpasst habe, das ich nicht getan habe Ich weiß nicht, oder ob es tatsächlich keine Rechtfertigung für die Verwendung dieses Anti-Musters gibt.
Tim Abell
9

Wie wäre es mit Leistung? Während des Lasttests einer .NET-Webanwendung haben wir 100 simulierte Benutzer pro Webserver erreicht, bis wir eine häufig auftretende Ausnahme behoben haben und diese Anzahl auf 500 Benutzer gestiegen ist.

James Koch
quelle
8

Josh Bloch beschäftigt sich ausführlich mit diesem Thema in Effective Java. Seine Vorschläge sind aufschlussreich und sollten auch für .Net gelten (mit Ausnahme der Details).

Insbesondere sollten Ausnahmen für außergewöhnliche Umstände verwendet werden. Die Gründe hierfür liegen hauptsächlich in der Benutzerfreundlichkeit. Damit eine bestimmte Methode maximal verwendbar ist, sollten ihre Eingabe- und Ausgabebedingungen maximal eingeschränkt sein.

Beispielsweise ist die zweite Methode einfacher zu verwenden als die erste:

/**
 * Adds two positive numbers.
 *
 * @param addend1 greater than zero
 * @param addend2 greater than zero
 * @throws AdditionException if addend1 or addend2 is less than or equal to zero
 */
int addPositiveNumbers(int addend1, int addend2) throws AdditionException{
  if( addend1 <= 0 ){
     throw new AdditionException("addend1 is <= 0");
  }
  else if( addend2 <= 0 ){
     throw new AdditionException("addend2 is <= 0");
  }
  return addend1 + addend2;
}

/**
 * Adds two positive numbers.
 *
 * @param addend1 greater than zero
 * @param addend2 greater than zero
 */
public int addPositiveNumbers(int addend1, int addend2) {
  if( addend1 <= 0 ){
     throw new IllegalArgumentException("addend1 is <= 0");
  }
  else if( addend2 <= 0 ){
     throw new IllegalArgumentException("addend2 is <= 0");
  }
  return addend1 + addend2;
}

In beiden Fällen müssen Sie überprüfen, ob der Anrufer Ihre API ordnungsgemäß verwendet. Aber im zweiten Fall benötigen Sie es (implizit). Die weichen Ausnahmen werden weiterhin ausgelöst, wenn der Benutzer das Javadoc nicht gelesen hat, aber:

  1. Sie müssen es nicht dokumentieren.
  2. Sie müssen nicht darauf testen (abhängig davon, wie aggressiv Ihre Unit-Test-Strategie ist).
  3. Der Anrufer muss nicht drei Anwendungsfälle bearbeiten.

Der bodennahe Punkt ist, dass Ausnahmen nicht sollten als Rückkehrcodes verwendet werden sollten, hauptsächlich weil Sie nicht nur IHRE API, sondern auch die API des Anrufers kompliziert haben.

Das Richtige zu tun ist natürlich mit Kosten verbunden. Die Kosten sind, dass jeder verstehen muss, dass er die Dokumentation lesen und befolgen muss. Hoffentlich ist das sowieso der Fall.

jasonnerothin
quelle
7

Ich denke, dass Sie Ausnahmen für die Flusskontrolle verwenden können. Es gibt jedoch eine Kehrseite dieser Technik. Das Erstellen von Ausnahmen ist eine kostspielige Sache, da sie einen Stack-Trace erstellen müssen. Wenn Sie also Ausnahmen häufiger verwenden möchten, als nur eine Ausnahmesituation zu signalisieren, müssen Sie sicherstellen, dass das Erstellen der Stapelspuren Ihre Leistung nicht negativ beeinflusst.

Der beste Weg, um die Kosten für das Erstellen von Ausnahmen zu senken, besteht darin, die fillInStackTrace () -Methode wie folgt zu überschreiben:

public Throwable fillInStackTrace() { return this; }

In einer solchen Ausnahme werden keine Stacktraces ausgefüllt.

paweloque
quelle
Die Stapelverfolgung erfordert auch, dass der Aufrufer alle Throwables im Stapel "kennt" (dh von ihnen abhängig ist). Das ist eine schlechte Sache. Auslösen von Ausnahmen, die der Abstraktionsebene entsprechen (ServiceExceptions in Services, DaoExceptions von Dao-Methoden usw.). Übersetzen Sie einfach, wenn nötig.
Jasonnerothin
5

Ich sehe nicht wirklich, wie Sie den Programmfluss in dem von Ihnen zitierten Code steuern. Neben der ArgumentOutOfRange-Ausnahme wird keine weitere Ausnahme angezeigt. (Ihre zweite Fangklausel wird also niemals getroffen). Sie verwenden lediglich einen äußerst kostspieligen Wurf, um eine if-Anweisung nachzuahmen.

Außerdem führen Sie nicht die unheimlicheren Operationen aus, bei denen Sie nur eine Ausnahme auslösen, damit sie an einer anderen Stelle abgefangen wird, um die Flusskontrolle durchzuführen. Sie bearbeiten tatsächlich einen Ausnahmefall.

Jason Punyon
quelle
5

Hier sind die Best Practices, die ich in meinem Blogbeitrag beschrieben habe :

  • Lösen Sie eine Ausnahme aus, um eine unerwartete Situation in Ihrer Software anzuzeigen.
  • Verwenden Sie Rückgabewerte für die Eingabevalidierung .
  • Wenn Sie wissen, wie Sie mit Ausnahmen umgehen, die eine Bibliothek auslöst, fangen Sie sie auf der niedrigstmöglichen Ebene ab .
  • Wenn Sie eine unerwartete Ausnahme haben, verwerfen Sie den aktuellen Vorgang vollständig. Tu nicht so, als wüsstest du, wie du mit ihnen umgehen sollst .
Vladimir
quelle
4

Da der Code schwer zu lesen ist, können Probleme beim Debuggen auftreten. Sie werden nach langer Zeit neue Fehler beim Beheben von Fehlern einführen. Er ist in Bezug auf Ressourcen und Zeit teurer und nervt Sie, wenn Sie Ihren Code und debuggen der Debugger stoppt beim Auftreten jeder Ausnahme;)

Gambrinus
quelle
4

Abgesehen von den genannten Gründen besteht ein Grund, keine Ausnahmen für die Flusskontrolle zu verwenden, darin, dass dies den Debugging-Prozess erheblich erschweren kann.

Wenn ich beispielsweise versuche, einen Fehler in VS aufzuspüren, aktiviere ich normalerweise "Alle Ausnahmen unterbrechen". Wenn Sie Ausnahmen für die Flusskontrolle verwenden, werde ich den Debugger regelmäßig einschalten und diese nicht außergewöhnlichen Ausnahmen so lange ignorieren müssen, bis ich zum eigentlichen Problem komme. Dies wird wahrscheinlich jemanden verrückt machen !!

Sean
quelle
1
Ich habe diesen einen Higer bereits behandelt: Debuggen bei allen Ausnahmen: Das gleiche gilt nur, weil das Design, keine Ausnahmen zu verwenden, üblich ist. Meine Frage war: Warum ist das überhaupt üblich?
Peter
Ist Ihre Antwort also im Grunde "Es ist schlecht, weil Visual Studio diese eine Funktion hat ..."? Ich programmiere jetzt seit ungefähr 20 Jahren und habe nicht einmal bemerkt, dass es eine Option "Pause bei allen Ausnahmen" gibt. Trotzdem "wegen dieser einen Funktion!" klingt nach einem schwachen Grund. Verfolgen Sie die Ausnahme einfach bis zu ihrer Quelle. Hoffentlich verwenden Sie eine Sprache, die dies einfach macht. Andernfalls liegt Ihr Problem in den Sprachfunktionen und nicht in der allgemeinen Verwendung von Ausnahmen.
Loduwijk
3

Nehmen wir an, Sie haben eine Methode, die einige Berechnungen durchführt. Es gibt viele Eingabeparameter, die validiert werden müssen, um eine Zahl größer als 0 zurückzugeben.

Die Verwendung von Rückgabewerten zur Signalisierung des Validierungsfehlers ist einfach: Wenn die Methode eine Zahl kleiner als 0 zurückgibt, ist ein Fehler aufgetreten. Wie kann man dann feststellen, welcher Parameter nicht validiert wurde?

Ich erinnere mich an meine C-Tage, als viele Funktionen folgende Fehlercodes zurückgaben:

-1 - x lesser then MinX
-2 - x greater then MaxX
-3 - y lesser then MinY

etc.

Ist es wirklich weniger lesbar als eine Ausnahme zu werfen und zu fangen?

Kender
quelle
Deshalb haben sie Enums erfunden :) Aber magische Zahlen sind ein ganz anderes Thema. de.wikipedia.org/wiki/…
Isak Savo
Tolles Beispiel. Ich wollte das Gleiche schreiben. @IsakSavo: Aufzählungen sind in dieser Situation nicht hilfreich, wenn von der Methode erwartet wird, dass sie einen Bedeutungswert oder ein Objekt zurückgibt. Beispielsweise sollte getAccountBalance () ein Money-Objekt zurückgeben, kein AccountBalanceResultEnum-Objekt. Viele C-Programme haben ein ähnliches Muster, bei dem ein Sentinel-Wert (0 oder null) einen Fehler darstellt. Anschließend müssen Sie eine andere Funktion aufrufen, um einen separaten Fehlercode abzurufen und festzustellen, warum der Fehler aufgetreten ist. (Die MySQL C API ist wie folgt.)
Mark E. Haase
3

Sie können eine Hammerklaue verwenden, um eine Schraube zu drehen, genauso wie Sie Ausnahmen für den Kontrollfluss verwenden können. Dies bedeutet nicht, dass es sich um die beabsichtigte Verwendung der Funktion handelt. Die ifAnweisung drückt Bedingungen aus, deren beabsichtigte Verwendung die Steuerung des Flusses ist.

Wenn Sie eine Funktion unbeabsichtigt verwenden, während Sie sich dafür entscheiden, die für diesen Zweck vorgesehene Funktion nicht zu verwenden, fallen Kosten an. In diesem Fall leiden Klarheit und Leistung unter keinem wirklichen Mehrwert. Was bringt Ihnen die Verwendung von Ausnahmen gegenüber der allgemein akzeptierten ifAussage?

Anders gesagt: Nur weil du kannst, heißt das nicht, dass du es solltest .

Bryan Watts
quelle
1
Wollen Sie damit sagen, dass keine Ausnahme erforderlich ist, nachdem wir iffür den normalen Gebrauch oder die Verwendung von Ausführungen nicht beabsichtigt sind, weil dies nicht beabsichtigt ist (Zirkelargument)?
Val
1
@Val: Ausnahmen gelten für Ausnahmesituationen. Wenn wir genug erkennen, um eine Ausnahme auszulösen und zu behandeln, verfügen wir über genügend Informationen, um sie nicht auszulösen und dennoch zu behandeln. Wir können direkt zur Handhabungslogik übergehen und den teuren, überflüssigen Versuch / Fang überspringen.
Bryan Watts
Nach dieser Logik können Sie genauso gut keine Ausnahmen haben und immer einen System-Exit durchführen, anstatt eine Ausnahme auszulösen. Wenn Sie vor dem Beenden etwas tun möchten, erstellen Sie einen Wrapper und rufen Sie diesen auf. Java-Beispiel: public class ExitHelper{ public static void cleanExit() { cleanup(); System.exit(1); } }Rufen Sie das einfach auf, anstatt zu werfen: ExitHelper.cleanExit();Wenn Ihr Argument stichhaltig wäre, wäre dies der bevorzugte Ansatz und es gäbe keine Ausnahmen. Sie sagen im Grunde: "Der einzige Grund für Ausnahmen ist ein anderer Absturz."
Loduwijk
@ Aaron: Wenn ich die Ausnahme sowohl auslöse als auch fange, habe ich genug Informationen, um dies zu vermeiden. Das bedeutet nicht, dass alle Ausnahmen plötzlich tödlich sind. Anderer Code, den ich nicht kontrolliere, könnte ihn abfangen, und das ist in Ordnung. Mein Argument, das stichhaltig bleibt, ist, dass das Auslösen und Abfangen einer Ausnahme im selben Kontext überflüssig ist. Ich habe auch nicht angegeben, dass alle Ausnahmen den Prozess beenden sollten.
Bryan Watts
@BryanWatts bestätigt. Viele andere haben gesagt, dass Sie Ausnahmen nur für alles verwenden sollten, von dem Sie sich nicht erholen können, und dass Sie bei Ausnahmen immer abstürzen sollten. Deshalb ist es schwierig, diese Dinge zu diskutieren; Es gibt nicht nur 2 Meinungen, sondern viele. Ich bin immer noch nicht einverstanden mit Ihnen, aber nicht stark. Es gibt Zeiten, in denen das gemeinsame Werfen / Fangen der am besten lesbare, wartbarste und am besten aussehende Code ist. Normalerweise geschieht dies, wenn Sie bereits andere Ausnahmen abfangen, also bereits try / catch haben und das Hinzufügen von 1 oder 2 weiteren Fängen sauberer ist als separate Fehlerprüfungen durch if.
Loduwijk
3

Wie andere bereits zahlreich erwähnt haben, verbietet das Prinzip des geringsten Erstaunens , dass Sie Ausnahmen übermäßig nur für Kontrollflusszwecke verwenden. Andererseits ist keine Regel zu 100% korrekt, und es gibt immer Fälle, in denen eine Ausnahme "genau das richtige Werkzeug" ist - gotoübrigens ähnlich wie sie selbst, die in Form von breakund continuein Sprachen wie Java geliefert wird sind oft der perfekte Weg, um aus stark verschachtelten Schleifen zu springen, die nicht immer vermeidbar sind.

Der folgende Blog-Beitrag erklärt einen ziemlich komplexen, aber auch ziemlich interessanten Anwendungsfall für einen Nicht-Lokalen ControlFlowException :

Es wird erklärt, wie innerhalb von jOOQ (einer SQL-Abstraktionsbibliothek für Java) solche Ausnahmen gelegentlich verwendet werden, um den SQL-Rendering-Prozess frühzeitig abzubrechen, wenn eine "seltene" Bedingung erfüllt ist.

Beispiele für solche Bedingungen sind:

  • Es sind zu viele Bindungswerte aufgetreten. Einige Datenbanken unterstützen keine beliebige Anzahl von Bindungswerten in ihren SQL-Anweisungen (SQLite: 999, Ingres 10.1.0: 1024, Sybase ASE 15.5: 2000, SQL Server 2008: 2100). In diesen Fällen bricht jOOQ die SQL-Rendering-Phase ab und rendert die SQL-Anweisung mit eingebetteten Bindungswerten neu. Beispiel:

    // Pseudo-code attaching a "handler" that will
    // abort query rendering once the maximum number
    // of bind values was exceeded:
    context.attachBindValueCounter();
    String sql;
    try {
    
      // In most cases, this will succeed:
      sql = query.render();
    }
    catch (ReRenderWithInlinedVariables e) {
      sql = query.renderWithInlinedBindValues();
    }

    Wenn wir die Bindungswerte explizit aus dem Abfrage-AST extrahieren, um sie jedes Mal zu zählen, würden wir wertvolle CPU-Zyklen für die 99,9% der Abfragen verschwenden, die nicht unter diesem Problem leiden.

  • Einige Logikfunktionen sind nur indirekt über eine API verfügbar, die nur "teilweise" ausgeführt werden soll. Die UpdatableRecord.store()Methode generiert abhängig von den internen Flags eine Anweisung INSERToder . Von außen wissen wir nicht, in welcher Art von Logik enthalten ist (z. B. optimistisches Sperren, Behandlung von Ereignis-Listenern usw.), daher möchten wir diese Logik nicht wiederholen, wenn wir mehrere Datensätze in einer Batch-Anweisung speichern. wo wir nur die SQL-Anweisung generieren möchten, nicht tatsächlich ausführen. Beispiel:UPDATERecordstore()store()

    // Pseudo-code attaching a "handler" that will
    // prevent query execution and throw exceptions
    // instead:
    context.attachQueryCollector();
    
    // Collect the SQL for every store operation
    for (int i = 0; i < records.length; i++) {
      try {
        records[i].store();
      }
    
      // The attached handler will result in this
      // exception being thrown rather than actually
      // storing records to the database
      catch (QueryCollectorException e) {
    
        // The exception is thrown after the rendered
        // SQL statement is available
        queries.add(e.query());                
      }
    }

    Wenn wir die store()Logik in eine "wiederverwendbare" API ausgelagert hätten, die so angepasst werden kann, dass die SQL optional nicht ausgeführt wird, würden wir versuchen, eine ziemlich schwer zu wartende, kaum wiederverwendbare API zu erstellen.

Fazit

Im Wesentlichen entspricht unsere Verwendung dieser nicht lokalen gotos genau dem, was [Mason Wheeler] [5] in seiner Antwort gesagt hat:

"Ich bin gerade auf eine Situation gestoßen, mit der ich derzeit nicht richtig umgehen kann, weil ich nicht genug Kontext habe, um damit umzugehen, aber die Routine, die mich angerufen hat (oder etwas weiter oben im Aufrufstapel), sollte wissen, wie ich damit umgehen soll . "

Beide Verwendungen von ControlFlowExceptions waren im Vergleich zu ihren Alternativen recht einfach zu implementieren, sodass wir eine breite Palette von Logik wiederverwenden konnten, ohne sie aus den relevanten Interna heraus zu überarbeiten.

Das Gefühl, dass dies für zukünftige Betreuer eine kleine Überraschung ist, bleibt jedoch bestehen. Der Code fühlt sich ziemlich empfindlich an, und obwohl dies in diesem Fall die richtige Wahl war, würden wir es immer vorziehen, keine Ausnahmen für den lokalen Kontrollfluss zu verwenden, bei denen es einfach ist, die Verwendung einer normalen Verzweigung zu vermeiden if - else.

Lukas Eder
quelle
2

Normalerweise ist an sich nichts falsch daran, eine Ausnahme auf niedrigem Niveau zu behandeln. Eine Ausnahme ist eine gültige Nachricht, die viele Details darüber enthält, warum eine Operation nicht ausgeführt werden kann. Und wenn Sie damit umgehen können, sollten Sie es tun.

Wenn Sie wissen, dass es eine hohe Ausfallwahrscheinlichkeit gibt, auf die Sie prüfen können, sollten Sie im Allgemeinen die Prüfung durchführen ... dh wenn (obj! = Null) obj.method ()

In Ihrem Fall bin ich mit der C # -Bibliothek nicht vertraut genug, um zu wissen, ob die Datums- und Uhrzeitzeit eine einfache Möglichkeit bietet, zu überprüfen, ob ein Zeitstempel außerhalb der Grenzen liegt. Wenn dies der Fall ist, rufen Sie einfach if (.isvalid (ts)) auf, andernfalls ist Ihr Code grundsätzlich in Ordnung.

Im Grunde kommt es also darauf an, auf welche Weise sauberer Code erstellt wird ... wenn die Operation zum Schutz vor einer erwarteten Ausnahme komplexer ist als nur die Behandlung der Ausnahme; Dann haben Sie meine Erlaubnis, die Ausnahme zu behandeln, anstatt überall komplexe Wachen zu erstellen.


quelle
Zusätzlicher Punkt: Wenn Ihre Ausnahme Informationen zur Fehlererfassung enthält (ein Getter wie "Param getWhatParamMessedMeUp ()"), kann dies dem Benutzer Ihrer API helfen, eine gute Entscheidung darüber zu treffen, was als nächstes zu tun ist. Andernfalls geben Sie einem Fehlerstatus nur einen Namen.
Jasonnerothin
2

Wenn Sie Ausnahmebehandlungsroutinen für den Kontrollfluss verwenden, sind Sie zu allgemein und faul. Wie bereits erwähnt, wissen Sie, dass etwas passiert ist, wenn Sie die Verarbeitung im Handler übernehmen, aber was genau? Im Wesentlichen verwenden Sie die Ausnahme für eine else-Anweisung, wenn Sie sie für den Kontrollfluss verwenden.

Wenn Sie nicht wissen, welcher mögliche Status auftreten kann, können Sie einen Ausnahmebehandler für unerwartete Status verwenden, z. B. wenn Sie eine Bibliothek eines Drittanbieters verwenden müssen oder wenn Sie alles in der Benutzeroberfläche abfangen müssen, um einen netten Fehler anzuzeigen Nachricht und protokollieren Sie die Ausnahme.

Wenn Sie jedoch wissen, was möglicherweise schief geht, und keine if-Anweisung oder etwas anderes eingeben, um dies zu überprüfen, sind Sie nur faul. Es ist faul, dem Ausnahmehandler zu erlauben, das Allheilmittel für Dinge zu sein, von denen Sie wissen, dass sie passieren könnten, und es wird Sie später verfolgen, da Sie versuchen werden, eine Situation in Ihrem Ausnahmehandler aufgrund einer möglicherweise falschen Annahme zu beheben.

Wenn Sie Logik in Ihren Ausnahmebehandler einfügen, um festzustellen, was genau passiert ist, wären Sie ziemlich dumm, wenn Sie diese Logik nicht in den try-Block einfügen würden.

Ausnahmebehandlungsroutinen sind der letzte Ausweg, wenn Ihnen die Ideen / Möglichkeiten ausgehen, um zu verhindern, dass etwas schief geht, oder wenn Dinge außerhalb Ihrer Kontrollmöglichkeiten liegen. Der Server ist ausgefallen und es tritt eine Zeitüberschreitung auf, und Sie können nicht verhindern, dass diese Ausnahme ausgelöst wird.

Wenn alle Überprüfungen im Voraus durchgeführt werden, zeigt dies, was Sie wissen oder erwarten, und macht es explizit. Der Code sollte in seiner Absicht klar sein. Was würdest du lieber lesen?


quelle
1
Überhaupt nicht wahr: "Im Wesentlichen verwenden Sie die Ausnahme für eine else-Anweisung, wenn Sie sie für den Kontrollfluss verwenden." Wenn Sie sie für den Kontrollfluss verwenden, wissen Sie genau, was Sie fangen, und verwenden niemals einen allgemeinen Fang, sondern a spezifisch natürlich!
Peter
2

Vielleicht möchten Sie einen Blick auf das Zustandssystem von Common Lisp werfen, das eine Art Verallgemeinerung von Ausnahmen darstellt, die richtig gemacht wurden. Da Sie den Stapel auf kontrollierte Weise abwickeln können oder nicht, erhalten Sie auch "Neustarts", die äußerst praktisch sind.

Dies hat nicht viel mit Best Practices in anderen Sprachen zu tun, zeigt Ihnen jedoch, was mit einigen Designgedanken in (ungefähr) der Richtung, in die Sie denken, getan werden kann.

Natürlich gibt es immer noch Leistungsüberlegungen, wenn Sie wie ein Jojo auf und ab hüpfen, aber es ist eine viel allgemeinere Idee als der Ansatz "Oh Mist, lass uns auf Kaution gehen", den die meisten Catch / Throw-Ausnahmesysteme verkörpern.

Simon
quelle
2

Ich glaube nicht, dass etwas falsch daran ist, Ausnahmen für die Flusskontrolle zu verwenden. Ausnahmen ähneln Fortsetzungen und in statisch typisierten Sprachen sind Ausnahmen leistungsfähiger als Fortsetzungen. Wenn Sie also Fortsetzungen benötigen, Ihre Sprache diese jedoch nicht hat, können Sie sie mit Ausnahmen implementieren.

Nun, wenn Sie Fortsetzungen benötigen und Ihre Sprache diese nicht hat, haben Sie die falsche Sprache gewählt und sollten lieber eine andere verwenden. Aber manchmal haben Sie keine Wahl: Die clientseitige Webprogrammierung ist das beste Beispiel - es gibt einfach keine Möglichkeit, an JavaScript vorbei zu kommen.

Ein Beispiel: Microsoft Volta ist ein Projekt, mit dem Webanwendungen in unkompliziertem .NET geschrieben werden können und mit dem das Framework herausfinden soll, welche Bits wo ausgeführt werden müssen. Eine Folge davon ist, dass Volta in der Lage sein muss, CIL zu JavaScript zu kompilieren, damit Sie Code auf dem Client ausführen können. Es gibt jedoch ein Problem: .NET verfügt über Multithreading, JavaScript nicht. Daher implementiert Volta Fortsetzungen in JavaScript mithilfe von JavaScript-Ausnahmen und anschließend .NET-Threads mithilfe dieser Fortsetzungen. Auf diese Weise können Volta-Anwendungen, die Threads verwenden, so kompiliert werden, dass sie in einem unveränderten Browser ausgeführt werden - Silverlight ist nicht erforderlich.

Jörg W Mittag
quelle
2

Ein ästhetischer Grund:

Ein Versuch ist immer mit einem Haken verbunden, während ein Wenn nicht mit einem anderen verbunden sein muss.

if (PerformCheckSucceeded())
   DoSomething();

Mit try / catch wird es viel ausführlicher.

try
{
   PerformCheckSucceeded();
   DoSomething();
}
catch
{
}

Das sind 6 Codezeilen zu viele.

gzak
quelle
1

Sie werden jedoch nicht immer wissen, was in den von Ihnen aufgerufenen Methoden passiert. Sie wissen nicht genau, wo die Ausnahme ausgelöst wurde. Ohne das Ausnahmeobjekt genauer zu untersuchen ....

Gittergewebe
quelle
1

Ich habe das Gefühl, dass an Ihrem Beispiel nichts falsch ist. Im Gegenteil, es wäre eine Sünde, die von der aufgerufenen Funktion ausgelöste Ausnahme zu ignorieren.

In der JVM ist das Auslösen einer Ausnahme nicht so teuer. Sie erstellen die Ausnahme nur mit der neuen xyzException (...), da letztere einen Stack-Walk umfasst. Wenn Sie also einige Ausnahmen im Voraus erstellt haben, können Sie diese viele Male ohne Kosten auslösen. Auf diese Weise können Sie natürlich keine Daten mit der Ausnahme weitergeben, aber ich denke, das ist sowieso eine schlechte Sache.

Ingo
quelle
Entschuldigung, das ist einfach falsch, Brann. Es kommt auf den Zustand an. Es ist nicht immer so, dass die Bedingung trivial ist. Daher kann eine if-Anweisung Stunden, Tage oder sogar länger dauern.
Ingo
In der JVM also. Nicht teurer als eine Rückgabe. Stelle dir das vor. Aber die Frage ist, was würden Sie in die if-Anweisung schreiben, wenn nicht genau der Code, der bereits in der aufgerufenen Funktion vorhanden ist, um einen Ausnahmefall von einem normalen zu unterscheiden - also Codeduplizierung.
Ingo
1
Ingo: Eine Ausnahmesituation ist eine, die Sie nicht erwarten. dh eine, die Sie als Programmierer nicht gesehen haben. Meine Regel lautet also "Code schreiben, der keine Ausnahmen auslöst" :)
Brann
1
Ich schreibe nie Ausnahmebehandlungsroutinen, ich behebe das Problem immer (außer wenn ich das nicht kann, weil ich keine Kontrolle über den fehlerhaften Code habe). Und ich werfe niemals Ausnahmen, außer wenn der von mir geschriebene Code für eine andere Person bestimmt ist (z. B. eine Bibliothek). Nein, zeig mir den Widerspruch?
Brann
1
Ich stimme Ihnen zu, Ausnahmen nicht wild zu werfen. Aber was "außergewöhnlich" ist, ist sicherlich eine Frage der Definition. Dass String.parseDouble eine Ausnahme auslöst, wenn es beispielsweise kein nützliches Ergebnis liefern kann, ist in Ordnung. Was soll es sonst noch tun? NaN zurückgeben? Was ist mit Nicht-IEEE-Hardware?
Ingo
1

Es gibt einige allgemeine Mechanismen, über die eine Sprache das Beenden einer Methode ermöglichen kann, ohne einen Wert zurückzugeben und zum nächsten "catch" -Block abzuwickeln:

  • Lassen Sie die Methode den Stapelrahmen untersuchen, um die Aufrufstelle zu bestimmen, und verwenden Sie die Metadaten für die Aufrufstelle, um entweder Informationen zu einem tryBlock innerhalb der aufrufenden Methode oder den Ort zu finden, an dem die aufrufende Methode die Adresse ihres Aufrufers gespeichert hat. Überprüfen Sie in letzterem Fall die Metadaten, die der Anrufer des Anrufers auf dieselbe Weise wie der unmittelbare Anrufer ermittelt, und wiederholen Sie diese, bis ein tryBlock gefunden wird oder der Stapel leer ist. Dieser Ansatz fügt dem Fall ohne Ausnahme nur sehr wenig Overhead hinzu (er schließt einige Optimierungen aus), ist jedoch teuer, wenn eine Ausnahme auftritt.

  • Lassen Sie die Methode ein "verstecktes" Flag zurückgeben, das eine normale Rückgabe von einer Ausnahme unterscheidet, und lassen Sie den Aufrufer dieses Flag überprüfen und zu einer "Ausnahme" -Routine verzweigen, wenn es gesetzt ist. Diese Routine fügt dem Fall ohne Ausnahme 1-2 Anweisungen hinzu, aber relativ wenig Aufwand, wenn eine Ausnahme auftritt.

  • Lassen Sie den Anrufer Informationen oder Code zur Ausnahmebehandlung an einer festen Adresse relativ zur gestapelten Rücksprungadresse platzieren. Beispielsweise könnte man mit dem ARM anstelle der Anweisung "BL-Subroutine" die folgende Sequenz verwenden:

        adr lr,next_instr
        b subroutine
        b handle_exception
    next_instr:
    

Um normal zu beenden, würde das Unterprogramm einfach tun bx lroder pop {pc}; Im Falle eines abnormalen Austritts würde das Unterprogramm entweder 4 von LR subtrahieren, bevor die Rückgabe durchgeführt oder verwendet wirdsub lr,#4,pc (abhängig von der ARM-Variation, dem Ausführungsmodus usw.). Dieser Ansatz funktioniert sehr schlecht, wenn der Anrufer nicht dafür ausgelegt ist.

Eine Sprache oder ein Framework, das aktivierte Ausnahmen verwendet, kann davon profitieren, dass diese mit einem Mechanismus wie oben Nr. 2 oder Nr. 3 behandelt werden, während nicht aktivierte Ausnahmen mit Nr. 1 behandelt werden. Obwohl die Implementierung von geprüften Ausnahmen in Java ziemlich lästig ist, wären sie kein schlechtes Konzept, wenn es ein Mittel gäbe, mit dem eine Aufrufstelle im Wesentlichen sagen könnte: "Diese Methode wird als Auslösen von XX deklariert, aber ich erwarte es nicht Wenn dies der Fall ist, wird dies als "ungeprüfte" Ausnahme erneut ausgelöst. In einem Rahmen, in dem geprüfte Ausnahmen auf diese Weise behandelt wurden, könnten sie ein wirksames Mittel zur Flusskontrolle für Dinge wie Analysemethoden sein, die in einigen Kontexten eine haben können hohe Wahrscheinlichkeit eines Misserfolgs, aber wo ein Misserfolg grundlegend andere Informationen als der Erfolg zurückgeben sollte. I ' Ich kenne jedoch keine Frameworks, die ein solches Muster verwenden. Stattdessen besteht das üblichere Muster darin, für alle Ausnahmen den ersten Ansatz oben zu verwenden (minimale Kosten für den Fall ohne Ausnahme, aber hohe Kosten, wenn Ausnahmen ausgelöst werden).

Superkatze
quelle