Gérome
20.07.2008, 13:29
Wenn ein Entwickler Code schreibt, dann trifft er praktisch permanent Annahmen darüber, wie die jeweiligen Situationen im Programmablauf beschaffen sein könnten, wie Variablen bestückt sein könnten und wie jeweils mit ihnen umzugehen sei. Als Beispiel sei eine Funktion genannt, die drei Parameter erwartet. Der Entwickler „weiß“, dass der erste Parameter ein String ist, er „weiß“, dass der zweite Parameter eine Zahl ist und er „weiß“ auch, dass der dritte Parameter ein gefülltes Array ist. Bei streng typisierten Sprachen hilft ihm hierbei der Compiler, der die Typsicherheit einzelner Variablen sicherstellt. Bei Sprachen wie PHP hingegen, die keine strenge Typisierung kennen, ist das Risiko, dass so eine Annahme des Entwicklers nicht zutrifft, entschieden höher. Beispielsweise kann man einer Funktion Beliebiges als Parameter übergeben, angefangen von einer Ganzzahl, einem String, über ein Array bis hin zu einer getragenen Tennissocke. PHP wird sich nicht beschweren.
Es liegt also in der Natur der Sache, dass die einst vom Entwickler getroffenen Annahmen nicht zwingend in jeder Situation erfüllt werden müssen. Interessant wird dann die Frage, was im folgenden Programmablauf passiert- immerhin passen Programmcode und Variableninhalte nun nicht mehr zusammen. Fakt ist, dass der Entwickler das Ergebnis seines Programms nun nicht mehr garantieren kann und dieser Umstand stellt üblicherweise ein gravierendes Problem dar, welches sich in Form von hässlichen Fehlermeldungen, unerwarteten Verhalten der Software bis hin zum Datenverlust ausprägen kann.
Es ist also wünschenswert in Erfahrung zu bringen, ob die vom Entwickler einst getroffenen Annahmen zur Laufzeit der Software noch gültig sind. Und hier kommen Assertions ins Spiel. Mit ihnen können wir bestimmte Gegebenheiten prüfen und bestimmen, wie das System reagieren soll, falls mal eine Annahme nicht zutreffen sollte.
Vorab kurz eine Betrachtung der Frage, wo und wann wir Assertions verwenden. Niemals werden wir sie verwenden, um die Eingaben von Anwendern zu prüfen. Daten, die von Extern kommen (beispielsweise vom Anwender), sind grundsätzlich mit Skepsis zu behandeln und hier erwarten wir auch mögliche Fehler seitens des Anwenders. Hier, an vorderster Front sozusagen, prüfen wir defensiv und geben ggf. hilfreiche Fehlermeldungen an den Anwender, damit dieser Gelegenheit erhält, seinen Fehler zu korrigieren. Assertions stellt man meist in der zweiten Reihe auf. Nämlich dann, wenn die Eingaben des Anwenders geprüft sind und den eigenen Variablen zugeordnet sind. Dann erst fängt die interne Verarbeitungskette an, die zurecht und auch notwendigerweise gewisse Voraussetzungen erwartet. Und hier kommen Assertions ins Spiel – genau dann, wenn es um das korrekte und fehlerfreie Zusammenspiel der programminternen Funktionen geht („design by contract“). Als Entwickler überlegt man sich das Zusammenspiel der Programm-Module. Was geht an Daten rein, was geht an Daten wieder raus. Und nun gilt es, dieses Design sicherzustellen.
Gegeben sei eine simple Funktion, die zwei Zahlen erwartet und deren prozentuales Verhältnis zurückliefert.
PHP:
----------
Der Inhalt dieses Abschnitts ist nur für Lizenznehmer sichtbar, Sie werden derzeit jedoch nicht als Lizenzinhaber erkannt.<br />
<br />
Bitte öffnen Sie den <a href="http://members.vbulletin-germany.com/membersupport_priority.php">Kundenbereich</a>, tragen Sie Ihre E-Mail-Adresse ein, mit der Sie sich hier registriert haben und aktivieren Sie die Lizenzüberprüfung für http://www.vbulletin-germany.org.
----------
Solange sie tatsächlich zwei Zahlen erhält und $maximum zudem ungleich Null ist, arbeitet diese Funktion auch korrekt. Was aber, wenn nicht? Der Entwickler muss sich im Falle eines Fehlers unweigerlich die Frage stellen, warum hier falsche Parameter übergeben werden, denn schließlich ist so ein Funktionsaufruf üblicherweise in eine Reihe an Verarbeitungsschritten eingebettet und die Variablen kommen womöglich aus einer anderen Funktion, die unerwarteter weise etwas Falsches zurückgeliefert hat. Wenn der zuvor genannte Code bestehen bleiben soll, ließe er sich über Assertions wie folgt absichern:
PHP:
----------
Der Inhalt dieses Abschnitts ist nur für Lizenznehmer sichtbar, Sie werden derzeit jedoch nicht als Lizenzinhaber erkannt.<br />
<br />
Bitte öffnen Sie den <a href="http://members.vbulletin-germany.com/membersupport_priority.php">Kundenbereich</a>, tragen Sie Ihre E-Mail-Adresse ein, mit der Sie sich hier registriert haben und aktivieren Sie die Lizenzüberprüfung für http://www.vbulletin-germany.org.
----------
Diese Variante nutzt Assertions, um die vom Entwickler getroffenen Annahmen sicherzustellen. Wir sehen, dass die Assertions nicht nur als boolsche Bedingungen formuliert wurden, sondern auch in Hochkommata eingeschlossen wurden, die sich als String definieren. Im Ablaufverhalten ändert das nichts, doch auf diese Weise bekommen wie die Assertion als solche frei Haus geliefert, wenn wir einen eigenen Assertion-Handler installiere (siehe weiter unten).
Warum nicht das Gleiche mit if()-Konstrukten erreichen ? Sicherlich, Ähnliches ließe sich auch mittels if() erreichen, doch dann stünde man allerdings vor dem ELSE-Problem. Was wäre ein geeigneter Rückgabewert? Ein IF würde man hauptsächlich dann einsetzen, wenn das System den Fehler transparent korrigieren kann und die nicht eingehaltene Bedingung bedeutungslos ist. Assertions hingegen setzt man primär dann ein, wenn der Fehler nicht automatisch korrigiert werden kann und es somit Anzeichen dafür gibt, dass die Software fehlerhaft ist und der Entwickler mit diesem Programmverlauf offenbar nicht gerechnet hat. Ein weiteres Parade-Beispiel könnte auch der „default“-Zweig in einem switch()-Statement sein. Wenn das Programm dort hineinläuft, kann es sein, dass der Entwickler einen Fall nicht bedacht hat.
Wir haben gesehen, dass Assertions direkt in den Programmablauf integriert werden. Das hat einen weiteren, sehr großen Vorteil: Fehler werden sehr schnell nach ihrem Entstehen entdeckt. Denn wenn das nicht der Fall ist, kann es passieren, dass Fehler und daraus resultierende fehlerhafte Werte bis zum Programmende durchgeschleift werden und der Anwender nur „Käse“ auf dem Bildschirm sieht. Die Suche nach der Ursache wird dann erheblich schwieriger, da man tatsächlich vom Ende anfängt, rückwärts die Verarbeitungsschritte zu rekonstruieren und hofft, so den Fehler zu entdecken. Eine Assertion hingegen liefert uns auf Wunsch den Namen des Scripts sowie die Zeilennummer, in der die Assertion fehlgeschlagen ist. Wir haben hier eine sehr enge Verbindung zwischen dem Entdecken des Fehlers und der ihn verursachenden Codestelle.
Assertions können somit in erheblichem Maße zur Qualität des Programmcodes beitragen.
Werfen wir einen näheren Blick auf die Assertions in PHP. Die genaue Behandlung von Assertions steuert man in PHP über die Funktion „assert_options() (http://de.php.net/manual/de/function.assert-options.php)“. Über den Steuercode „ASSERT_ACTIVE“ können wir festlegen, ob Assertions überhaupt verarbeitet werden sollen. Es gibt Meinungen, Assertions aus Gründen der Performance in Produktiv-Systemen nicht zu verwenden, ich halte dies im Allgemeinen für eine falsche Entscheidung. Wenn tatsächlich der Programmlauf gestört ist und Variablen von unerwarteter Beschaffenheit von einer Funktion zur nächsten Weitergereicht werden, dann will ich das wissen. Unbedingt. Eine weiterer wichtiger Steuercode ist „ASSERT_CALLBACK“. Hier können wir eine eigene Funktion angeben, die aufgerufen werden soll, falls eine Assertion fehlschlägt. Über die Angabe von „assert_options(ASSERT_CALLBACK, 'my_assertion_handler');“ teilen wir PHP mit, dass unser eigener Handler aufgerufen werden soll. Unsere Funktion bekommt dann von PHP drei Parameter geliefert: „function my_assertion_handler( $file, $line, $code )“. $file und $line sollten selbsterklärend sein, in $code wird die Assertion als solche geliefert (wenn sie als String definiert wurde).
Hier könnten wir Beliebiges tun, ich persönlich tendiere zu einem Auswerfen des Callstacks, damit es leichter wird, dem Fehler auf die Schliche zu kommen sowie einem Abbruch des Programms. Ich setze Assertions genau dort ein, wo bestimmte Gegebenheiten vorherrschen müssen, und wenn dies nicht der Fall ist, dann läuft das Programm gerade gewaltig aus dem Ruder und ein Abbruch verhindert wenigstens weitere Schäden.
Hier ein Screenshot der Abbruch-Meldung:
3710
Wir sehen, dass im Script „class_dp-search.php“ in Zeile 561 die Typ-Prüfung der Variablen $keywords fehlgeschlagen ist. Darunter sehen wir den sog. „Callstack“, also die Liste der Funktionsaufrufe, die zu diesem Programmzustand geführt hat. Angefangen von unten hat das Script „vbseo.php“ in Zeile 1470 das Script „dp_search.php“ eingebunden. Dieses wiederum hat in seiner Zeile 59 das Objekt $dp_search erzeugt und diesem als Parameter ein Objekt der Klasse vB_Registry übergeben (i.A. $vbulletin). In Zeile 64 des Scripts „class_dp-search.php“ wird die Methode „fetch_parameters“ aufgerufen, die ihrerseits in Zeile 100 die Methode „set_keywords“ aufruft und dabei den String „windows vista“ als Parameter übergibt. Diese wiederum ruft in Zeile 340 die Methode „validate_keywords“ und übergibt ebenfalls einen String. Und in dieser Methode knallt es bei der Prüfung auf eine Ganzzahl (is_int()“. Damit liegt der Fehler quasi auf der Hand – mutmaßlich ist die bemängelte Variable $keywords der als Parameter übergebene String.
Assertions in Verbindung mit dem Callstack sind eine ungemein nützliche und wertvolle Hilfe, um Fehler im Code zu entdecken, da ersichtlich wird, ab wann beispielsweise fehlerhafte Parameter durch das System geistern.
Ich habe mal ein Produkt angefügt, welches einen Assertion-Handler installiert und alle von PHP erzeugten Assertions auf selbigen umbiegt. Dieses Produkt ist nur eine Demonstration des Prinzips – man wird sich vermutlich lieber selbst einen geeigneten Handler schreiben.
Es liegt also in der Natur der Sache, dass die einst vom Entwickler getroffenen Annahmen nicht zwingend in jeder Situation erfüllt werden müssen. Interessant wird dann die Frage, was im folgenden Programmablauf passiert- immerhin passen Programmcode und Variableninhalte nun nicht mehr zusammen. Fakt ist, dass der Entwickler das Ergebnis seines Programms nun nicht mehr garantieren kann und dieser Umstand stellt üblicherweise ein gravierendes Problem dar, welches sich in Form von hässlichen Fehlermeldungen, unerwarteten Verhalten der Software bis hin zum Datenverlust ausprägen kann.
Es ist also wünschenswert in Erfahrung zu bringen, ob die vom Entwickler einst getroffenen Annahmen zur Laufzeit der Software noch gültig sind. Und hier kommen Assertions ins Spiel. Mit ihnen können wir bestimmte Gegebenheiten prüfen und bestimmen, wie das System reagieren soll, falls mal eine Annahme nicht zutreffen sollte.
Vorab kurz eine Betrachtung der Frage, wo und wann wir Assertions verwenden. Niemals werden wir sie verwenden, um die Eingaben von Anwendern zu prüfen. Daten, die von Extern kommen (beispielsweise vom Anwender), sind grundsätzlich mit Skepsis zu behandeln und hier erwarten wir auch mögliche Fehler seitens des Anwenders. Hier, an vorderster Front sozusagen, prüfen wir defensiv und geben ggf. hilfreiche Fehlermeldungen an den Anwender, damit dieser Gelegenheit erhält, seinen Fehler zu korrigieren. Assertions stellt man meist in der zweiten Reihe auf. Nämlich dann, wenn die Eingaben des Anwenders geprüft sind und den eigenen Variablen zugeordnet sind. Dann erst fängt die interne Verarbeitungskette an, die zurecht und auch notwendigerweise gewisse Voraussetzungen erwartet. Und hier kommen Assertions ins Spiel – genau dann, wenn es um das korrekte und fehlerfreie Zusammenspiel der programminternen Funktionen geht („design by contract“). Als Entwickler überlegt man sich das Zusammenspiel der Programm-Module. Was geht an Daten rein, was geht an Daten wieder raus. Und nun gilt es, dieses Design sicherzustellen.
Gegeben sei eine simple Funktion, die zwei Zahlen erwartet und deren prozentuales Verhältnis zurückliefert.
PHP:
----------
Der Inhalt dieses Abschnitts ist nur für Lizenznehmer sichtbar, Sie werden derzeit jedoch nicht als Lizenzinhaber erkannt.<br />
<br />
Bitte öffnen Sie den <a href="http://members.vbulletin-germany.com/membersupport_priority.php">Kundenbereich</a>, tragen Sie Ihre E-Mail-Adresse ein, mit der Sie sich hier registriert haben und aktivieren Sie die Lizenzüberprüfung für http://www.vbulletin-germany.org.
----------
Solange sie tatsächlich zwei Zahlen erhält und $maximum zudem ungleich Null ist, arbeitet diese Funktion auch korrekt. Was aber, wenn nicht? Der Entwickler muss sich im Falle eines Fehlers unweigerlich die Frage stellen, warum hier falsche Parameter übergeben werden, denn schließlich ist so ein Funktionsaufruf üblicherweise in eine Reihe an Verarbeitungsschritten eingebettet und die Variablen kommen womöglich aus einer anderen Funktion, die unerwarteter weise etwas Falsches zurückgeliefert hat. Wenn der zuvor genannte Code bestehen bleiben soll, ließe er sich über Assertions wie folgt absichern:
PHP:
----------
Der Inhalt dieses Abschnitts ist nur für Lizenznehmer sichtbar, Sie werden derzeit jedoch nicht als Lizenzinhaber erkannt.<br />
<br />
Bitte öffnen Sie den <a href="http://members.vbulletin-germany.com/membersupport_priority.php">Kundenbereich</a>, tragen Sie Ihre E-Mail-Adresse ein, mit der Sie sich hier registriert haben und aktivieren Sie die Lizenzüberprüfung für http://www.vbulletin-germany.org.
----------
Diese Variante nutzt Assertions, um die vom Entwickler getroffenen Annahmen sicherzustellen. Wir sehen, dass die Assertions nicht nur als boolsche Bedingungen formuliert wurden, sondern auch in Hochkommata eingeschlossen wurden, die sich als String definieren. Im Ablaufverhalten ändert das nichts, doch auf diese Weise bekommen wie die Assertion als solche frei Haus geliefert, wenn wir einen eigenen Assertion-Handler installiere (siehe weiter unten).
Warum nicht das Gleiche mit if()-Konstrukten erreichen ? Sicherlich, Ähnliches ließe sich auch mittels if() erreichen, doch dann stünde man allerdings vor dem ELSE-Problem. Was wäre ein geeigneter Rückgabewert? Ein IF würde man hauptsächlich dann einsetzen, wenn das System den Fehler transparent korrigieren kann und die nicht eingehaltene Bedingung bedeutungslos ist. Assertions hingegen setzt man primär dann ein, wenn der Fehler nicht automatisch korrigiert werden kann und es somit Anzeichen dafür gibt, dass die Software fehlerhaft ist und der Entwickler mit diesem Programmverlauf offenbar nicht gerechnet hat. Ein weiteres Parade-Beispiel könnte auch der „default“-Zweig in einem switch()-Statement sein. Wenn das Programm dort hineinläuft, kann es sein, dass der Entwickler einen Fall nicht bedacht hat.
Wir haben gesehen, dass Assertions direkt in den Programmablauf integriert werden. Das hat einen weiteren, sehr großen Vorteil: Fehler werden sehr schnell nach ihrem Entstehen entdeckt. Denn wenn das nicht der Fall ist, kann es passieren, dass Fehler und daraus resultierende fehlerhafte Werte bis zum Programmende durchgeschleift werden und der Anwender nur „Käse“ auf dem Bildschirm sieht. Die Suche nach der Ursache wird dann erheblich schwieriger, da man tatsächlich vom Ende anfängt, rückwärts die Verarbeitungsschritte zu rekonstruieren und hofft, so den Fehler zu entdecken. Eine Assertion hingegen liefert uns auf Wunsch den Namen des Scripts sowie die Zeilennummer, in der die Assertion fehlgeschlagen ist. Wir haben hier eine sehr enge Verbindung zwischen dem Entdecken des Fehlers und der ihn verursachenden Codestelle.
Assertions können somit in erheblichem Maße zur Qualität des Programmcodes beitragen.
Werfen wir einen näheren Blick auf die Assertions in PHP. Die genaue Behandlung von Assertions steuert man in PHP über die Funktion „assert_options() (http://de.php.net/manual/de/function.assert-options.php)“. Über den Steuercode „ASSERT_ACTIVE“ können wir festlegen, ob Assertions überhaupt verarbeitet werden sollen. Es gibt Meinungen, Assertions aus Gründen der Performance in Produktiv-Systemen nicht zu verwenden, ich halte dies im Allgemeinen für eine falsche Entscheidung. Wenn tatsächlich der Programmlauf gestört ist und Variablen von unerwarteter Beschaffenheit von einer Funktion zur nächsten Weitergereicht werden, dann will ich das wissen. Unbedingt. Eine weiterer wichtiger Steuercode ist „ASSERT_CALLBACK“. Hier können wir eine eigene Funktion angeben, die aufgerufen werden soll, falls eine Assertion fehlschlägt. Über die Angabe von „assert_options(ASSERT_CALLBACK, 'my_assertion_handler');“ teilen wir PHP mit, dass unser eigener Handler aufgerufen werden soll. Unsere Funktion bekommt dann von PHP drei Parameter geliefert: „function my_assertion_handler( $file, $line, $code )“. $file und $line sollten selbsterklärend sein, in $code wird die Assertion als solche geliefert (wenn sie als String definiert wurde).
Hier könnten wir Beliebiges tun, ich persönlich tendiere zu einem Auswerfen des Callstacks, damit es leichter wird, dem Fehler auf die Schliche zu kommen sowie einem Abbruch des Programms. Ich setze Assertions genau dort ein, wo bestimmte Gegebenheiten vorherrschen müssen, und wenn dies nicht der Fall ist, dann läuft das Programm gerade gewaltig aus dem Ruder und ein Abbruch verhindert wenigstens weitere Schäden.
Hier ein Screenshot der Abbruch-Meldung:
3710
Wir sehen, dass im Script „class_dp-search.php“ in Zeile 561 die Typ-Prüfung der Variablen $keywords fehlgeschlagen ist. Darunter sehen wir den sog. „Callstack“, also die Liste der Funktionsaufrufe, die zu diesem Programmzustand geführt hat. Angefangen von unten hat das Script „vbseo.php“ in Zeile 1470 das Script „dp_search.php“ eingebunden. Dieses wiederum hat in seiner Zeile 59 das Objekt $dp_search erzeugt und diesem als Parameter ein Objekt der Klasse vB_Registry übergeben (i.A. $vbulletin). In Zeile 64 des Scripts „class_dp-search.php“ wird die Methode „fetch_parameters“ aufgerufen, die ihrerseits in Zeile 100 die Methode „set_keywords“ aufruft und dabei den String „windows vista“ als Parameter übergibt. Diese wiederum ruft in Zeile 340 die Methode „validate_keywords“ und übergibt ebenfalls einen String. Und in dieser Methode knallt es bei der Prüfung auf eine Ganzzahl (is_int()“. Damit liegt der Fehler quasi auf der Hand – mutmaßlich ist die bemängelte Variable $keywords der als Parameter übergebene String.
Assertions in Verbindung mit dem Callstack sind eine ungemein nützliche und wertvolle Hilfe, um Fehler im Code zu entdecken, da ersichtlich wird, ab wann beispielsweise fehlerhafte Parameter durch das System geistern.
Ich habe mal ein Produkt angefügt, welches einen Assertion-Handler installiert und alle von PHP erzeugten Assertions auf selbigen umbiegt. Dieses Produkt ist nur eine Demonstration des Prinzips – man wird sich vermutlich lieber selbst einen geeigneten Handler schreiben.