Ein Gutes Exception Beispiel
 
StartSeite | Neues | TestSeite | ForumSeite | Teilnehmer | Kategorien | Index | Hilfe | Einstellungen | Ändern

Es wird behauptet, dass Exceptions viele Vorteile hätten. Mir sind immer nur Nachteile aufgefallen, aber vielleicht fehlt einfach nur EinGutesExceptionBeispiel (natürlich würde ich zu zeigen versuchen, dass es ohne Exceptions genauso gut oder besser geht). -- HelmutLeitner

Was ist denn mit diesen Vorteilen:

Beispiele werden auf Nachfrage geliefert :-) -- JohannesDöbler?

Beispiel: Fehler Lokalisierung mit Ada in großen Projekten -> AdaExceptionHandler

AdaExceptionHandler ist ein schlechtes Bespiel, denn wenn ich es richtig verstehe, geht es dabei um die Aufdeckung von Programmierfehlern. Dafür sind Exceptions nicht gedacht. Exceptions sind dafür gedacht, um den Code zur Behandlung von selten auftretenden Situationen (eben "Ausnahmen") vom Code für den normalen Ablauf trennen zu können. Mit selten auftretenden Situationen sind keine Programmierfehler gemeint, sondern erwartungsgemäß vorkommen könnende Situationen. -- VolkerGlave (jetzt auch noch die richtige Anwendung von Exceptions verteidigen müssend, obwohl Exceptions für defizient haltend; strange world)

Eigentlich war die Seite als Antwort auf deine Frage bei LaufzeitFehler gedacht. Aber vielleicht hilft das ja bei der Frage ob Error-Codes sinnvoll sind weiter. -- HermannWacker


Die vermeintlichen Vorteile scheinen mir eher Nachteile zu sein:

Exceptions können von Entwicklern viel leichter als Error-Returncodes ignoriert werden, da sie sich ja auf den Standpunkt stellen dürfen, die Exceptions würden "auf höherer Ebene" behandelt werden können. Bei Error-Returncodes ist dagegen die Verantwortlichkeit völlig klar, und wenn jemand einen Error-Returncode ignoriert, hat man gleich den Schuldigen.

Wenn eine Exception nicht behandelt wird, bekommt man in der Regel eine Stackauflistung, wo man genau sehen kann, wo der Fehler passiert ist. Wenn man Fehlercodes ignoriert, wird der Fehler erst viel später sichtbar.

Exceptions vermindern die Übersichtlichkeit des Programms, weil die Fehlerbehandlungen nun nicht mehr nah am Ort des Entstehens erfolgen - wo man den Zusammenhang also noch einigermaßen überblicken würde -, sondern (falls die Exceptions nicht eh ignoriert werden) an ganz anderer Stelle, wo der Zusammenhang nicht mehr unmittelbar zu sehen ist. Zudem werden womöglich auch noch massig künstliche Exception-Ableitungshierarchieren erfunden, weil man ja die Fehlerinformation nun im Gegensatz zu früher durchreichen muss ...

Ich behaupte das Gegenteil. Mit Exceptions kann man die Fehler dort behandeln, wo man sie behandeln kann. Mit Returncodes muss man immer daran denken, den Returncode weiterzuleiten, weil dort, wo der Fehler passiert, man gar nicht weiß, wie der Fehler behandelt werden soll.

Soso. An der Stelle, wo der Feher passiert, wo ich also noch exakt die Begleitumstände des Fehlers kenne, weiß ich also nicht, wie der Fehler zu behandeln ist? Und tausend Kilometer weg, wo ich nur noch 'ne mickrige Exception in der Hand habe, da weiß ich plötzlich, wie der Fehler zu behandeln ist? Hui, das überzeugt mich jetzt aber.

Ist aber so. Beispiel: Methode, die eine Datei öffen soll. Die Datei gibt es nicht. Was soll die Methode machen? Wie soll sie den Fehler behandeln?

open() der Klasse fstream aus der Standard-C++-Library ist so eine Funktion, die eine Datei öffnen soll. Hier bei mir sieht die Beschreibung ungefähr so aus "Calls the associated basic_filebuf function open(s,mode,protection) and, if this function fails at opening the file, calls the basic_ios member function setstate(failbit). [...]". Um also deine Frage zu beantworten: Das Objekt, auf dem die gewünchte Operation (hier das Öffnen einer Datei) nicht so recht geklappt hat, wird so markiert, dass dies durch den Nutzer abfragbar ist. Selbstverständlich verhalten sich trotzdem in Folge alle weiteren Methoden, die auf dem Objekt möglich sind, vernünftig und definiert. Wo ist bloss eurer Problem, ich will endlich wissen wofür ihr diese dämlichen Exceptions braucht.

Irgendwie habe ich das Gefühl, dass Du diese Diskussion zu persönlich nimmst. Hier geht es um Software, nicht um die Befreiung Jerusalems. Was aber Deinen Beispiel mit fstream::open() angeht, den kann ich leider nicht akzeptieren. Open erkennt einen Fehlerzustand, behandelt den Fehler aber nicht. Eine Fehlerbehandlung ist sowas wie eine Dialogbox mit der Aufforderung die richtige Diskette einzulegen, oder eine SMS an den Admin schicken, oder einen Eintrag in eine Logdatei machen, oder oder oder. Also nochmal kurz: throw - Fehlerzustand erkennen. catch - Fehler behandeln. fstream::open() kann den Fehler nicht behandeln.

So kommen wir nicht weiter. Zuvor hattest du die Fragen gestellt "Beispiel: Methode, die eine Datei öffen soll. Die Datei gibt es nicht. Was soll die Methode machen? Wie soll sie den Fehler behandeln?". Dieses Fragen werden von fstream::open(), bei der es sich wie gefordert und wie gesagt um eben solch eine Funktion (die eine Datei öffnen soll) handelt, erschöpfend beantwortet. Auch die Frage "Wie soll sie den Fehler behandeln?" gehört dazu, am fstream-Objekt ist abfragbar, was zuvor passiert ist, damit kann nun jeder das machen, was er will (um eine von dir selbst weiter unten gebrauchte Formulierung wiederzuverwenden). Z. B. auch die von dir genannten Behandlungen.

Einen Flag zu setzen ist kein Behandeln eines Fehlers. Du hast ja behauptet, man sollte den Fehler dort behandeln, wo er passiert. Wäre dem so, dann würd man überhaupt keine Fehlerindikatoren brauchen, seien es Exceptions, Fehlercodes oder Stati. In Deiner Antwort auf die Frage, was eine Methode, die eine nicht existierende Datei öffnen soll, hast Du geantwortet, dass das Objekt als fehlerhaft markiert werden kann. Also wird die Fehlerbehandlung aufgeschoben. Dies ist aber keineswegs eine Fehlerbehandlung an der Stelle, wo der Fehler passiert. Die Fehlerbehandlung wird anderswo stattfinden - sei es durch Abfangen einer Exception, durch Auswertung eines Fehlercodes oder Auslesen eines Fehlerstatus. Nur ist dies mit Exceptions halt übersichtlicher und robuster.

Das Behandeln von Ausnahmesituationen ist offenbar ein Thema, in dem du sehr bewandert bist. Ich habe mir spasseshalber mal das Tool UUR von der auf deiner Homepage genannten Firmenhomepage gezogen. In deren readme.txt findet sich ja dein Name. In der Doku heisst es "Es fragt Sie ab und zu, was Sie gerade machen und schreibt Ihre Antwort in eine einfache Textdatei.". Das wollen wir doch mal testen und setzen uur.LOG auf readonly. Dann das Programm starten. Ich werde gefragt, was ich mache. Schön. Ich beende das Programm. Steht die von mir gemachte Antwort wie die Doku behauptet etwa in der Textdatei? Pustekuchen. (Ist aber ok, die Datei war ja readonly.) Bin ich etwa irgendwie darauf hingewiesen worden, dass der Eintrag nicht vorgenommen werden konnte? Ebenfalls Pustekuchen. Wasser predigen, Wein trinken, ein weiteres verbreitetes Charaktermerkmal der Exception-Befürworter.

:-) Ja ja, das UUR-Programm würde ich auf keinen Fall als ein gut geschriebenes Programm bezeichnen. Das Programm hat sich irgendwie "ergeben", innen sieht es ziemlich schlimm aus. Der einzige Grund, warum es UUR noch gibt, ist die Tatsache, dass es ganz nützlich ist, und ich seit Jahren keinen Grund hatte es zu ändern. Wenn es einen Grund gäbe, dann wäre es auch ein Grund, es ganz neu zu schreiben. Übrigens, in UUR werden keine Exceptions verwendet - damals gab es sowas nicht. Lerne also aus meinen Fehlern - benutze Exceptions. Es ist gar nicht so schlimm, wenn Du sie erst kapiert hast. BTW: Bist Du sicher, dass Du nicht lieber Priester oder Greenpeacemitglied oder sowas werden solltest? ;-)

Na, na, na, damals gab es sowas nicht? Immerhin ist es ein 32-bit Programm und in MSVC++ geschrieben, so alt ist es also nicht und Exceptions gab es meiner Erinnerung nach in MSVC++ da sehr wohl schon ... aber egal, lassen wir es dabei, du nutzt lieber Exceptions für Ausnahmebehandlung, ich bleibe dabei, sie für mehr als überflüssig zu halten. (Nutzen muss ich sie übrigens leider dennoch, da ich nicht im Elfenbeinturm lebe ...)

Tja, aber UUR stammmt noch der Zeit von Windows 3.11 und MSVC 1.5. Es auf Win32 zu portieren war wesentlich einfacher als es ganz umzuschreiben. Zeitlang war das Programm sogar für beide Plattformen kompilierbar, aber irgendwann habe ich den Support für 16b Plattformen aufgegeben. Immerhin ist das Programm gut genug, also lohnt sich kein Refactoring. Deine Einsicht, dass Exceptions für jemanden durchaus Sinn machen können überrascht mich - positiv. Ich verstehe aber nicht, wieso Du sie nutzen musst, Du kannst sie doch in Fehlercodes oder Fehlerstati umwandeln.

