Throw 7/10
Design und Tehnik


Fehler

Fangen wir erstmal langsam an. Angenommen Sie schreiben eine Funktion. ÄInnerhalb dieser Funktion können Fehler auftreten. Eine Funktion, die die Wurzel des Parameters berechnet kann beispielsweise nicht korrekt arbeiten, wenn der Parameter negativ ist. Was machen Sie? Eine übliche Technik ist es ein Ergebnis zurückzuliefern, das bei gültingen Parametern nicht vorkommt. In diesem Fall beispielsweise '-1'. Doch dadurch wird der Benutzer dazu gezwungen das Ergebnis immer auf den eventuellen Fehlercode abzufragen. Das ist mühsam und kostet Zeit. Auserdem kann man es leicht mal vergessen. Zudem gibt es auch Funktionen, die alle möglichen Werte gültig sind. Dann wird meistens eine globale Variable verwendet, die im Fehlerfall auf einen bestimmten Wert gesetzt wird. Dann muss man auch noch eine globale Variablen nach jedem Funktionsaufruf überprüfen.


Das QB Fehlersystem

QB bietet ein Fehlersystem bestehend aus 'ON ERROR', 'RESUME', 'ERR', 'ERROR' und dem ziemlich unnützen 'ERL'. Vor allem die Bedeutung von 'ERROR' wird von vielen häufig unterschäzt. Die meisten benutzen 'ON ERROR', um die QB eigenen Fehler abzufangen, 'ERR', um den Aufgetretenen Fehler zu identifizieren, und gegebenen Falls 'RESUME', um nach Behandlung des Fehlers fortzufahren. So kann man beispielsweise versuchen von diskette zu lesen und im Fehlerfall den Benutzer aufforden eine Diskette einzulegen und es erneut versuchen.
Zum unterschätzten 'ERROR': Einige wissen nichtmals, dass 'ERROR' überhaut existiert. Mit 'ERROR' erzeugt man einen eigenen Fehler oder wie es heutzutage heißt (für die die es nicht kennen klingts erstmal seltsam) man kann einen Fehler werfen. Man gibt hinter 'ERROR' den Fehlercode an und schon springt das QB eigene Fehlersystem an.
Hört sich doch schonmal ganz gut an. Jedoch kann man nur eine Zahl als Fehler werfen. Und die haben größten Teils eine feste Bedeutung. Einige sind auch ganz sinnvoll, wie '53': "Datei nicht geunden", andere machen eher weniger sinn, wie '1': "FOR ohne NEXT". Men erkennt sofort den Nachteil: Es ist extrem schwer eigene Feher zu werfen, da man selbst keine Fehler definieren kann und die vorhandenen nicht immer passend sind. Das einzige, was im Fall der Wurzel aus einer Negativen Zahl passt, ist "Unzulässiger Funktionsaufruf". Das ist aber nicht grade aussagekräftig. Wie soll man aus "Unzulässiger Funktionsaufruf" darauf schließen, dass der Parameter der Wurzelfunktion nicht negativ sein darf. (Die QB-Funktion 'SQR', die ebenfalls die Wurzel berechnet wirft im übrigen genau den selben nichtssagenden Fehler.)
Es wäre doch nett, wenn der Fehlertyp nicht eine unverständliche Nummer, sondern etwas Aussagekräftiges wäre. Außerdem sollte der Fehlertext nicht fest vorgegeben sein, sondern ebenfalls frei definierbar. Eine solche Fehlerbehandlung wäre wesentlich flexiebler und die Fehler ließen sich leichter identifizieren.


Das eigene Fehlersystem

Also werden wir unser eigenes Fehlersystem aufbauen. Wie Eingangs erwähnt müssen Sie das nicht selber tun. Die Bibliothek liegt schon lomplett vor. Hier geht es nur arum zu verstehen, wie es funktioniert und Ideen zu geben, wie man selbst etwas vergleichbares entwickelt.
Was brauchen wir? Eine Funktion, die den Fehler wirft und eine Möglichkeit ihn zu fangen. Da man das System zum bearbeiten jeglicher art von Ausnahmesituation verwenden kann, werde ich von nun an von Ausnahmen reden.
Zum werfen einer Ausnahme behelfen wir uns eines Tricks: Viele der Fehlercodes, die QB zur verfügung stellt sind unbenutzt. Deshalb können wir problemlos einen dieser Codes verwenden um die Fehlerbehandlung darüber abzuwikeln. Versuchen Sie nie etwas selbst zu schreiben, was es breits gibt. Es hilft niemandem, wenn Sie ständig das Rad neu erfinden. Ich verwende den Code 100, der garantiert von keiner mir bekannte QB-Version verwendet wird. Die Prozedur zum Werfen eines Fehlers setzt den Typ des Fehlers ('exceptionType'), sowie seine Beschreibung ('what') und verursacht dann den Fehler 100.

SUB mqbThrow (exceptionType AS STRING, what AS STRING)
   mqbInternalSetException exceptionType, what
   ERROR 100
END SUB

