Was tun, wenn’s klemmt?

Eine kürzliche Leserfrage (nein, ich werd mich hier nicht erneut wiederholen: Nur vielen Dank an dieser Stelle!) ist Anregung für einen neuen Grundlagenbeitrag: Wie gehe ich mit Fehlern um, und wie verhindere ich die unschöne Exception-Messagebox, die bei unverhofften Fehlern auftritt und dem Programm eine etwas unsaubere Anmutung verleihen kann?

exceptionMessage

Programmfehler können auf unterschiedlichste Art zustandekommen: Eine Datei kann nicht gelesen oder geschrieben werden, ein Fehler in Xojo oder im Betriebssystem mag zu Problemen führen, der Programmcode selbst hat das Programm aufs Abstellgleis gefahren, oder die Anwendung ist auf ungültige Daten gestoßen, was dem Anwender anzulasten sein mag … (Liste beliebig erweiterbar).

Zunächst mal ein Blick auf den letzteren Fall: Nehmen wir mal an, irgendeine Methode soll normalerweise einen Integerwert zurückliefern. Es mag aber Fälle geben, in denen kein gültiger Wert zu bestimmen ist. Was macht man dann? Der Standardwert von Integer ist 0 und damit auch ein gültiger Integerwert. Eigentlich müsste man zwei Werte zurückliefern: Die Zahl und einen Boolean, der besagt, ob die gelieferte Zahl als gültig anzusehen ist. Nun können Methoden nur einen Wert zurückgeben, weshalb man dann gerne zum Boolean per Byref greift:

Dim Gültig As Boolean
Dim i As Integer = MeineMethode(gültig)

und MeineMethode hat den Aufbau

Public Function MeineMethode(Byref IstGültig As Boolean) as Integer
  Dim Berechnungswert as Integer
  // Nach Berechnung:
  IstGültig = True // oder False, je nachdem
  Return Berechnungswert
End Function

Auf aufrufender Seite prüft man dann, ob Gültig nun auch True ist, bevor man etwas damit macht. Kann man so machen – de facto arbeiten sehr viele Programme und auch (größtenteils ältere) OS-Bestandteile so –, aber so richtig elegant ist das nicht.

Die Ausnahme als Regel

Das Xojo-Framework (und man kennt ja meine Vorlieben) benutzt stattdessen Exceptions – Ausnahmesituationen. Man signalisiert also der Laufzeitumgebung – dem „Drumherum-Code“, der nebst dem eigentlichen Programmcode für die Ausführung der Anwendung sorgt –, dass der Zustand der gesicherten Daten verlassen wurde. Erfolgt keine Behandlung dieser Situation, dann kommt es zur obigen MessageBox und das Programm verabschiedet sich aus Sicherheitsgründen.

Das will man ja nun mal nicht unbedingt. Statt loszupreschen und die entsprechende Funktion zu benutzen, vielleicht eher vorsichtiger rangehen. Man probiert also erst einmal, ob sie nicht womöglich eine Exception liefert: Mit der Klausel Try/End Try.

Zu Try/End Try gehört noch ein weiteres Kommando, nämlich Catch: Fange die Exception auf. Tritt nach einem Try (und vor dem End Try) eine Exception auf, wird der Code hinter Catch angesprungen. Dabei kann optional die Art der Exception angegeben werden, um auf diese genauer einzugehen. Ein Beispielcode, wie so häufig aufs neue Framework zugeschnitten, und hier als Action-Eventhandler eines Buttons verpackt:

Sub Action() Handles Action
 Using Xojo.io
 Using Xojo.Core
 Dim F as FolderItem
 Try
  f = New FolderItem (GetOpenFolderItem("").NativePath.ToText)
 Catch NilObjectException
 End Try
 If f <> Nil And f.Exists Then
  Try
   Dim b As BinaryStream
   b = BinaryStream.Open(f, BinaryStream.LockModes.Read)
   Dim mb As New MemoryBlock(b.Read(b.Length))
   b.Close
  Catch Err As IOException
  MsgBox "Lesefehler "+err.ErrorNumber.ToText+EndOfLine+EndOfLine+err.Reason
  End Try
 End If
End Sub

Try/End Try wird hier also gleich zweimal benutzt: Einmal, um die NilObjectException aufzufangen, die auftritt, wenn versucht wird, auf eine abgebrochene GetOpenFolderItem-Methode zuzugreifen: Das alte FolderItem, das dabei herauskommt, ist Nil. Die Catch-Methode dient dann auch nur profan dazu, den Fehler abzufangen und im Text weiterzumachen – der Anwender will an dieser Stelle nichts weiter hören, denn er hat ja auf „Abbruch“ geklickt.

Und zum zweiten, falls beim Lesen des BinaryStreams ein Fehler auftritt, wie er bei Zugriffen auf Dateien nun mal existieren kann. Hier analysiert der Catch-Befehl dann auch die entsprechende IOException und weist den Anwender auf den Grund hin.

Exceptions gibt es in sehr unterschiedlichen Geschmacksrichtungen. Im Zweifelsfall hilft ein Blick in die Dokumentation:

ExceptionList

Das Stoppen stoppen

Wenn es Sie stört, dass Xojo auch bei per Try/Catch behandelten Exceptions in den Debugger springt, können Sie die Direktive

#Pragma BreakOnExceptions False

einsetzen, um dieses Verhalten temporär – bis zum Ende der Methode oder bis es mit dem Wert True wieder eingeschaltet wird – zu deaktivieren.

Eigene Ausnahmen als Regel