Die finally-Syntax begünstigt falsch plazierten Aufräum-Code. Aufräum-Arbeiten haben gefälligst über Destruktoren zu erfolgen, so funktioniert es bei C++ jedenfalls richtig, und zwar auch bei Exceptions. (Hab' grad nochmal 'rumgesucht: siehe auch "C++ destructors are useful; Java's finalize is not" ff. auf http://web.archive.org/web/20031224090327/http://www.cs.wisc.edu/~glew/programming-languages.html.) -- VolkerGlave

Das ist aber absurd. Java's finalize ist kein Destruktor wie in C++. In C++ gibt es auch Exceptions, aber kein finally, also kann man dieses Argument kaum in einer Diskussion über Exceptions verwenden. -- GregorRayman

Java hat keinen brauchbaren Ersatz für deterministische Destruktoren wie in C++. Absurd ist lediglich, dass die finally-Syntax regelmäßig als vorgeblicher Ersatz genannt wird. Warum sie keiner ist, hat aber mit Exceptions ziemlich wenig zu tun. Umgekehrt können aber deterministische Destruktoren finally ersetzen.

-- VolkerGlave


Hier, vorweg, ein Beispiel, wie einfach Programme mit Exceptions aussehen können, verglichen mit Programmen, die keine Exceptions haben. Beispiele in Java:

Mit Exceptions
class Hello {
  Hello(String aMessage) {
    this.message = aMessage;
  }
  public void sayHello() {
    System.out.println(message);
  }
  private final String message;
}

irgendwo im Quelltext:

  Hello hello = new Hello(time.getHours() >= 12 ? "Good afternoon world!" : "Good morning world");
  hello.sayHello();

Ohne Exceptions (eine mögliche Variante)
  int hours = time.getHours();
  if (hours < 0) return FAIL_CLOCKERROR;
  Hello hello = new Hello(hours >= 12 ? "Good afternoon world!" : "Good morning world");
  if (null == hello) {
    if (GetSystemError() == ERR_OUT_OF_MEMORY) {
      return ERR_OUT_OF_MEMORY;
    } else {
      return ERR_CLASS_NOT_FOUND;
    }
  }
  err = hello.sayHello();
  if (FAILED(err)) {
     if (err = ERR_SECURITY) {
        ...
     }
   ...
  }
--gR

Sorry, verstehe nicht, warum im oberen Teil "time.getHours() >= 12" steht, im unteren aber nur "time >= 12". Ein Versehen? Bei Exception-Jüngern ist man das zwar gewohnt, trotzdem bitte etwas mehr Sorgfalt.

Weiterhin finde ich, dass, wenn wir hier Exception-behafteten und Exception-losen Code miteinander vergleichen wollen, dann müssen die zu vergleichenden Codeteile aber vom Verhalten her gleich sein. Das ist bei dem gegebenen Beispiel nicht der Fall. Der obere Code gibt beispielsweise "Good morning world" aus, wenn time.getHours() einen Wert kleiner 0 liefert, wohingegen der untere Code es nicht tut. (Wahrscheinlich fehlt mir hier genauere Kenntnis über time.getHours(), aber der Autor hat darüber nun mal leider nichts gesagt ...) -- vgl

Den Tippfehler habe ich bereits korrigiert. time.getHours() ist eine Funktion, die ich mir nur so ausgedacht habe. Sie gibt eben die Anzahl der Stunden einer Uhr zurück. Es macht natürlich keinen Sinn -1 zurückzugeben, denn eine Uhr zeigt ja nie -1, es sei den, man benutzt den Wert -1 als Fehlerindikator. In dem Beispiel mit Exceptions wird dies nich gebraucht.

Ich sehe es so. Es gibt drei Stellen, die von einem Fehler betroffen werden.
1. Die Stelle, wo der Fehler passiert. Bei Exceptions wird hier die Exception geworfen, ohne Exceptions wird ein Fehlercode zurückgegeben.
2. Die Stelle, wo der Fehler behandelt wird. Bei Exceptions wird hier die Exception gefangen und behandelt, ohne Exceptions wird hier der Fehlercode ausgewertet und der Fehler behandelt.
3. Die Stellen dazwischen. Bei Exceptions muss man hier nichts machen, was den Quelltext übersichtlich macht. Ohne Exceptions muss man hier den Fehlercode weiterleiten oder anpassen. Was die Übersichtlichkeit des Code vermindert.

Ich sehe immer noch nicht, dass beide Programme, z. B. wenn time.getHours() fehlschlägt, sich gleich verhalten. Spezifiziere doch bitte (quasi aus Kundensicht), wie sich das Programm in dem Fall (und den übrigen Fehlerfällen, die offenbar vorkommen können) verhalten soll, dann ziehen wir das in beiden Fällen durch, und dann sehen wir ja, welcher Code übersichtlicher ist. Bis jetzt ist jedenfalls noch gar nichts bewiesen. -- vgl

Ist aber drin, in den Quelltexten. Wenn time.getHours() eine Exception wirft, wird diese hier nicht abgefangen und läuft weiter nach oben. Genauso bei der Variante ohne Exceptions. Wenn getHours() -1 liefert, wird die Methode abgebrochen und der Fehler nach oben signalisiert. Der Aufrufer dieser Methode kann dann machen, was er will.



Mit einem Fehler im Programmablauf kann man imho auf zwei sinnvolle Arten umgehen: Exceptions und Error-Returncodes signalisieren nun einen aufgetretenen Fehler und dienen gemäß der zweiten Alternative dazu, den Fehler an einen übergeordneten Kontext weiterzugeben, da er an der aufgetretenen Stelle nicht sinnvoll behandelt werden kann.

Meine Behauptung: Ich halte Exceptions für das mächtigere Ausdrucksmittel, mit dem man die Fehlerbehandlung von Programmen eleganter und besser formulieren kann als mit Returncodes.

Ins Detail (alle Programmbeispiele in einer Pseudosprache):

void write(int i) throws IOException;
boolean write(int i);  // return: write erfolgreich?
Beide Functionen signalisieren, daß write fehlschlagen kann. Der Unterschied: Wenn ich die Exception misachte, macht mich der Compiler darauf aufmerksam, beim Returncode ist es alleine meine Disziplin. Genau das hat mein erster Vorteil ausgedrückt. That's all.

Nicht notwendigerweise. "Wenn ich die Exception misachte, macht mich der Compiler darauf aufmerksam, beim Returncode ist es alleine meine Disziplin.", schön. Aber gehört im ersten Fall - der Compiler meckert - nicht genauso Diziplin dazu, nun nicht try { [...] file.write([...]); [...] } catch(IOException e) {} zu schreiben? Ich frag' ja bloß. -- vgl

mit Returncodes:
File file = new File();
if (!file.open("test.txt"))
{
  message("I/O-Fehler: kann test.txt nicht öffnen");
  return;
}
if (!file.write("name="))
{
  message("IO/Fehler: write fehlgeschlagen");
  file.close();
  return;
}
if (!file.write(name))
{
  message("IO/Fehler: write fehlgeschlagen");
  file.close();
  return;
}
if (!writeContent(file)) // rufe Hilfsmethode
{
  message("IO/Fehler: unbekannter Fehler");
  file.close();
  return;
}
file.close();

mit Exceptions:
File file = new File();
try
{
  file.open();        // throws IOException  
  file.write("name=");// throws IOException 
  file.write(name);   // throws IOException
  writeContent(file); // throws IOException
}
catch(IOException e)
{
  message("IO/Fehler: " + e.getReason());
}
finally
{
  if (file.open())
    file.close();
}

(Jetzt hat auch Helmut sein Beispiel :-))