'mqbInternalSetException' setzt die Ausnahme. Dazu werden 'SHARED'-Variablen verwendet, damit sie in mehrern Prozeduren zur verfügung stehen.


Fehler fangen

Nachdem Sie jetzt die Möglichkeit haben Fehler zu werfen, sollte es auch jemanden geben, der sie wieder fängt. Da wir im Hintergrund das QB-eigene Fehlersystem verwenden kommen wir um ein 'ON ERROR' nicht herum. Befor wir uns aber mit dem Fangen beschäftigen brauchen wir jemanden, der einen Fehler wirft. Dazu schreiben wir eine einfach Wurzelfunktion, die auf der QB-internen Funktion 'SQR' basiert.

DEFSNG A-Z
FUNCTION Wurzel (radiant AS DOUBLE)
IF radiant < 0 THEN
   mqbThrow "invalid argument", "Radiant muss größer oder gleich 0 sein"
END IF

wurzel = SQR(radiant)
END FUNCTION

'Wurzel' wirft bei negativem Radiant eine Ausnahme. Nu zum Fangen: Jede Datei, die das neue Fehlersystem benutz muss mit folgender Zeile beginnen:

ON ERROR GOTO Fehlerbehandlung

Statt 'Fehlerbehandlung' ist natürlich auch jeder andere In QB als Marke zugelassener Bezeichner erlaubt. Viel interessanter ist aber das, was auf die entsprechende Marke folgt. Zum Fangen des Fehler benötigen Wir den Typ des Fehlers. Dazu verwenden wir 'mqbGetExceptionType', das einfach die 'SHARED'-Variable ausließt und zurückliefert. Gefangen wird die Ausnahme von einer 'SELECT CASE'-Struktur. Die Beschreibung des Fehlers erhalten Sie durch aufruf der Funktion 'mqbGetWhat'.

Fehlerbehandlung:

IF ERR = 100 THEN
   SELECT CASE mqbGetExceptionType
      CASE "invalid argument"
         PRINT "Unzulässiges Argument"
         PRINT mqbGetWhat

      CASE ELSE
         PRINT "Andere Ausnahme"
         PRINT "Fehler-Code: "; mqbExtractErrorCode
         PRINT "Fehler-Text: "; mqbExtractErrorText

   END SELECT

   END
END IF

Auf diese Weise können Sie nun beliebige Fehler werfen und fangen. Damit ist es wesentlich klarer, was genau schiefgelaufen ist. Sie sollten aber nicht für jede Kleinigkeit einen eigenen Fehlertyp kreieren. Wenn es sich um einen ungültigen Parameter handelt sollten sie immer 'invalied argument' verwenden und die genaue Klassifizierung in die Beschreibung verlegen.


QB-Fehler

Sie können eigene Ausnahmen jetzt bequem und übersichtlich veralten, aber was ist mit den QB-internen Fehlern? Man ist weiter auf die seltsamen Fehlercodes angewiesen. Natürlich gibts auch dafür eine Lösung: 'mqbMapError'. Diese Funktion mappt intern die QB-Fehler auf MQB-Ausnahmen. So können Sie QB-Fehler auf die selbe Weise verwalten, wie andere Ausnahmen auch. Sie müssen lediglich den Wert, den 'ERR' zurückliefert übegeben.
'mqbGetWhat' liefert dann einen Text, der sowhol den QB-Fehlercode, als auch eine Beschreibung beinhaltet, wie z.B. "53 File not found". Um die Arbeit weiter zu vereinfachen stehen die Funktionen 'mqbExtractQBErrorCode' und 'mqbExtractQBErrorText', die falls der Fehlertyp 'QB' ist den Fehlercode als integer bzw. eine Beschreibung des Fehlers zurückliefern. 'QB' ist der Name des Fehlertyps, den 'mqbMapError' verwendet, wenn es sich um einen QB-Fehler handelt. Eigene Ausnahmen werden nich verändert.
Sollten Sie 'mqbExtractQBErrorCode' verweden, wenn es sich um einen anderen Ausnahmetyp als 'QB' handelt, bekommen Sie '-1' zurück. Hier muss auf dieses altmodische Mittel zurückgegriffen werden, da wir uns bereits in der Ausnahmebehandlung befinen. 'mqbExtractQBErrorText' liefert einfach einen leeren Text. Eigentlich sollte Sie das nicht kümmern, da Sie die Funktionen nur verwenden, wenn der Fehlertyp 'QB' ist. Falls Sie dennoch eine '-1' erhalten wissen Sie, das Sie die Funktionen versehentlich im falschen Ast der 'SELECT CASE'-Structur vewenden, oder 'QB' falsch geschreiben haben.
Eine letzte Anmerkung: Sie sollten niemals 'QB' als Ausnahmetyp verwenden, da es für QB-Fehler vorgesehen ist.

Fehlerbehandlung:

mqbMapError ERR


