Ein TRY-Element (ab Version ≥ 3.29-07) ist ein nicht standardmäßiges Element in einem Strukturogramm, das die strukturierte Ausnahmebehandlung (auch Fehlerbehandlung genannt) ausdrückt — ein fortgeschrittenes Konzept für erfahrene Programmierer. Daher sind TRY-Elemente im Modus Simplified toolbars (vereinfachte Symbolleisten) nicht verfügbar. Wenn Sie mit strukturierter Ausnahmebehandlung nicht vertraut sind, lesen Sie zunächst die Hintergrundinformationen.
Das TRY-Element gehört nicht zum offiziellen Elementsatz, den Isaac Nassi und Ben Shneiderman vorgeschlagen haben, noch wurde es in den Standard DIN 66261 aufgenommen (es war damals schlicht noch nicht „erfunden"). Es fügt sich jedoch überraschend gut in die Elementvielfalt ein. (Die Implementierung in Structorizer, verfügbar seit Version 3.29-07, hat experimentellen Charakter.)
Wie fügen Sie ein TRY-Element in Structorizer ein und verwenden es?
Stellen Sie sich folgendes Szenario vor: Sie möchten eine Routine schreiben, die eine Datei mit einem gegebenen Namen öffnet und versucht, eine ganze Zahl daraus zu lesen. Dabei könnten mehrere Dinge schiefgehen: Die Datei existiert möglicherweise nicht, zumindest nicht im aktuellen Verzeichnis, oder der Inhalt ist möglicherweise nicht als Zahl lesbar. Die Routine selbst weiß nicht, von wo und zu welchem Zweck sie aufgerufen wurde, sodass sie ein auftretendes Problem nur erkennen, aber keine sinnvollen Lösungswege vorschlagen kann. Sie muss die Entscheidung dem Aufrufer überlassen. Der Aufrufer muss jedoch Informationen über die Art des Problems erhalten, um damit umgehen zu können. Die Routine könnte folgendermaßen aussehen (siehe File I/O API auch für weitere Beispiele mit TRY-Blöcken):
Unterroutine für die TRY-Demo
Das Ergebnis wird in der Variablen number gespeichert. Daher wird sie oben deklariert und initialisiert. Dann versucht die Routine, die Datei zu öffnen. Die eingebaute Funktion fileOpen löst keinen Fehler aus, gibt aber einen Wert kleiner oder gleich 0 zurück, wenn das Öffnen fehlschlägt. In diesem Fall löst unsere Routine selbst eine Ausnahme mit einem aussagekräftigen Text aus. Ein throw-Jump verlässt die Routine durch Stack Unwinding (siehe Hintergrundinformationen). Andernfalls versucht die Routine, eine ganze Zahl über fileReadInt zu lesen. Diese Funktion löst einen Fehler aus, wenn die Datei leer ist oder der anfängliche Teil des enthaltenen Texts nicht in eine ganze Zahl umgewandelt werden kann. Das wäre zwar akzeptabel, aber wir möchten die Datei nicht offen lassen. Daher platzieren wir die fehleranfällige Anweisung in einen TRY-Block, sodass wir das Schließen der Datei im finally-Abschnitt erzwingen können. Das TRY-Element fängt (und neutralisiert) den Fehler, unabhängig davon, ob der catch-Abschnitt etwas tut oder nicht — der Fehler würde als abgefangen und behandelt betrachtet. Die Routine würde regulär enden. Wir wollen den Fehler jedoch nicht unbemerkt lassen. Daher entscheiden wir uns, den abgefangenen Fehler weiterzuwerfen. Dies geschieht durch einfaches Einfügen eines leeren throw-Jumps in den catch-Abschnitt. Die Wirkung des Weiterwerfens wird verzögert, bis der finally-Abschnitt abgeschlossen ist.
Konstruieren wir nun diese Routine in Structorizer.
Zunächst: Sie müssen den Modus Simplified toolbars deaktiviert haben (Menü "Preferences › Simplified toolbars?"): TRY-Blöcke sind nur im Standard-GUI-Modus verfügbar.
Wählen Sie das Element aus, vor oder nach dem Sie das TRY-Element einfügen möchten:
Erster Schritt der TRY-Demo
Wählen Sie das TRY-Symbol in der Symbolleiste (mit gedrückter Shift-Taste, wenn der TRY-Block oberhalb des ausgewählten Elements eingefügt werden soll) oder fügen Sie es über den entsprechenden Menüpunkt "Diagram › Add › Before/After › TRY" (oder über das Kontextmenü) hinzu. Alternativ können Sie auch die Tastenkombination <Ctrl><F5> drücken (ab Version ≥ 3.29-13). Der Element-Editor öffnet sich (siehe Screenshot unten). Widerstehen Sie nun der Versuchung, die geschützten Anweisungen hier einzugeben! Füllen Sie stattdessen nur einen Namen (oder eine vollständige Deklaration) für die Ausnahmevariable ein. Diese Variable enthält die vom fehlschlagenden Code (oder vom Executor selbst) geworfene Ausnahmezeichenkette, sodass Sie im catch-Abschnitt damit arbeiten können. Nochmals: Schreiben Sie den geschützten Code NICHT hier (dies geschieht im nächsten Schritt)!
Sie können jedoch unbedenklich einen nützlichen Kommentar in das Kommentarfeld schreiben.
Element-Editor für einen TRY-Block (V 3.30-15)
Beachten Sie das Kontrollkästchen „Show the FINALLY block even if empty" (mit dem blauen Rahmen im obigen Screenshot markiert), das mit Version 3.30-15 eingeführt wurde. Wenn es nicht aktiviert ist, zeigt das erstellte TRY-Element keinen Finally-Block an, sodass wir dort keine Elemente hinzufügen könnten. Unser Plan ist es jedoch, sicherzustellen, dass die Datei geschlossen wird, unabhängig davon, ob ein Fehler auftritt oder nicht. Wir benötigen daher den finally-Abschnitt (siehe Schritt 5 unten), also stellen Sie sicher, dass das Kästchen aktiviert ist. Kein Problem, wenn Sie es vergessen haben: Sie können es jederzeit später aktivieren, wenn Sie den Editor für das TRY-Element erneut öffnen. Vor Version 3.30-15 wurde der finally-Abschnitt immer gezeichnet, was unnötig Platz für leere finally-Abschnitte verbrauchte.
Das eingefügte TRY-Element sieht wie im folgenden Screenshot aus. Jetzt ist es an der Zeit, Anweisungen oder beliebige Elemente in den geschützten oberen Abschnitt (zwischen den Beschriftungen „try" und „catch") einzufügen. Wählen Sie dazu das Feld mit dem Leermengen-Symbol aus und fügen Sie die gewünschten Elemente wie in den jeweiligen Abschnitten dieses Leitfadens beschrieben ein:
Füllen des try-Abschnitts in einem TRY-Element
Wählen Sie nun den mittleren Block des TRY-Elements aus, um die Fehlerbehandlungsaktivitäten anzugeben. Dieser Teil macht typischerweise von der deklarierten Ausnahmevariablen Gebrauch, um herauszufinden, was das Problem ist, und gibt möglicherweise den Inhalt an den Benutzer aus. Fügen Sie die benötigten Elemente wie gewohnt ein. In unserem Beispiel möchten wir die Ausnahme weiterwerfen, also fügen wir ein EXIT-Element ein:
Füllen des Catch-Abschnitts
Das Jump-Element ist mit dem in den Parser-Einstellungen für die EXIT-Anweisung bei Fehler konfigurierten Schlüsselwort zu füllen:
Konfiguration des throw-Schlüsselworts
Der untere Block in einem TRY-Element ist für die „Aufräum"-Aktivitäten reserviert. Diese werden garantiert ausgeführt, unabhängig davon, was in den try- und catch-Abschnitten passiert ist. Dieser Abschnitt kann leer sein, wenn keine Aufräumarbeiten notwendig sind. In unserem Beispiel möchten wir sicherstellen, dass die erhaltene Dateiressource an das Betriebssystem zurückgegeben wird, also fügen wir eine fileClose-Anweisung hinzu:
Füllen des Finally-Abschnitts
Nachdem das gesamte TRY-Element gefüllt ist, können wir der Routine weitere Elemente hinzufügen. Wählen Sie dazu das TRY-Element als Ganzes aus (durch Klicken auf seinen äußeren Rahmen) und hängen Sie die return-Anweisung für die Routine an:
Nachfolgende Elemente anhängen
Jetzt ist die Routine fertig und kann in den Arranger übertragen werden (der als Unterroutinen-Pool für den Executor dient).
Fertige Routine
Wir könnten nun ein Hauptdiagramm erstellen, das die obige Routine nutzt. Nehmen wir an, wir möchten den Benutzer bitten, uns den Namen einer Datei anzugeben, in der eine Kontonummer gespeichert ist. Wir können unsere gerade erstellte Routine getNumberFromFile verwenden, wissen aber, dass sie Fehler werfen kann. Sinnvollerweise rufen wir sie daher in einem TRY-Element auf. Diesmal wird kein finally-Abschnitt benötigt, da wir auf der Hauptebene keine Ressource einführen. Im Behandlungsabschnitt teilen wir dem Benutzer lediglich den erhaltenen Fehlertext mit — er befindet sich in der im TRY-Element-Editor angegebenen Variable, hier haben wir sie exception genannt. Dann wird der Benutzer aufgefordert, eine andere Datei zu wählen:
Hauptprogramm mit der getNumberFromFile-Routine
Sie können dieses kleine Programm im Executor testen, um zu beobachten, wie der Mechanismus funktioniert. Das Arrangement-Archiv kann hier heruntergeladen werden:
Hier ist ein einfacheres Beispiel (der finally-Abschnitt wurde leer gelassen, sodass er seit Version 3.30-15 standardmäßig nicht angezeigt wird):
Einfaches Programmdiagramm mit TRY
Hintergrundinformationen
Als Nassi und Shneiderman um 1973 ihren Modellierungsansatz für die strukturierte Programmierung vorschlugen, war das Konzept der strukturierten Ausnahmebehandlung (SEH) noch nicht geprägt worden. Es ist daher keine Überraschung, dass der Satz von Nassi-Shneiderman-Diagrammelementen (insbesondere gemäß DIN 66261) keine Elemente für die strukturierte Ausnahmebehandlung enthält. Die meisten modernen Sprachen bieten jedoch spezifische syntaktische Strukturen zur sicheren und bequemen Unterstützung der Fehlerbehandlung, typischerweise in Form von try-catch-Blöcken, ergänzt durch Mittel zum „Werfen" eines Problems auf die Ebene, die es behandeln kann. Wenn sie nicht übermäßig missbraucht wird, ist die strukturierte Ausnahmebehandlung eine leistungsstarke Programmiertechnik, die die rekursive Weitergabe von Statuswerten als Routineergebnisse möglicherweise mehrere Aufrufebenen nach oben vermeidet, was wiederholte Prüfungen erfordert und die gewöhnliche Ergebnisweitergabe behindert.
Programmierer stehen häufig vor einem Problem, das oft mit externen Ressourcen zusammenhängt (z. B. eine Datei, die nicht geöffnet werden kann oder ungeeigneten Inhalt hat), das gelöst (oder umgangen) werden könnte, aber nicht sinnvoll auf der aktuellen Ebene innerhalb verschachtelter Schleifen oder in einer tiefen Aufrufssituation. Die Lösung erfordert möglicherweise Benutzerinteraktion oder zumindest einen weiteren Überblick auf einer höheren Ebene, auf der mehr Wissen über den Kontext und die Ziele verfügbar ist. Die Informationen über das Problem sollen daher an diese höhere Kompetenzebene weitergegeben werden, in der Regel mehrere Aufrufebenen nach oben. Dies über die normalen Unterroutinen-Parameterlisten oder Rückgabewerte zu tun ist möglich, belastet jedoch die Aufrufschnittstellen (Routinesignaturen) entlang des Pfades mit vielen sporadisch benötigten Besonderheiten.
Die zentrale Idee besteht nun darin, eine geschützte Umgebung — den „Try"-Block — bereitzustellen, in den Operationsfolgen, die an einem niederschwelligen Detail scheitern könnten, eingebettet werden können. Diese Anweisungsfolge wird durch Fehlerbehandlungscode — den „Catch"-Block — bewacht, der in Aktion tritt, wenn eines der erwarteten potenziellen Probleme (Ausnahmen) tatsächlich eintritt. Zu diesem Zweck „wirft" der Code, der unmittelbar mit dem Fehler konfrontiert ist (z. B. eine Low-Level-Unterroutine), die Fehlerinformationen in der Hoffnung nach oben, dass sie vom lauernden Fänger der „Try"-Umgebung „aufgefangen" und behandelt werden. Wenn dies nicht in einem solchen Try-Kontext geschieht, steigt der geworfene Fehler bis zur obersten Programmebene auf und bringt das Programm zum Absturz.
„Try"-Strukturen können verschachtelt werden. Wenn also der Fänger eines „Try"-Blocks entscheidet, nicht kompetent genug zu sein, die Ausnahme zu behandeln, kann er sie „weiterwerfen", sodass sie von einem äußeren „Try"-Block-Wächter aufgefangen werden könnte. Wenn kein „Catch"-Block entlang des Stack-Pfades nach oben das Problem behandelt, wird das Programm beendet.
Das Auslösen der Ausnahme geht mit „Stack Unwinding" einher, d. h. die verlassenen Aufrufkontexte werden vom Stack entfernt, als wären sie zurückgekehrt, obwohl sie natürlich nicht fortgesetzt werden — die verbleibenden Operationen der abgewickelten Ebenen werden einfach aufgegeben.
Auf der abfangenden Ebene gibt es die hilfreiche Möglichkeit, einen „Finally"-Block anzugeben, der in jedem Fall ausgeführt wird, unabhängig davon, ob die versuchten Anweisungen erfolgreich waren oder eine Ausnahme verursacht haben, und unabhängig davon, ob die Ausnahme abgefangen und behandelt oder weitergeworfen (nach oben weitergegeben) wurde. Dieser Finally-Block ist für notwendige Aufräumaktivitäten gedacht, d. h. um Ressourcen freizugeben, die innerhalb oder unmittelbar vor dem Try-Block erworben wurden. Seine Ausführung ist garantiert, es sei denn, er verursacht selbst einen Fehler. Wenn eine Ausnahme aufgetreten ist und nicht behandelt oder weitergeworfen wurde, wird das Stack Unwinding und das Auslösen der Ausnahme jedoch unweigerlich fortgesetzt, nachdem der Finally-Block beendet ist. Die Anweisungen nach dem Finally-Block werden nur ausgeführt, wenn entweder keine Ausnahme aufgetreten ist oder die aufgetretene Ausnahme vom „Catch"-Block behandelt wurde.