Was ich illustrieren will: Wenn Returncodes verwendet werden, wird der eigentliche Code und die Fehlerbehandlung gemischt. Mit Exceptions kann ich das sauber trennen. Mit Returncodes bin ich u. U. gezwungen Cleanup-Code zu duplizieren. Mit dem finally-Block vermeide ich das (hat übrigens nichts mit Java's finalize()-Methode zu tun - nur so nebenbei).

"Sauber trennen" läßt sich mit Returncodes für das gegebene Beispiel genauso - wenn nicht besser - erledigen, siehe unten. -- vgl

"finalize" wird von mir oben lediglich als Zitierverweis auf eine Stelle eines externen Artikels verwendet, an der es in erster Linie um Javas finally-Konstrukt geht. Dass im obigen Code file.close(); im finally-Block steht, sehe ich auch eher nicht ein, das sollte doch besser der Destruktor von File übernehmen. Aber ok, bei Java macht man das anscheinend so. -- vgl

Muss man in Java so machen, da es keine Destruktoren gibt. Das hängt damit zusammen, dass Objekte in Java grundsätzlich auf dem Heap abgelegt werden und das Deallozieren implizit (irgendwann, wenn überhaupt) vom GarbageCollector erledigt wird. -- ip

@Volker: Offensichtlich scheinst Du viele Beispiele zu kennen, in denen Exceptions schlecht eingesetzt werden. Kann ich nachvollziehen. Gleiches läßt sich gegen viele ausdrucksstarke syntaktische Mittel vorbringen (bestes Beispiel: objektorientiere Programmiersprachen), widerlegt aber noch nicht meine Behauptung, daß jemand, der die Ausdrucksmittel zu nutzen weiß, damit bessere Programme schreiben kann.

by the way: unter http://www.aztrecrider.com/xslt gibts meinen freien XSLT-Prozessor und 1.5 MB Sourcecode, der meine Ansichten bestens darstellt. -- JohannesDöbler?


Aber gehört im ersten Fall - der Compiler meckert - nicht genauso Diziplin dazu, nun nicht try { [...] file.write([...]); [...] } catch(IOException e) {} zu schreiben?

Ja, dazu gehört auch Disziplin. Ein großer Vorteil ist aber, dass ein leerer catch-Block sehr viel auffälliger ist, als ein ignorierter Rückgabewert. Durch Entfernen eines solchen leeren catch-Blocks habe ich gerade letztens einen Programmierfehler in einem kleinen Hilfsprogramm aufgedeckt (der zu einer bis dato ignorierten ArrayIndexOutOfBoundsException geführt hatte). Der Fehler war bereits Monate unentdeckt geblieben, und ich bezweifle, dass ich ihn gefunden hätte, wenn es sich um einen ignorierten Rückgabewert gehandelt hätte. -- IljaPreuß

Es wird hier (bislang vergeblich, was mich nicht wundert) EinGutesExceptionBeispiel gesucht. Wenn du ein solches hast, stelle es doch bitte vor.

Ja, sobald ich eines habe, dass ich überzeugender finde als das ganz unten, stelle ich es hier vor. Meinem Kommentar zum NullObjekt als Alternative magst Du entnehmen, dass ich in dieser Beziehung selber noch unentschlossen bin. Und solange werde ich meine Beobachtungen gerne mit Interessierten diskutieren - und es schien mir auf dieser Seite durchaus Interesse zu geben.

(Was ist das überhaupt für eine abstruse Einstellung, diesen konkreten wg. ignoriertem Rückgabewert zustandegekommenen Programmierfehler für von dir fast nicht auffindbar zu erklären?) -- vgl

Möglicherweise habe ich mich oben ungenau ausgedrückt: Ich habe den Fehler nicht explizit gesucht, sondern bin darüber "gestolpert" dadurch, dass ich den leeren Catch-Block entfernt habe. Stimmst Du zu, dass ein leerer Catch-Block beim Überfliegen von Code leichter zu sehen ist als ein ignorierter Rückgabewert, und es damit auch wahrscheinlicher ist, dass ein solches Problem entdeckt und behoben wird, oder sind Deine Erfahrungen in dieser Hinsicht anders? -- IljaPreuß

Ignorierte Rückgabewerte sind nicht nur leicht zu sehen (Voraussetzung: der Codeleser kennt in etwa die Funktionen, die genutzt werden, was eine geringe Voraussetzung ist, ansonsten kann der Leser den Code ja eh nicht verstehen), sie sind sogar automatisch auffindbar (LintProgramme: lint, PC-lint). -- vgl

Naja, für das Finden von leeren Catch-Blöcken muss ich den Code noch nicht mal lesen, da reicht ein Überfliegen. Das mit dem automatischen Auffinden ist aber natürlich ein Argument, das nicht von der Hand zu weisen ist, danke für den Hinweis! Wobei es vielleicht nicht *immer* eindeutig etwas schlechtes ist, wenn ein Rückgabewert ignoriert wird, während das für den Catch-Block eigentlich ziemlich grundsätzlich gilt. Der Unterschied mag aber marginal sein... -- ip

Absichtlich ignorierte Rückgabewerte verdeutlicht man halt per vorangestelltem "(void)" (dann meckert lint aucht nicht mehr), so wie man normalerweise absichtlich ignorierte Exceptions halt mit leerem catch-Block verdeutlicht (bei dir wird man ärgerlicherweise noch 'n extra Kommentar hinschreiben müssen, sonst glaubst du noch, man würde vorsätzlich fälschlich ignorieren, und würde bei dir Aufwand produzieren ... ;-) -- vgl

"Sauber trennen" mit Returncodes:

Es sei zugestanden, dass ich das Beispiel in C++ formuliere. Die Notwendigkeit eines expliziten "file.close();" entfällt damit schonmal, das wird der Destruktor von File schon machen, falls es nötig ist, darüber brauch' ich mir gar keinen Kopf zu machen. (Normal, so muss es sein.)

Aus Gründen der Waffengleichheit zu IOException muß außerdem zugestanden sein, dass irgendwie (der Möglichkeiten sind viele) ein Fehlerbeschreibungsobjekt erhältlich ist.

File file;
if (file.open("test.txt") &    file.write("name=") &    file.write(name) &    writeContent(file)) // rufe Hilfsmethode
{
  [...]
}
else
{
  message("IO/Fehler: " + getError().getReason());
}

-- vgl

Wäre natürlich jetzt gemein, aus den drei Schreibbefehlen 30 zu machen :-) Dann sähe die &&-Konstruktion schon etwas alt aus. Den getError() gestehe ich Dir gerne zu. Aber ist getError() threadsafe? Wie wird damit umgegangen, wenn in den write-Befehlen mehrere Fehler auftreten, ... Exceptions sind da wesentlich einfacher, weil sie den Fehlergrund und -kontext eindeutig und ohne Aufwand für den Programmierer festhalten. -- jd

Mit den 30 Schreibbefehlen sehe ich eigentlich kein großes Problem. Allerdings bricht diese Lösung zusammen, wenn ich nicht einfach nur eine Sequenz von gleichartigen Befehlen ausführen will, sondern zwischendurch auch noch andere Methoden aufrufen oder irgendwelche Kontrollstrukturen benutzen möchte.

Alternativ/parallel zu Returncodes würde auch das klassische Durchreichen ganz gut funktionieren. Skizze:

class MyErrorInfo : public ErrorInfo
{
public:
  ~MyErrorInfo()
    { if (errorHappened()) message(getReason()); }
};

[...]
{
  MyErrorInfo errinfo;

  File file(errinfo);

  file.open("test.txt");
  file.write("name=");
  file.write(name);

  // rufe Hilfsmethode
  writeContent(errinfo, file);
}

File beeinflußt im Fehlerfall das Fehler-Info-Objekt und die Funktionen von File "tun im weiteren nichts", falls ein Fehler aufgetreten ist. Weitere 30 Schreibbefehle: kein Problem (kommt mir jetzt nicht mit Performanzproblemen). Threadsafe: yep. Fehlergrund und -kontext eindeutig: ja, ohne wildes Exception-Herumgespringe. Aufwand: ja, Klassen zur Fehlerinformationsdurchreichung sind zu erstellen bzw. liegen vor (reflektiert Exceptionklassen). Und: ja, die Durchreichung selbst muß erfolgen. Warum nicht, die Fehlerbehandlung wird dadurch vom Stiefkind ("Ausnahme") zum first-class citizen. -- vgl

Eine Methode, die sofort zusammenbricht, wenn Du dazwischen andere Objekte manipulierst, deren Zustand Du in einem Fehlerfall nicht verändern solltest.

Eigentlich bist Du jetzt bei einem selbstgemachten Exceptionhandling angekommen. Nur daß das Exceptionobject leider auch erzeugt wird, wenn kein Fehler auftritt, und als expliziter Parameter mitdurchgeschleift werden muß, was Dir die Exceptionsyntax auch einfacher bieten würde. Noch nicht gelöst ist die Situation, wenn mehrere Fehler auftreten. MyErrorInfo? müsste die dann schon alle speichern können. Nicht tragfähig ist aber imho die Konstruktion, das Programm weiter zu durchlaufen, wenn irgendwo ein Fehler auftritt. Das mit der Performance kann ein reales Problem sein, oder es kann (z. B. bei der Steuerung eines Roboters) fatal sein, den Fehler erstmal zu ignorieren. -- jd

Eigentlich bist Du jetzt ...: 1.: Aber eben ohne Exceptions, d. h. die Programmstruktur spiegelt den Programmfluss wieder; von einer Funktion, die ich aufrufe, weiß ich auch (gäbe es eben keine Exceptions), daß sie (falls überhaupt) auch nur von da wiederkehrt.

Die Programmstruktur ohne Exceptions spiegelt den Programmfluss weit weniger gut wieder als mit! Schließlich kann ich einem Methodenaufruf bei Deiner Lösung ja gar nicht ansehen, ob er bei einem vorherigen Fehler noch etwas bewirkt oder nicht. Insbesondere ergeben sich möglicherweise erhebliche Wartungsaufwände durch erhöhte Fehleranfälligkeit, da ja sichergestellt sein muss, dass auch jede Methode jeder Unterklasse angemessen auf eine Fehlersituation reagiert.

2. ist es nur ein Angebot, gibt ja noch die andere oben besprochene Möglichkeit.

Da dieses Angebot von Dir als Reaktion auf Schwächen der anderen Möglichkeit gemacht wurde, scheint mir diese Erkenntnis doch eine gewisse Bedeutung zu besitzen.

Nur daß das Exceptionobject leider auch erzeugt wird, ..., und als expliziter Parameter mitdurchgeschleift werden muß, ...: 1. kann die Operation sehr billig sein, 2. könnten die lokalen Fehler-Info-Objekte womöglich stattdessen besser als Membervariablen in den Klassen angelegt werden, bei deren Nutzung es zu Fehlern kommen kann: Parameterübergabe fällt weg. Noch nicht gelöst ist die Situation, wenn mehrere Fehler auftreten.: Tut auch nicht nötig, der Beispielcode mit den Exceptions liefert schließlich auch nur lediglich den ersten auftretenden Fehler.

Da hast Du recht, allerdings leistet das Dein Code wirklich nur, wenn alle Objekte (innerhalb des Methodenaufrufs) dasselbe Fehlerobjekt benutzen.

Nicht tragfähig ist aber imho die Konstruktion ...: Natürlich ist sie tragfähig, solange das Programmverhalten das gewünschte ist. Mein Beispiel verhält sich im Fehlerfall wie das Exception-Beispiel.

...und liest sich auch fast genauso. Ich verstehe immer noch nicht den Vorteil, keine Exceptions zu benutzen...

Das mit der Performance kann ein reales Problem ...: Ja, ja, das mit der Performance kann immer ein Problem sein. Totschlagargument. Und wir wollen auch nicht "im Voraus optimieren" (siehe SoftwareOptimierung).

Ich sehe das Problem eher darin, dass ja möglichst sämtlicher Code, den ich normalerweise in einen try-Block packen würde, auch fehlersensitiv sein sollte - nicht nur die Objekte, die einen Fehler hervorrufen können, sondern auch diejenigen, die in ihrem Umfeld benutzt werden (und auf deren Code ich im Extremfall gar keinen Zugriff habe). Mit Exceptions habe ich ein Konzept, dem ich durch die standardisierte Handhabung und direkte Einbindung in die Laufzeitumgebung blind vertrauen kann, ob nun bei eigenem oder fremden Code.

... den Fehler erstmal zu ignorieren: Versteh' ich nicht, es wird nichts "ignoriert", jedenfalls nicht mehr, als im Exception-Beispiel. -- vgl

Verstehe ich, ehrlich gesagt, auch nicht... ;-) -- ip

Ich meinte, bloß, wenn "file.write("name=");" einen Fehler bewirkt, "file.write(name)", etc. auch noch ausgeführt werden.
-- jd


Was mich im Zusammenhang mit der Exception-Anwendung besonders beschäftigt ist: Die Arbeit mit Fehlerstatus-Rückgabe ist etwas mühsamer (noch mühsamer in Java auf Grund des fehlenden goto), aber total simpel und jede Funktion kann für sich in Bezug auf korrekte Fehlerbehandlung beurteilt werden. Bei der Verwendung von Exceptions scheint mir die Sicherstellung der korrekten Fehlerbehandlung nur durch eine komplexe Zusammenschau über das Gesamtprojekt möglich. --hl

P.S. Mit Returncodes könnte eine Fehlerbehandlung vielleicht so aussehen (keine konkrete Programmiersprache gewählt):

  error=0;
  file=FileOpenMess("test.txt"); // generates default error message
  if(file==NULL) {
    error=-1; goto finalize;
  }
  status=FileWriteMess(file,"name=" + name); // generates default error message
  if(status) {
    error=-1; goto finalize;
  }
  status=FileWriteContent(file);
  if(status) {       
    message(...);
    error=-1; goto finalize;
  }
finalize:
  FileClose(file); // tolerant gegen file==NULL
  return error;

wobei viele Variationsmöglichkeiten existieren. Sicherlich gibt es nicht das Problem der Übersichtlichkeit.

"generates default error message" - Schön, aber wohin? Ins Logfile, auf den Bildschirm oder per Email? Muss man dann ein FileOpenMessLog, FileOpenMessScreen und FileOpenMessEmail schreiben? Ebenso dann für FileWriteMessLog, FileWriteMessScreen und FileWriteMessEmail? Und was passiert, wenn die Operation "name=" + name im Aufruf von FileWriteMess an Speichermangel scheitert? Die Arbeit mit Returncodes mag "total simpel" sein, aber nur dann, wenn auch nur simple Fehler passieren können.

(Nicht ganz ernstgemeinte Varianten verschoben nach ComeFrom.)

Mit Java-Exceptions:

try {
  file=FileOpenMess("test.txt"); // generates default error message
  FileWriteMess(file,"name=" + name); // generates default error message
  FileWriteContent(file);
} catch (SomeException e) {
  message(...);
  throw e;
} finally {
  FileClose(file); // tolerant gegen file==NULL
}