Ebenso, wie sich Exceptions abfangen lassen, sind sie auch selbst hervorrufbar. Um dann vom eigenen Programmcode abgefangen zu werden …

Ganz einfaches Beispiel, wieder aus dem Action-Eventhandler eines Buttons à la Douglas Adams: „Bitte diesen Knopf nicht drücken!“

Sub Action() Handles Action
 Using Xojo.Core
 Dim err As New UnsupportedOperationException
 err.ErrorNumber = 1
 err.Reason = "Verbotener Knopf gedrückt"
 Raise err
End Sub

Was man sich im Realfall einfacher machen sollte durch eine eigene MakeException-Methode, die nur die Fehlernummer aufnimmt und den Grund aus Konstanten dazusucht oder was auch immer …

Der Befehl Raise jedenfalls ruft die Exception hervor, die entweder aufgefangen wird oder zur berüchtigten Exception-Meldung vom Anfang führt.

Schönere Ausnahmen

Der Unterschied zwischen einem Programm, das scheinbar mal einfach so abstürzt, und einem, das diese Situation professionell handhabt, liegt meist nur in ein paar Zeilen Code. Gemäß den Goldenen Regeln „Kein Programm ist fehlerfrei“ und „Aus Fehlern lernt man“ (und in Verbindung mit „Man weiß nie, wie der Rechner des Anwenders konfiguriert ist“) kann man von einer Sache sicher ausgehen: Es wird immer mal zu Problemen kommen.

Allermindestens sollte der Benutzer über den Fehler gründlich informiert werden, und nach Möglichkeit sollte er uns diesen auch mitteilen können. Damit das passieren kann, muss es eine Stelle geben, an der diese Exceptions behandelt werden können. Und die gibt es auch, nämlich im App-Objekt mit dem UnhandledException-Event.

Der macht genau das, was sein Name vermuten lässt: Er feuert dann, wenn eine Exception zutage trat, die nicht vom Programm an anderer Stelle aufgefangen wurde, und liefert die Exception natürlich gleich als Property mit.

In dieser ist neben der eigentlichen Fehlernummer noch der Stack bzw. unter iOS bzw. dem Xojo-Framework der CallStack interessant: Hier wird in absteigender zeitlicher Reihenfolge notiert, welche Funktionsaufrufe zum Auftreten des Fehlers führten. Meine Empfehlung: Ein eigenes Fenster anlegen, das die Daten hübsch formatiert, und dann womöglich noch einen Button dazusetzen, um diese Beschreibung als Mail oder per Fernzugriff auf eine Datenbank an Sie zu senden.

UnhandledException erwartet noch einen Booleschen Rückgabewert. Ist dieser True, wird einfach mit dem Programm weitergemacht, als wäre nichts passiert. Es empfiehlt sich in der Regel nicht, so vorzugehen, es sei denn, man hat genau sichergestellt, dass durch die Exception die weitere Funktionsfähigkeit gewährleistet bleibt.

True dient aber auch dazu, die sonst auftretende Fehlermeldung Xojos zu unterdrücken. Weshalb man nach einem

Quit

durchaus noch True zurückliefern kann.

Eine einfache Methode, die Exception Xojo-Framework-kompatibel abzufangen, sieht z.B. so aus:

Function UnhandledException(error As RuntimeException) Handles UnhandledException as Boolean
 Dim stack() As Text
 dim cstack() as Xojo.Core.StackFrame = error.CallStack
 For q As Integer = 0 To cStack.Ubound
  stack.Append uinteger(cstack(q).Address).totext+": "+cstack(q).Name
 Next
 Dim w As New errorwindow
 w.ErrorLabel.text = "Fehler "+error.ErrorNumber.ToText+": "+error.Reason
 w.ErrorArea.text = Text.Join(stack, &u0d)
 w.ShowModal
 dim Weiter as Boolean = w.Result
 If Not weiter Then Quit
 return true
End Function

Der CallStack des neuen Frameworks ist nicht viel anders als der Stack des alten: Wurde früher ein Array of Strings geliefert, in dem die Adressen des Codes und Informationen dazu an einer Stelle standen, ist es nun ein Array of Xojo.Core.Callstack – eine eigene Klasse, in der Adresse und Beschreibung separate Properties sind. Ich mache mit der Schleife als nichts anderes, als den alten Zustand zu rekonstruieren.

Ich habe im kleinen Demoprogramm dazu ein minimales Fenster gestrickt. Klickt man nun auf den verbotenen Knopf, sieht das Ergebnis wie folgt aus:

ExceptionButtonWindow.png

Statt weiter zu machen (in diesem Fall ist es ohne Risiko), sollte ein echter Exception-Handler wie gesagt eher die Daten an den Entwickler schicken. In jedem Fall: Der Stack sagt uns, dass der Fehler nach Klick auf PushButton1 aufgetreten ist. Die umformatierten Adresszeilen sind in der Regel weniger aussagekräftig, da die internen Adressen von Methoden und Controls im Programm nicht unbedingt so schnell zu rekonstruieren sind.

Ein weiteres Textfeld im Demoprogramm dient dazu, Ziffern einzugeben. Hält man sich nicht daran, wird die Exception abgefangen, die nach Integer.FromText auftritt, wenn kein reiner Integerwert übergeben wurde, wie im Fall von Buchstaben oder anderen Satzzeichen.

Wenn Sie’s noch hübscher mögen, schauen Sie sich einmal den Vorschlag von Beatrix Willius im Xojo-Forum zum Formatieren von Exceptions an.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s