SELECT CASE mqbGetExceptionType
   CASE "invalid argument"
      PRINT "Unzulässiges Argument"
      PRINT mqbGetWhat

   CASE "QB"
      PRINT "Standard QB Fehler"
      PRINT "Fehler-Code: "; mqbExtractQBErrorCode
      PRINT "Fehler-Text: "; mqbExtractQBErrorText

END SELECT

END

Erleichterung

Sehr häufig kommt es vor, dass man eine Bedingung überprüft, die eingehalten werden muss. Ansnsten wird eine Ausnahme geworfen. Um ihnen die Arbeit zu ersparen stellt MQB eine Funktion bereit, die ihnen diese Arbeit erleichtert.

DEFINT A-Z
SUB mqbEnforce (condition AS INTEGER, exceptionType AS STRING, message AS STRING)
   IF condition = 0 THEN
      mqbThrow exceptionType, message
   END IF
END SUB

Beispiel: Der Benutzer soll den Pfad eines Bitmaps angeben (ich meine eine *.bmp-Datei). Sie lesen diese Datei ein und zeigen Sie an. Die ersten beiden Zeichen sind immer "BM". Wenn das nicht so ist, handelt es sich auch nicht um ein Bitmap. Somit liegt ein Fehler vor.

mqbEnforce LOF(geoeffneteDatei), "Bitmap loading", "Datei ist leer"

DIM kennung AS STRING * 2

GET geoeffneteDatei, 0, kennung
mqbEnforce kennung = "BM", "Bitmap loading", "Datei ist kein Bitmap"

Assertion

Nun können Sie problemlos Benutzerfehler finden. Allerdings gibt es jemanden, der wesentlich mehr Fehler macht: Der Progammierer. Jeder Programmierer macht Fehler. Das wissen Sie nur zu gut. Desween besteht auch ein großer Teil des Programmierens aus Debugen.
Das Problem ist, dass manchmal Fehler entstehen, die sich erst später an einer anderen Stele bemerkbar machen oder aber dass ein Fehler an einer Stelle auftritt, von der man absolut sicher ist, das dort kein Fehler forkommen kann und man deshalb dort gar nicht sucht.
Ein sehr nützliches Mittel ist ein 'assert', das viele Sprachen bereitstellen. QB leider nicht. Also werden ich versuchen es so weit wie möglich nachzubauen.
Zunächst die Funktin selbst:

DEFINT A-Z
SUB mqbAssert (condition AS INTEGER, message AS STRING)
   IF condition = 0 THEN
      ShowErrorText message
      STOP
   END IF
END SUB

Sie erkennen sofort eine gewissen Ähnlichkeit zu 'mqbEnforce'. Lediglich der Typ des Fehlers ist festgelegt. Sie werden sich jetzt Fragen, was das soll. 'mqbAssert' ist nur zusammen mit einem Trick zusammen nützlich. 'Alt' 's' 'd' eine Kombination, die ich im Schlaf kann. Damit gelangen Sie in den 'Ändern'-Dialog. Wie hängt das denn jetzt mit einander zusammen?
Wenn die Sprache ein Feature nicht bitet, muss man eben erfinderisch werden. Hier können Sie 'mqbAssert' einfach durch 'REM mqbAssert' ersetzen und umgekehrt. So können Sie den Assertion-Mechanismus ein und ausschalten. So können Sie eine Debugversion und eine Releaseversion erstellen.
Da 'mqbAssert' auf diese Weise nur in der Testphase Zeit verbraucht, und die endgültige Version die Bedingung nicht mehr geprüft wird önnen Sie richtig verschwenderisch mit 'mqbAssert' umgehen. Überprüfen Sie alles, was beim Programmieren schiefgehen könnte. Als simples Beispiel nehme ich jetzt mal eine eigene Wurzelfunktion:

FUNCTION Wurzel# (radiant AS DOUBLE)
   mqbAssert radiant >=0, "negativer radiant"

   ... 'Formel zum berechnen der Wurzel (hier unwichtig)
END FUNCTION

Sollte 'Wurzel' jemals versehentlich mit einer negativen Zahl aufgerufen werden, landen Sie in der 'mqbAssert'-Sub und können dann indem Sie schrittweise durchs Programm laufen (F8) in die Funktion gelangen, die 'mqbAssert' ausgelöst hat. Indem Sie 'F4' drücken können Sie sich angucken, was 'ShowErrorText' geschrrieben hat.
Warum 'ShowErrorText' und nicht 'mqbShowErrorText'? Weil es sich um eine funktion handelt, die Sie selbst schreiben sollen. Wenn Sie eine SVGA-Bibliothek verwenden köntte ein normales 'PRINT' keine Fehler ausgeben. Vielleicht wollen Sie auch lieber eine Nachricht in einer Datei erhalten. Sie haben hier völlig freie Hand.
Mit 'mqbAssert' habe Sie so ein recht flexibles Werkzeug zur Hand, das Fehler frühtzeitig erkennt. 'mqbAssert' ist nicht dazu gedacht, Fehler des Benutzers zu erkennen. Wenn 'mqbAssert' losgeht wissen Sie, das ein logischer Fehler in ihrem Programm vorliegt.


vorheriges
Index
nächstes