Ein besonders überzeugendes Beispiel für die Anwendung von Exceptions ist für mich die Behandlung syntaktischer Fehler in einem Recursive-Descent-Parser. Ein solcher enthält für jedes syntaktische Konstrukt eine entsprechende Analyse-Funktion, und die syntaktische Analyse eines Konstrukts involviert den rekursiven Aufruf der Analyse-Funktionen für die darin eingeschachtelten Unter-Konstrukte.

Syntaktische Fehler werden nun typischerweise irgendwo tief unten in dieser Aufrufverschachtelung entdeckt. Es wäre jedoch sehr umständlich und würde den gesamten Code gewaltig aufblähen, wenn man für jeden dieser Aufrufe einen Returncode explizit abfragen und nach oben durchreichen müsste.

Möglicherweise ist das aber gar nicht die einzige Alternative - eine Idee wäre z. B. die Benutzung des NullObjekt Musters stattdessen. Unter Umständen ließe sich auf diese Weise das Problem rein über die Ausnutzng von Polymorphie lösen, ohne den Code durch Exception-Handling "aufzublähen"... Habe ich aber ehrlich gesagt noch nie ausprobiert. -- IljaPreuß

Versteh' ich nicht so recht: Wie soll das NullObject?-Pattern etwa die Rückkehr zu einer "oberen" Aufruf-Ebene ähnlich wie mit Exceptions vereinfachen (s. folgenden Absatz)? --KlausGünther

Das NullObjekt könnte die sofortige Rückkehr zu dieser "oberen" Aufruf-Ebene unnötig machen, indem es den "darunterliegenden" Ebenen ein sinnvolles default- oder "null"-Verhalten gibt. Sicherlich ist es nicht möglich, eine Exception einfach durch ein NullObjekt zu ersetzen, sondern selbiges würde einen größeren Einfluss auf das allgemeine Design haben - ich bin mir nicht sicher, ob das negativ zu bewerten ist. -- ip

Ebenso typisch ist, dass man in solchen Fehlerfällen mit der Fehlerbehandlung auf einer viel gröberen Ebene ansetzen möchte, als da wo der Fehler entdeckt worden ist. Z. B. möchte man einfach beim nächsten Statement wiederaufsetzen. Mit Exceptions kann man auf einfache Weise mit einem Schlag unter korrektem Abbau der Aufrufverschachtelung auf diese grobe Ebene zurückkehren und dort fortfahren.

Siehe auch ExceptionsInSpracheLava.

--KlausGünther


Merkwürdig, tagsüber komme ich nicht an diese Seite ran, sie wird von der Filterliste der Firmenfirewall blockiert.

Für Exception-Beispiele brauche ich nur in einer Smalltalk Entwicklungsumgebung nach den dort vorhandenen Exceptions suchen. Die meisten werden schnell einleuchten, andere sind exotisch:

Ein gutes allgemeines Beispiel sind hangeschriebene Parser. (Ist der Parser generiert, stört mich der unübersichtliche Code nicht ;-) Dabei brauchen die Exceptions gar nicht unbedingt bis in die Modulschnittstelle bekannt gemacht werden. Es reicht völlig, sie modulintern zu verwenden. Der Parser hat mitunter eine total simple Schnittstelle, ist aber intern kompliziert. Fehlerbehandlung ist nur rudimentär möglich, da die interne Struktur des Parsers hinter der Schnittstelle verborgen bleibt. Fehlermöglichkeiten gibt es aber i. d. R. sehr viele. Verhinderung unbehandelter Exceptions ist einfach, weil die Schnittstelle schlank ist. Die mögliche Reaktion des Nutzers bezieht sich eher auf die Datenquelle denn auf den Parsevorgang selbst. Im Effekt benötige ich also einen raschen, geregelten Abbruch des Parsevorgangs und Information über die Art des Fehlers. Für soetwas ist der Exceptionmechanismus gut gerüstet.

-- SaschaDördelmann

Ok, arbeiten wir es Punkt für Punkt ab. Ich sehe auf Anhieb keine Chance für "Arithmetikfehler" (es sei denn, ich habe eine Programmiersprache mit schrottiger Arithmetik). Kannst du ein Beispiel nennen? -- VolkerGlave

Die IEEE floating point exceptions: z. B. NaN, Division durch 0, INF, Over- und Underflow u. v. m. werden von vielen modernen Prozessoren implementiert und daher von praktisch allen Programmiersprachen auch so übernommen.

Das steht hier ja gerade zur Debatte, ob es von Vor- oder von Nachteil ist, diese Dinge als sog. "Exceptions" zu übernehmen. Meiner Ansicht nach ist es von Nachteil. Siebenkommafünf geteilt durch Null ist INF. Keine Problem. Erst recht kein "Fehler". -- vgl (erst Mo. wieder vor Ort)

Division durch 0 durch irgendeine als Unendlich festgeschriebene Konstante zu ersetzen ist aber leider mathematisch falsch und entspricht auch nicht dem IEEE Standard. Exceptions sind eine allgemeine Lösung für viele Probleme. Lösen wir uns doch mal von den "Fehlern" und konzentrieren uns darauf, dass "exception" englisch für "Ausnahme", "Ausnahmeregelung" oder "Einwand" heisst und das Konzentrieren auf Fehlerbehandlung daher nicht Zielführend ist.

Jetzt wird es hier langsam blödsinnig. Dass Division von Siebenkommafünf durch Null Unendlich ergibt, ist nicht "leider mathematisch falsch", sondern (ohne leider) mathematisch richtig. Und es entspricht dem IEEE Standard, indem es hier "Inf" ergibt. -- vgl

Na ja, mathematisch ist es tatsächlich nicht richtig. Das spielt aber bei dieser Diskussion nicht so große Rolle. Was ist das Ergebnis von: 12 + "a" / 2i?

"..., mathematisch ist es tatsächlich nicht richtig.": Ist es wohl. Offenbar kommen wir hier nicht zusammen. Ich hätte jedenfalls gerne eine Programmiersprache, bei der mathematisch korrekt Unendlich herauskommt. "Was ist das Ergebnis von: 12 + "a" / 2i?": Bei kompilierenden Sprachen hätte ich gerne in der Kompilephase Abbruch mit etwas in der Art "Syntax error ...", bei interpretierenden Sprachen _unmittelbaren_ Abbruch der Ausführung mit ebenfalls etwas in der Art "Syntax error ...". -- vgl

... Ist es wohl. Na ja, sagen wir es so: INF ist eine praktische Lösung der IEEE, mit der Defintion der mathemantischen Division der realen Zahlen hat es aber wenig zu tun. Schon deswegen, weil INF keine reelle Zahl ist :-) Nun aber zum Thema: Du hast Recht, dass kompilierte Sprachen bei 12 + "a" / 2i einen Compilefehler melden könnten. Man könnte z.B. in C++ aber auch Klasse definieren, die "beliebige" Werte halten kann (Integer, Float, String, Datum, Complex, ...) und die mathematische Operatoren implementiert. Nennen wir sie z.B. Var. Den Fehler im Ausdruck "Var(12) + Var("a") / Var(0, 2)" würde der Compiler schwer erkennen können.

Es kann also auch bei kompilierten Sprachen zu Laufzeitfehlern innerhalb der Ausdruckauswertung kommen. Wie reagiert man auf solche Fehler? Dein Vorschlag, das Programm abzubrechen, ist sicherlich eine Möglichkeit. Warum sollten aber gerade Ausdruckauswertungsfehler eine Ausnahme bilden? Warum sollten sie nicht mit anderen Laufzeitfehlern gleichbehandelt werden? Was, wenn der Ausdruck nicht in Quelltext steht, sondern vom Benutzer eingegeben wird? Sollte auch dann das Programm abgebrochen werden müssen?

Übrigens, wenn man damit rechnen muss, dass ein Statement zum Programmabbruch führt, ist die Codesequenz a; b; c; nicht mehr so klar. Ein Abbruch erinnert irgendwie an das Werfen einer Exception - nur mit dem Unterschied, dass der aktuelle Prozess ihn nicht "fangen" und nicht "abräumen" kann. Dass muss dann das aufrufende Programm übernehmen. -- GregorRayman

"Warum sollten aber gerade Ausdruckauswertungsfehler eine Ausnahme bilden?": Sollen sie nicht, habe ich auch nicht gesagt. "Warum sollten sie nicht mit anderen Laufzeitfehlern gleichbehandelt werden?": Sie sollen gleichbehandelt werden. Unmittelbarer Programmabbruch. "Was, wenn der Ausdruck nicht in Quelltext steht, sondern vom Benutzer eingegeben wird? Sollte auch dann das Programm abgebrochen werden müssen?" Ja. Wenn das Verhalten nicht akzeptabel ist (und das ist es normalerweise nicht), dann ist die Benutzereingabe vor der Bewertung eben zu prüfen. Muss eh getan werden, wenn man eine brauchbare Benutzermeldung präsentieren will. Wird seit Tausend Jahren auch so gemacht. "Dass muss dann das aufrufende Programm übernehmen." Über ein "aufrufendes Programm" reden wir hier nicht. Allerdings: Das eigentliche Programm ist so zu schreiben, dass es nicht (von sich aus) abbricht. Tut es das doch, dann enthält es entweder noch einen Programmierfehler, der zu korrigieren ist. Oder das Verhalten wird so akzeptiert. -- vgl

**** "Warum sollten sie nicht mit anderen Laufzeitfehlern gleichbehandelt werden?": Sie sollen gleichbehandelt werden. Unmittelbarer Programmabbruch.
    • Und ich dachte, andere Fehler können durch einen Fehlercode signalisiert werden. Oder habe ich Dich falsch verstanden?
  • "Was, wenn der Ausdruck nicht in Quelltext steht, sondern vom Benutzer eingegeben wird? Sollte auch dann das Programm abgebrochen werden müssen?" Ja. Wenn das Verhalten nicht akzeptabel ist (und das ist es normalerweise nicht), dann ist die Benutzereingabe vor der Bewertung eben zu prüfen. Muss eh getan werden, wenn man eine brauchbare Benutzermeldung präsentieren will. Wird seit Tausend Jahren auch so gemacht.
    • Das ist aber schon eine andere Behandlung als bei anderen Fehlern, nicht wahr? Bei anderen Fehlern willst Du deren Auftreten zulassen, und über Fehlercode signalisieren; bei Ausdrücken darf der Fehler nicht passieren. Beispiel: a) Der Benutzer gibt den Namen einer nicht vorhanderer Datei ein. Das Programm versucht sie zu öffnen und gibt statt eines Filehandle den Fehlercode -1 zurück. b) Der Benutzer gibt den hypotehtischen Ausdruck "Filecontents(nichtvorhanden.txt)" ein. Warum kann man es nicht gleichartig behandeln? Warum sollte das Programm zuerst überprüfen, ob bei der Auswerung nicht ein Fehler droht? Was ist, wenn die Fehlersituation erst nach der Überprüfung ensteht? (z.B. weil die Datei danach gelöscht wird?)
Übrigens: Wir besprechen hier nur Fehler, die in einem korrekten Programm passieren können. Programmierfehler sind ein anderes Thema. -- gR

Bevor wir hier dutzende neue Fässer aufmachen, möchte ich gerne den Fall "Siebenkommafünf geteilt durch Null" (als ein Beispiel vermeintlicher "Arithmetikfehler") zuende behandeln. Hier nochmal meine Wunschvorstellung: _Entweder_ soll die Programmiersprache es hergeben, dass dabei ein plausibles Rechenergebnis - üblicherweise Unendlich - herauskommt. So handhaben es bspw. die Sprachen SpracheJ (7.5 % 0 ergibt _, _7.5 % 0 ergibt __) und SpracheK (7.5 % 0 ergibt 0i, -7.5 % 0 ergibt -0i). _Andernfalls_, wenn die Programmiersprache so definiert wird, dass bei "Siebenkommafünf geteilt durch Null" kein plausibles Rechenergebnis herauszubekommen ist, wünsche ich, dass, sollte dennoch diese Rechnung durchzuführen versucht werden, das Programm unmittelbar abgebrochen wird (natürlich mit irgendeiner zugehörigen ausführlichen Hinweismeldung, sofern in der Umgebung der Programmiersprache die Meldung von Hinweisen vorgesehen ist). Das bedeutet dann natürlich, dass bei solchen Programmiersprachen der Programmierer von vorneherein dafür zu sorgen hat, dass solche Fälle nicht eintreten; tritt es doch ein (Folge: Abbruch), hat er noch einen zu korrigierenden Programmierfehler in seinem Programm, oder er gibt sich zufrieden mit diesem Abbruchverhalten. -- vgl

[Weitere Reaktionen auf die von mir oben vertretenen Ansicht, dass bei der Division von Siebenkommafünf durch Null sehr wohl Unendlich das plausible Ergebnis sei. -- VolkerGlave]

Ist es nicht. Wenn 7.5/0==INF, dann muss 0*INF==7.5 sein. Aber auch 1/0==INF, folglich 0*INF=1. Daran ist überhaupt nichts richtig. Tatsächlich sind +INF, -INF und NaN drei verschiedene NullObjekte. Und ich finde, es ist ein ziemlicher Schmerz im verlängerten Rücken, dass besonders diese NaNs eine Tendenz haben, sich zu vermehren. Mit Exceptions (die ich in diesem Fall gern einschalte, wenn sie die Hardware bietet) ist das Debugging wesentlich einfacher.

Hä, Debugging bei INF und NaN? Nehmen wir an, es sei Aufgabe, eine kleine Divisionstabelle zu produzieren. Ich nehme SpracheCee, die standardmäßig _keine_ Exception produziert (erfreulicherweise):
#include "stdio.h"
int main() {
    int ix, iy;
    for (ix = -1; ix <= 1; ++ix) {
        for (iy = -2; iy <= 2; ++iy) {
            const float x = ix;
            const float y = iy;
            printf("x=%.2f, y=%.2f, x/y=%.2f\n", x, y, x / y);
        }
    }
    return 0;
}
Das Programm gibt aus:
x=-1.00, y=-2.00, x/y=0.50
x=-1.00, y=-1.00, x/y=1.00
x=-1.00, y=0.00, x/y=-Inf
x=-1.00, y=1.00, x/y=-1.00
x=-1.00, y=2.00, x/y=-0.50
x=0.00, y=-2.00, x/y=-0.00
x=0.00, y=-1.00, x/y=-0.00
x=0.00, y=0.00, x/y=NaN
x=0.00, y=1.00, x/y=0.00
x=0.00, y=2.00, x/y=0.00
x=1.00, y=-2.00, x/y=-0.50
x=1.00, y=-1.00, x/y=-1.00
x=1.00, y=0.00, x/y=Inf
x=1.00, y=1.00, x/y=1.00
x=1.00, y=2.00, x/y=0.50
Ich bleibe dabei, dieses Verhalten für gut und nützlich zu halten, um so eben auf unproblematische Weise (z. B.) eine Divisionstabelle zu erhalten. Und ich kapiere andersherum nicht, wie man als Entwickler wünschen könnte, während der Ausgabe der dritten Zeile zur Laufzeit lieber eine "Exception" haben zu wollen (um etwa - abstruse Idee - hier igendetwas "debuggen" zu wollen, wo es - ohne Exceptions - doch problemlos und fehlerfrei durchläuft). -- VolkerGlave

"Bei solchen Beispielen brauche ich auch kein Exceptionhandling ... Wenn ich aber ..." - Mal brauchst du Exceptionhandling, dann brauchst du plötzlich wieder keines. Entscheide dich doch mal.
"Ich habe das Problem tatsächlich erst gefunden, nachdem ich die zugehörige Hardwareexception aktiviert habe." - Hm. Stattdessen auf einige Zwischenergebnisse [Tausende?!] einen bedingten Breakpunkt "xyz == NaN" zu setzen, war dir wohl zu simpel. -- VolkerGlave

Auf dieser Seite geht es darum, EinGutesExceptionBeispiel zu liefern, um die anderen Konstrukte geht es nicht. Noch sehe ich nicht, dass jemand so ein Beispiel geliefert hätte. Was ich sehe, ist, dass ein zunächst behaupteter Anlass für eine Exception (beim Entstehen von INF) von mir mit einem realistischen Gegenbeispiel entkräftet wurde. EinGutesExceptionBeispiel steht somit weiterhin aus. -- VolkerGlave

Sorry, falls ich gerade gegen irgendeine Regel verstoße, aber vielleicht kann ich etwas Licht in das Problem der Behauptung x/0 = INF bringen. Die mathematisch korrekte Aussage hierzu ist, dass die Division durch 0 nicht definiert ist. Die Form x/0 = INF stellt eine verkürzte Form von Der Grenzwert von x/n für n gegen 0 ist unendlich (gibt es hier im Wiki eigentlich einen Formelsatz?). -- RalfEbert

Darf ich auch noch was zum Thema Unendlichkeit beisteuern? Inf ist tatsächlich keine reelle Zahl, das folgt aus der Definition reeller Zahlen. Jetzt kann man sich bestenfalls noch darüber unterhalten, ob diese Definition sinnvoll ist. Ja, sie ist deswegen sinnvoll, weil man solche Gesetzmäßigkeiten wie Kommutativität, Assoziativität, Distributivität, unbeschränkte Teilbarkeit usw. haben möchte. Die Null ist als Ausnahme schon schlimm genug, weil man durch null nicht teilen kann. Wie Udo ausgeführt hat, hat Inf eben nicht 0 als Reziprokes und es gibt auch sonst keine reelle Zahl, die Inf als Reziprokes haben könnte. Man könnte den Bereich der reellen Zahlen um infinitesimal kleine und unendlich große Zahlen erweitern. Damit kommt man zur Nichtstandardanalysis. Wenn man jetzt aber so eine unendlich große Zahl nimmt, nennen wir sie mal w, dann kann bei Beibehaltung der schönen Gesetzmäßigkeiten trotzdem nicht 1/w = 0 sein (Udos Argument funktioniert wieder ganz genauso), sondern es wäre 1/w eine Zahl die größer als Null aber kleiner als jede reelle Zahl ist. So was könnte man auf dem Computer auch implementieren, dann bräuchte man aber auch Darstellungen für 2*w, 3*w, w^2, w^3, 1/w usw., denn es sollen doch wenigstens die vier Grundrechenarten unbegrenzt ausführbar sein. Aber für den normalen numerischen Gebrauch lohnt sich das sicher nicht. Bleibt man bei reellen Zahlen gibt es kein Unendlich und das Zeichen dafür ist nicht als eigenständige Zahl aufzufassen, sondern ist eine Kurzschreibweise, welche sich nur im Kontext des Ausdruckes interpretieren lässt. Etwa bedeutet "lim_{x->\infty} f(x)" nicht, dass wir "x gegen Unendlich" laufen lassen, sondern lediglich, dass wir beobachten, was passiert, wenn "x" immer größer wird und jede mögliche Schranke überschreitet. Genauso bedeutet "lim_{x->a} f(x) = \infty" nicht, dass "f(x)" einem ominösen Unendlich entgegenstrebt, sondern schlicht, dass "f(x)" über alle Schranken wächst, wenn sich "x" an "a" annähert. -- HenningThielemann


"Der Benutzer gibt den Namen einer nicht vorhanderer Datei ein. Das Programm versucht sie zu öffnen und gibt statt eines Filehandle den Fehlercode -1 zurück."

Warum lassen wir es hier überhaupt erst zu einem Fehler kommen? Warum kann das Programm nicht erstmal prüfen, ob die Datei vorhanden ist, bevor es versucht, sie zu öffnen?

Weil es nichts bringen würde. Im Zeitraum zwischen der (noch klappenden) Prüfung und dem folgenden Zugriffsversuch kann die Datei unzugreifbar geworden sein. Es ist schon ok, den Zugriffsversuch zu machen, und dann den Ergebniswert des Zugriffsversuchs zu bewerten. (Wobei die Bewertung entfallen kann, sofern die Weiterverarbeitung unabhängig vom Ergebniswert ist und der Ergebniswert eine Nicht-Bewertung zulässt.) -- vgl

Eben, es würde nichts bringen, vorab zu prüfen. Warum sollte diese Situation anders gehandhabt werden, als wenn der Benutzer einen Ausdruck eingibt (der das Programm desn Ausdruck aus einer Datei einlißet), dessen Auswertung ab und zu zu einem Fehler führen kann? Warum soll hier nur die Möglicheit bestehen, einen falschen Wert oder einen Abbruch zu liefern?

Übrigens: Eine Exception zu werfen ist, was den abbrechenden Code angeht, dem Abbruch eines Programmes gleich. -- gR

(a) "Übrigens: ...": Kommt es nicht. Eine Exception kann abgefangen werden. Der Abbruch kann und soll es nicht.
Ich sage ja: "was den abbrechenden Code angeht"
(b) "Eben, es würde nichts bringen, vorab zu prüfen. Warum sollte diese Situation anders gehandhabt werden, ...": Weil die Situation anders ist. In diesem Fall kann vorher geprüft werden, da zwischen Prüfung und Bewertung keine Änderung des Ausdrucks mehr stattfindet.
Es können doch Daten geändert werden, auf denen der Ausdruck basiert. z.B. Ausdruck: "Maximaler Eintrag in der Datei X".
Deine beiden Punkte "Übrigens: ..." und "Eben, ..." sind also falsche Aussagen. Ich verstehe nicht, warum du solche Aussagen machst. Wenn das so weiter geht, verzichte ich auf eine weitere Diskussion. -- vgl
Sie sind nicht falsch, bloß missverstanden :-) In Gegenteil, Deine Aussage, dass ein Abbruch nicht abgefangen werden soll, ist falsch ;-)

Exceptions vs. Assert
Hier ist schon mehrfach geäußert worden, Exceptions sollten nicht dazu dienen, Programmierfehler aufzudecken. Ich teile diese Einschätzung nicht. Sie fußt vielleicht auf der Angst, jemand könne durch Behandlung von Exceptions Programmierfehler verschleiern. Ich will nicht ausschließen, dass das gelegentlich vorkommen kann, aber ich ziehe Exceptions einem harter Abbruch mit einem C-style assert trotzdem vor. Ich bin darüber hinaus auch mit ThePragmaticProgrammer einig darüber, dass Assertions im Produktivcode nicht deaktiviert werden sollten. --SDö

"Ich will nicht ausschließen, dass das gelegentlich vorkommen kann, aber ich ziehe Exceptions einem harter Abbruch ... trotzdem vor.": Schön und gut, dass du das vorziehst, leider verschweigst du das Entscheidende: Warum ziehst du es vor? Ein harter Abbruch würde dich dazu zwingen, das Programm an der fehlerprovozierenden Stelle richtigzustellen (etwaig das Assert selbst). Du möchtest aber darüber hinaus noch die Freiheit haben, an einer Reihe von nicht-fehlerprovozierenden Stellen eingreifen zu können. Das Verschleiern-Können hast du also bereits eingeplant. Besten Dank. -- vgl

Eine nichtbehandelte Exception mündet ebenfalls in einen Programmabbruch. Exceptions sind Informationsträger und erleichtern das Debuggen. Deine Polemik ist deplaziert.

[Ahem, zum Verschleiern gehört es unter Umständen, aufwändig eingegebene Daten in Sicherheit zu bringen. Der Unterschied zwischen "Oh, mein Programmierer ist ein Idiot, ich zerstör mich selber." und "Mein Programmierer ist zwar ein Idiot, ich hab die Datei aber trotzdem mal lieber gespeichert." ist der zwischen einem Amokläufer und einem User, der das Update kauft.]

Du hast aber die Chance, sie zu behandeln, und dann also nicht notwendigerweise einen Abbruch. D. h. du hast die Chance, zu verschleiern. Bei hartem Abbruch hast du die Chance nicht, da muss der Fehler beseitigt werden. Harte Abbrüche führen also auf längere Sicht (sofern das Programm überhaupt noch gewartet wird) zwangsläufig zu robustem, weil Fehlerursachen-beseitigtem Code, wohingegen Exceptions auf längere Sicht (sofern das Programm überhaupt noch gewartet wird) zwangsläufig zu aufgeblähtem, klapprigem, weil hier und da Fehlerursachen-bewahrendem und nur Symptom-beseitigendem Code führt. -- vgl

Das ist zu pauschal. Es gibt sogar Programme, die dürfen gar nicht abstürzen und müssen ggf. im laufenden Betrieb gepatcht werden. Dann gibt es evtl. Programmteile, auf die ich nur bedingt Zugriff habe und die daher ebenfalls nicht abstürzen dürfen. Wenn sie es doch tun, muss ich mir Gedanken bzgl. der Stabilität des Gesamtsystems machen und greife auf Alternativen zurück. Die Grenzen zwischen Daten- und Programmierfehler sind zudem fließend: Ein nicht abgefangener Datenfehler kann ein Programmierfehler oder ein gewollt in Kauf genommener Kompromiss sein. Es gibt also Fälle, in denen eine Art Pauschalbehandlung aller Fehler Sinn macht. Diese Pauschalbehandlung muss der Wartung keineswegs entgegenstehen. Ich kann z. B. den Benutzer vor dem eigentlichen Programmabbruch darüber informieren, dass sich das Programm zwangsbeendet! Dafür muss zwischen einem aus der Business-Logik kommenden Fehler und dem Programmabbruch ein Dialog geschaltet werden. Mit Exceptions nur ein Anwendungsfall eines allgemeinen Prinzips. Deinem "da muss der Fehler beseitigt werden" halte ich entgegen: Es kann im Gegenteil dazu führen, dass der Kunde das Vertrauen in dein Programm verliert, weil sich dieses nicht einmal geordnet beendet.

"Es gibt sogar Programme, die dürfen gar nicht abstürzen und müssen ggf. im laufenden Betrieb gepatcht werden.": "Sogar"? Lustige Einstellung. _Kein_ Programm darf abstürzen. Im übrigen redet hier niemand von abstürzen. "Die Grenzen zwischen Daten- und Programmierfehler sind zudem fließend: Ein nicht abgefangener Datenfehler ...": Schnurzpiepe ob die fließend sind, ich zumindest rede hier ("Exceptions vs. Assert") _ausschließlich_ über Programmierfehler. "Ich kann z. B. den Benutzer vor dem eigentlichen Programmabbruch darüber informieren, dass sich das Programm zwangsbeendet!": Kannst du nicht, weil vorher ein unerwarteter Fehler passiert ist, und dehalb unbekannt ist, ob noch Ressourcen für die Benutzer-Information bestehen. Richtig ist aber, dass selbstverständlich nach Programmabbruch der Benutzer über die Zwangsbeendigung informiert werden kann. "Es kann im Gegenteil dazu führen, dass der Kunde das Vertrauen in dein Programm verliert, weil sich dieses nicht einmal geordnet beendet." Es wird geordnet beendet. Das Programm muss korrigiert werden, anschließend passiert derselbe Fehler nicht mehr. Wüsste nicht, wieso das Korrigeren von Fehlern vertrauenmindert sein sollte. -- vgl

Zeig doch bitte mal was man mit der Rückgabe eines Fehlercodes machen kann was man mit Exceptions nicht machen kann. Ich behaupte da gibt es gar nichts. Exceptions vereinheitlichen die Fehlerbehandlung. Wie und wo Fehlerinformation zu finden sind, sollte nun keine Willkür der Bibliotheksentwickler sein (wie GetLastError?, GetLastSystemError?, GetErrorText?, GetDbError? oder mal den Fehlercode als Parameter, mal als Rückgabewert, malt mit Fehlertext, mal ohne). Fehlerinformationen gehören in Exception-Objekt. Da sucht man sie, da findet man sie. Durch Exceptions läß sich die Anzahl der Parameter vieler Prozedure vereinfachen. Ob man Programme mit Fehlern schreibt ist keine Frage ob man Exceptions verwendet oder nicht. Ich bin mir sicher, du würdest mit deinem Fehlerbewustsein in einem Programm das nur Exceptions verwendet nicht mehr Fehler produzieren als jetzt. Und ich auch nicht. hz

PS. Was sind eigentlich deine Ziele in dieser Diskussion? Möchtest du Exceptions aus dem Sprachschatz der Programmiersprachen verband wissen (so nehme ich an, da du sie ja strikt ablehnst) und als Fehlentwicklung Brandmarken? hz

"Was sind eigentlich ... Ziele in dieser Diskussion?": Ziel dieser Seite und verwandter Seiten (z. B. ExceptionsDiskussion) ist es, zu klären, ob Exceptions eher von großem Vorteil oder eher von großem Nachteil (und also eine Fehlentwicklung) sind. Bislang ist eine Klärung nicht in Sicht. Meine Meinung ist "Fehlentwicklung", richtig. -- vgl

Mich interessierten eigentlich nur "deine" Ziele. Meine Meinung nach, ist "Fehlentwicklung" falsch. Desweiteren glaube ich das die Frage, ob Exceptions Vor- oder Nachteile bringen, jeder schon für sich geklärt hat. Und ohne Exceptions, geht es auch nicht mehr. Zumindest bei Delphi, Java und C# sind Exceptions Pflicht.

Deine letzten beiden Sätze sind für die Diskussion uninteressant. Dass es Sprachen gibt, die das Sprachmittel "Exception" haben, heißt noch lange nicht, dass Exceptions eher von Vor- als von Nachteil sind. In C# ist man immerhin schon mal zur Einsicht gelangt, Member-Exceptionspezifikationen für schädlich zu halten (CheckedExceptionsConsideredHarmful). Außerdem sind Exceptions bei den von dir genannten Sprachen insofern nicht Pflicht, als man bei eigenen Algorithmen selbst entscheiden darf, sie mehr oder weniger (oder gar nicht) einzusetzen. -/- Mein Ziel ist, zu erfahren, ob Exceptions eher von großem Vorteil oder eher von großem Nachteil (und also eine Fehlentwicklung) sind. Ich denke nicht, dass das eine Geschmacksfrage ist, sondern glaube noch, dass sich das prinzipiell klären lassen müsste. So, wie das für "goto" auch irgendwann mal geklärt worden ist (und zwar so, dass das Thema "Einsatz von goto" um Größenordnungen weniger strittig ist als "Einsatz von Exceptions"). Natürlich bin ich nicht so naiv, zu glauben, dass wir das hier im DseWiki klären werden. Da müsste schon jemand mit richtig viel Ahnung kommen und einen Bericht "ExceptionsConsideredHarmful" verfassen. -- vgl

Exceptionspezifikationen sind ein Thema für sich und haben nichts mit der prinzipiellen Entscheidung für oder gegen Exceptions zu tun. Ich für meinen Teil habe die Frage ob Exceptions oder ob nicht längst für mich geklärt. Du scheinbar auch. Mit einer allgemeinen Entscheidung gegen Exceptions ist m. E. nicht zu rechnen. --SDö

"Exceptionspezifikationen ...": Da ist unsere Wahrnehmung unterschiedlich. Ich halte ihre Strittigkeit für ein Indiz der Strittigkeit des ganzen Themas. "Mit einer allgemeinen Entscheidung gegen Exceptions ist m. E. nicht zu rechnen.": 1957 wurde die in Folge populäre SpracheFortran erfunden, natürlich mit "goto"s. Erst mehr als zehn Jahre später wurde die Erkenntnis GoToConsideredHarmful allmählich Mehrheitsmeinung. Und erst nach weiteren über 25 Jahren wurde 1994 die in Folge populäre SpracheJava erfunden und dabei "goto"s wg. dieser Erkenntnis weggelassen (und zwar - völlig bewußt - trotz des Umstandes, dass "goto"s in gewissen exotischen Situationen ganz praktisch sein mögen). Bei Exceptions ist heute Fakt, dass sie in Teilen der Community für gefährlich gehalten werden, wenn auch in der Minderheit. Niemand, der logisch denkt, würde jedoch - z. B. angesichts des "goto"s-Beispiel - heute überzeugt die Aussage machen, dass mit einer allgemeinen Entscheidung gegen (oder für) Exceptions zukünftig zu rechnen sei. Das ist heute nicht (auch nicht tendenziell) entscheidbar. Selbstverständlich ist übrigens denkbar, dass statt Exceptions ja/nein eine irgendwie "dazwischen" liegende Lösung gefunden wird, so wie statt "goto" die abgeschwächten Formen switch/case/default/break/continue/(early) return/Polymorphie/... gefunden wurden. -- VolkerGlave

Volker, Dein Goto-Beispiel hinkt vorne und hinten. Als Goto erfunden wurde, gab es überhaupt keine Alternative dazu. Erst mit der Einführung von Unterprogrammen, kamen Gotos in Verruf. Die Diskussion um Gotos hat wahrlich eine lange Geschichte, aber ich kann mich nicht erinnern, dass über den Sinn von Unterprogrammen diskutiert wurde. Exceptions wurden erfunden, als die von dir gepriesenen Fehlercodes, die Standardmethode zur Fehlerbehandlung waren. Warum also hat man sie erfunden? Langeweile? Ich behaupte aus reiner Unzufriedenheit mit den Fehlercodes. Wenn man Goto als Beispiel nimmt, dann ist es richtiger Goto mit den Fehlercodes und Exceptions mit Unterprogrammen gleichzusetzen. Die Zukunft mag was besseres bringen, aber bis dahin werden wir auch Exceptions benutzen. Und zu Fehlercodes, als einzige Möglichkeit der Fehlerbehandlung, führt kein Weg zurück. hz

Du bist spät in die Diskussion eingestiegen und hast offenbar erst einen Bruchteil nachgearbeitet. Ich habe nicht Fehlercodes als einzige Möglichkeit der Fehlerbehandlung gepriesen. (Nur ein Beispiel: Bei Arrayzugriff mit falschem Index plädiere ich für sofortigen Programmabbruch, statt 'ne Exception zu schmeißen. Hat also absolut nichts mit Fehlercodes zu tun.) "Als Goto erfunden wurde, gab es überhaupt keine Alternative dazu.": Selbst wenn das stimmen würde (was ich nicht glaube), das war nicht mein Punkt. Mein Punkt war, dass selbst nachdem Gotos in Verruf gekommen waren, es noch über 25 Jahre gedauert hat, bis eine populäre Programmiersprache das Goto aus ihrem Sprachschatz weggelassen hat. Dies wiederum war ein Gegenpol zum Statement von Sascha, dass seines Erachtens mit einer allgemeinen Entscheidung gegen Exceptions nicht zu rechnen sei, um nämlich darauf hinzuweisen, dass vermeintlich feststehende Entscheidungen nach Jahrzehnten kippen können. "Die Zukunft mag was besseres bringen, aber bis dahin werden wir auch Exceptions benutzen.": Und wir werden es diskutieren und genau das tun wir hier. So what. -- vgl

Volker, du schreibst gerne viel und machst es damit lästig auf alle deine Behauptungen und "Argumente" einzugehen. Doch heute werde ich mir mal dafür Zeit nehmen. "Du bist spät in die Diskussion eingestiegen", trotzdem habe ich sie von Anfang an verfolgt. "und hast offenbar erst einen Bruchteil nachgearbeitet", weil ich nicht das schreibe was du lesen möchtest? "Ich habe nicht Fehlercodes als einzige Möglichkeit der Fehlerbehandlung gepriesen.", wieso teilst du uns das mit, ich habe nirgendwo so etwas behauptet. Ich schrieb "die von dir gepriesenen Fehlercodes" und finde dass das immer noch eine haltbare Aussage ist. "(Nur ein Beispiel: Bei Arrayzugriff mit falschem Index plädiere ich für sofortigen Programmabbruch, statt 'ne Exception zu schmeißen)". endlich eine interessante Ausage. So kann man höchstens handeln, wenn man Programmierer des Hauptprogramms ist. Programmiert man Kommponenten oder Bibliotheken führt so eine Einstellung dazu, dass Flugzeuge abstürtzen und Benutzer um den Lohn ihrer Arbeit gebracht werden. "Selbst wenn das stimmen würde (was ich nicht glaube)", dein Glaube hilft uns nicht weiter. Dann kommt blah, blah, blah "um nämlich darauf hinzuweisen, dass vermeintlich feststehende Entscheidungen nach Jahrzehnten kippen können.", auf das, meinst du uns aufmerksam machen zu müssen. Arrogant. "Und wir werden es diskutieren", du bist der Meinung du diskutierst hier? Auf mich wirkt es eher so, als wolltest du aus dieser Seite, eine Volker-G.-Gedächtnis-Seite machen. hz

Wer in Diskussionen zu Gesellschaftsthemen als erster die Nazi-Karte spielt, hat automatisch verloren. Wer in Diskussionen zu Programmierthemen als erster die Flugzeugabsturz-Karte spielt, hat automatisch verloren. (*) Du hast soeben verloren. -- vgl (*) Die Regel habe ich mir gerade ausgedacht. Sollte sie neu sein (wahr ist sie zweifelsohne), darf sie arroganterweise gerne nach mir benannt werden.

Dein Kommentar ist ja noch schlechter als meiner. Jeder bastelt gerne an seinen eigenen Regeln, warum sollte das bei dir anders sein. Auf Inhalt hast du leider verzichtet. So beziehe ich mich noch mal auf meinen eigenen Kommentar. Ich stelle folgende Behauptung zur Diskussion:

Bei einem falschen Array-Zugriff, kann an der Stelle wo er erkannt wird, nicht entschieden werden, ob ein Programmabbruch sinnvoll ist, da dort der Aufruf-Kontext unbekannt ist. Viel mehr muß der Fehler entlang der Aufrufkette hochgereicht werden, bis er zu einer Methode gelangt, die entscheiden kann ob die korrekte Ausführung des Unterprogramms so existensiell ist, dass bei einem Fehler das Programm abgebrochen werden muß. Denn evt. reicht es auf den Fehler zu reagieren und einen neuen Versuch zu starten oder ihn zu melden (Protokolldatei oder Messagebox).

Beispiel: Ich schreibe in einer Textverarbeitung einen umfangreichen Text und füge dann eine Grafikdatei ein, die einen Fehler enthält und somit einen OutOfRangeError? provoziert. Statt eines Programmabruchs erwarte ich eine Meldung "Datei konnte wegen ... nicht geladen werden.". In einer anderen Situation, z.B. wenn die Grafikdatei während eines Batch-Jobs geladen wird, mag ein Programmabbruch sinnvoll sein.

Ich komme für mich zu dem Schluß, dass jede Methode, in dessen Kontext es nicht möglich ist, zu entscheiden, ob auf ihre Dienste verzichtet werden kann, ein Fehler an die aufrufende Methode gemeldet werden muß. Und um Fehlerart/-code/-meldung weiter zu reichen, gibt es für mich momentan nichts besseres als Exceptions. Denn selbst wenn sich keiner um die Exception kümmert, kommt es zu einer Reaktion des Programms. hz

Man muss sich dem Schluss nicht notwendigerweise anschliessen. Du plädierst ungefähr für die Strategie, wie sie z. B. bei der Standard-C++-String-Template-Klasse bei der Funktion at() gegeben ist:
21.3.4 basic_string element access
const_reference at(size_type pos) const;
reference at(size_type pos);
Requires: pos < size()
Throws: out_of_range if pos >= size().
Returns: operator[](pos).
Daneben gibt es den Elementzugriffsoperator operator[]():
const_reference operator[](size_type pos) const;
reference operator[](size_type pos);
Returns: If pos < size(), returns data()[pos]. Otherwise,
if pos == size(), the const version returns charT().
Otherwise, the behavior is undefined.
Ich präferiere diesen Zugriffsoperator, weil ich unter allen Umständen vermeiden will, dass, sollte ein Programmierfehler vorliegen und versehentlich mit einem falschen Index zugegriffen werden, irgendwer auch nur die Chance erhält, um diesen Fehler herumzuprogrammieren. Und von vorneherein vermeiden will, dass nun wie bei at() Diskussionen losgetreten würden, ob das Programm korrigiert werden soll, oder die Exception zu behandeln sei. Ich will unter allen Umständen, dass der Fehler beseitigt wird. Deshalb darf keine Exception kommen. Gut. Allerdings gefällt mir selbstredend das "Otherwise, the behavior is undefined." nicht. Ich hätte lieber definiertes Verhalten. Etwas in der Art "Otherwise, the program terminates (see ...).". -- VolkerGlave

Wie nicht anders zu erwarten, hatte ich dagegen einige Probleme, mich an das Verhalten des []-Operators in manchen C++ Bibliotheken zu gewöhnen. Eine echte Alternative sind at:ifAbsentPut: bzw. at:ifAbsent: aus SpracheSmalltalk. Du magst daraus ersehen, dass ich für intelligente Schnittstellen zur Vermeidung von Ausnahmesituationen durchaus zu haben bin. Das ändert jedoch nichts an meiner grundsätzlichen Haltung zu Exceptions. --SDö

Ich will unter allen Umständen, dass der Fehler beseitigt wird. Deshalb darf keine Exception kommen. Gut. Allerdings gefällt mir selbstredend das "Otherwise, the behavior is undefined." nicht. Also das verstehe ich jetzt überhaupt nicht, Volker. Diese Zugriffsart funktioniert doch nur, wenn der Index ok oder gerade gleich der Anzahl der Elemente ist. Milliarden anderer Eingabewerte, lassen das System in einen undefiniert Zustand "weiterlaufen" (ist doch eigentlich das schlimmste was es gibt oder?). Das heißt,im allgemeinen wird ein Fehler gar nicht bemerkt und deswegen auch nicht beseitigt. Ich hätte lieber definiertes Verhalten. Etwas in der Art "Otherwise, the program terminates (see ...).".. Die von dir abgelehnte at() Version bietet doch genau das. Okay, die Exception kann abgefangen werden, ein weiterer Vorteil wie ich finde. Welchen Vorteil bietet die andere Methode? Mit scheint sie sowieso nur deswegen so designt zu sein um die typischen "eleganten" C Schleifen programmieren zu können. hz

Ich kann mich ab hier nur wiederholen. @SDö: Das mit der Alternative passt hier nicht, denn ich sprach von einem Programmierfehler durch Arrayzugriff mit falschem Index. Also, hypothetische Ist-Situation: Ein vor sich hinlaufendes Programm, in dem nun zur Laufzeit die Situation eintritt, dass mit falschem Index zugegriffen wird, und zwar aufgrund eines Programmierfehlers. Das "und zwar aufgrund eines Programmierfehlers" ist mir wichtig. Ich betrachte ausdrücklich _nicht_ Fälle, wo "mit Absicht" bzw. wissentlich bzw. in Erwartungshaltung (wie bei at:ifAbsent: et al.) per falschem Index zugegriffen wird. Falls sich Leser hier etwaig wundern, dass doch niemand absichtlich mit falschem Index zugreifen wird, doch, es gibt ein paar Leute, die bei Sprachen/Bibliotheken, die bei falschem Arrayzugriff eine Exception auslösen, absichtlich genau diese Eigenschaft nutzen, um zur Weiterverarbeitung zu gelangen. Diese Vorgehensweise ist auch nicht von vorneherein als "schlecht" zu werten, denn die Sprache sieht es halt vor, und die Leute nutzen es eben. Und darum, nochmal, geht es mir hier nur im Fälle, wo _versehentlich_ mit falschem Index zugegriffen wird, eben um Programmierfehler. Und deshalb ist Saschas Argument "Eine echte Alternative sind at:ifAbsentPut: bzw. at:ifAbsent ..." hier nicht sehr interessant, denn das wäre bereits die Behebung des Fehlers (oder ihn gar nicht zu machen), aber ich betrachte hier das _Bestehen_ der Fehlersituation. Und unter der Voraussetzung des Auftretens solch eines akuten Programmierfehlers bewerte ich die zwei Varianten: (a) Es wird eine Exception ausgelöst (die Exception wird mit Infos zur Programmausführungssituation versorgt) und (b) es wird keine Exception ausgelöst, sondern das Programm wird sofort beendet (und die Infos zur Programmausführungssituation möglichst in irgendeinen Meldungskanal ausgegeben, tut hier aber nichts zu Sache). Variante (b) erhellt nahezu zweifelsfrei, dass ein Programmierfehler vorliegen muss, sie erzwingt, dass das Programm analysiert und korrigiert oder abgelöst wird (oder man lässt es bestehen und lebt mit dem Fehlverhalten). Bei Variante (a) ist zunächst einmal gar nicht so sicher, ob das Fehlverhalten überhaupt zügig bemerkt wird, denn es ist ohne weiteres möglich, dass etwas weiter oben im Call-Stack jemand nach dem Verfahren programmiert hat, wie ich es einige Sätze zuvor geschildert habe. Dann manifestiert sich die Exception nicht. Resultat: Das Programm liefert mehr oder weniger falsche Ergebnisse. Selbst aber wenn die Exception unbehandelt "bis oben durchschlägt" und also zur Programmbeendigung führt, geht dann eine Riesenarbeit los, weil ermittelt, erkannt und entschieden werden muss, ob überhaupt ein Programmierfehler vorliegt (was ja meine Voraussetzung war, was aber der Analyst anfänglich noch nicht wissen kann). Erst nach diesem langwierigen Prozess wird das Programm korrigiert, sofern nicht absichtlich oder versehentlich die Fehlentscheidung getroffen wird, um den Programmierfehler herumzuprogrammieren (was eine Möglichkeit ist, die es bei Variante (b) nicht gibt). Eine Fehlentscheidung wäre es zwangsläufig, denn die/eine Lösung, die man bei Variante (b) finden würde (und man findet immer eine), lässt sich auch bei Variante (a) anwenden, und sie ist eine Behebung der Fehlerursache. Der Vollständigkeit halber: Wie bei Variante (b) kann man sich natürlich auch bei Variante (a) für Ablösung oder Weiterbetrieb entscheiden. ==> Aus diesem Vergleich ist mein Schluss, Variante (b) sympathischer zu finden. (Bitte nicht mit dem Argument kommen, man könne sich beim gesamten Vorgehen nach Variante (b) ja auch irgendwo versehen. Ja, das kann man. Aber diese "Versehensmöglichkeiten" hat man identisch auch beim Vorgehen nach Variante (a).) -/- @hz: Ich verstehe das "Weiterlaufen" nicht, von dem du zu meinen scheinst, dass ich dafür plädieren würde. Ich plädiere fur sofortigen Abbruch, also nicht für "Weiterlaufen". Bei falschem Indexzugriff "undefined behavior" zu zeigen, gefällt mir nicht und will ich nicht haben. Die "Lösung", statt dessen eine Exception zu werfen, wie in Java/Smalltalk/... und in C++-at() gehandhabt, gefällt mir ebensowenig (eher schlechter, aber egal ...). Ich hätte gerne Abbruch (Begründung nun mehrfach gegeben), was zwar keine der aufgeführten Sprachen derzeit leistet, aber Sprachen verändern sich ja und es kommen neue dazu. -- vgl

Nachtrag 2005-03-21: Stolpere gerade über eine Java-Klasse "Reporter": "This library provides a way to report exceptions with one easy method call: Reporter.report(); [...]". Da gibt es eine Funktion "setExitOnException()": "If set to true, System.exit(1) is called after any exception is reported, which can save time during debugging. [...] Default is true.".
https://noexceptions.dev.java.net/nonav/source/browse/*checkout*/noexceptions/net/java/dev/noexceptions/Reporter.html
Wer sagt's denn, erfreulich, dass es noch normale Leute gibt. -- vgl

Nachtrag 2005-06-10: Hier eine Programmiersprache, die bei "Out-of-Range Indexing" nicht mit kindischen "Exceptions" reagiert, sondern das Verhalten sauber und durchgängig definiert: http://kx.com/q/d/q1.htm, Abschnitt "8.1 Nulls and Out-of-Range Indexing", Zitat: "[...] Indexing a list with an out-of-range index produces the null value for the datatype of the list. [...]". -- vgl

Ein nicht vom Benutzer initiiertes Programmende ist ein Absturz. Man kann den Effekt jedoch mildern, indem man einen Dialog vorschaltet. Fehlende Ressourcen würde ich als davon separierbares Problem sehen. Das ist aber so speziell, dass eine Diskussion nicht sprachunabhängig erfolgen kann. Dass die Fehlerkorrektur eine das Vertrauen mindernde Maßnahme ist, habe ich nie behauptet. -- SaschaDördelmann


Was ich auch aus dieser Diskussion über Exceptions gelernt habe: GregorRayman/Exceptions


Oh Mann, diesen Schlagabtausch hier und auf anderen Seiten zur Ausnahmebehandlung der DSE-Wiki zu lesen, ist ganz schön anstrengend. Was soll ich mit dieser Diskussion anfangen? Es ist wahrscheinlich besser, wenn jeder seinen Ansatz vorstellt und die Vorteile anpreist und darunter diejenigen, die anderer Meinung sind, die Nachteile aufzählen. Und das nicht immer im Wechsel wie in einer Mailingliste, sondern etwa so, dass erst ein Block Vorteil, dann ein Block Nachteile kommt und nichts weiter. Oder aber Vorteile/Nachteile zu verschiedenen Aspekten. Versetzt euch doch mal in die Lage des Lesers!

Das Thema Fehlerbehandlung finde ich enorm wichtig. Ich habe jetzt manche Einleitung zu Realisierungen von Ausnahmen in verschiedenen Programmiersprachen gelesen, aber nicht, wie man sie nun sinnvoll einsetzt. Vielleicht interessiert jemanden eine Erfahrung, die ich immerhin schon sammeln konnte. Ich habe mich kürzlich dafür entschieden, in der Modula-3-Bibliothek für arithmetische Berechnungen solche Ausnahmen wie "Vektoraddition zweier Vektoren ungleicher Dimension" in ASSERTs umzuwandeln. Der Übersetzer für die SpracheModula3 (dessen Ausnahmebehandlung in die SpracheJava übernommen wurde) schleppt einem vorbildlich beharrlich alle unbehandelten Fehler hinterher (genau wie Rückgabewerte), so dass man sie nicht so einfach ignorieren kann. Deswegen musste ich immer den Fehler für die Addition zweier Vektoren ungleicher Dimension behandeln, obwohl beim Aufruf meistens automatisch sichergestellt war, dass die Dimensionen passten und ich es sonst leicht hätte testen können. Das hat mir gezeigt, dass dies kein Benutzungs- sondern ein Programmierfehler ist.

Diesen Fall konnte ich endlich klar entscheiden. Was ist aber mit Fehlern wie "Division durch 0" oder "Überlauf"? Bei Überlauf tendiere ich eher zu Benutzungsfehler, denn durch Benutzereingaben können Ergebnisse sehr groß werden. Aber um einen Überlauf bei einer Multiplikation zu erkennen, müsste ich die Berechnung in kleinere Multiplikationen aufteilen, die nicht fehlschlagen können. Division durch 0? 0 als Teiler kann man leicht abfragen, aber innerhalb einer größeren Berechnung, die insgesamt wertlos wird, wenn irgendwo eine Division durch 0 auftritt, ist es doch recht umständlich. Andererseits kann man es auch so sehen: Rechnet man mit Fließkommazahlen, ist nicht nur die 0 als Teiler schlecht, sondern jede andere Zahl nahe bei 0 bedeutet, dass Gefahr im Verzug ist. Da hülfe auch eine Ausnahme nichts. -- HenningThielemann

"Es ist wahrscheinlich besser, wenn jeder seinen Ansatz vorstellt [...]" - Das hat zumindest einer getan, siehe GregorRayman/Exceptions.
"Das Thema Fehlerbehandlung finde ich enorm wichtig. [...]" - Meine Privatmeinung: Es gibt nur zwei Arten von "Fehlern". Nämlich einerseits Programmierfehler. Die sollten (wieder: nur meine Meinung), wenn das Laufzeitsystem sie denn überhaupt entdecken kann, zum sofortigen Beenden der Programmausführung führen (möglichst unter Hinterlassung eines irgendwie gearteten Abbruchprotokolls). Und zweitens Hardwarefehler, gegen die wir Softwareker aber eh machtlos sind (jedenfalls 99,9 % von uns), und gegen die wir also nichts unternehmen können und auch nichts zu unternehmen brauchen. Alles andere ist _kein_ Fehler. Wenn ich z. B. eine Datei X öffnen will, und diese Datei X ist nicht da, dann ist das kein Fehler, sondern es ist völlig normal, wie es auch normal wäre, dass sie da ist.
"Was ist aber mit Fehlern wie 'Division durch 0' [...]" - Da haben wir so einen Fall. Division durch 0 ist kein Fehler, jedenfalls nicht per se. Division durch 0 ergibt meistens Unendlich, und wenn eine Programmiersprache dieses Ergebnis auszudrücken in der Lage ist, warum denn nicht. -/- Aber du hast schon recht, wenn ich Berechnungen durchführe, die sich an der Auflösungsgrenze der verwendeten Datentypen bewegen, dann habe ich halt einen Programmierfehler gemacht. Und bekomme eben schrottige Ergebnisse. Sollte das Laufzeitsystem dies als "Fehler" entdecken können, dann wünsche ich mir auch hier einen Abbruch, was dann dazu führen dürfte, dass ich das Programm umschreibe (und den Programmierfehler ausbaue). -- VolkerGlave


siehe ExceptionsConsideredHarmful und WardsWiki:UseExceptionsInsteadOfErrorValues, EinSchlechtesExceptionBeispiel


KategorieDiskussion KategorieException
StartSeite | Neues | TestSeite | ForumSeite | Teilnehmer | Kategorien | Index | Hilfe | Einstellungen | Ändern
Text dieser Seite ändern (zuletzt geändert: 5. September 2005 16:12 (diff))
Suchbegriff: gesucht wird
im Titel
im Text