|
Druckversion anzeigen:
|
 |
Wussten Sie schon... ? |
| |
| |
... dass Visual Objects die Auswertung logischer Ausdrücke optimiert?
Ab Version 2.6 aber manchmal (leider?) eben doch nicht!
|
|
| |
Alles, was Sie über logische Ausdrücke und wie diese ausgewertet werden wissen sollten. (Bei einem scheinbar so einfachen Thema, ist man schnell geneigt, zu denken, man weiss eigentlich schon alles dazu.Trotzdem sind wir sicher, dass Sie in diesem Artikel ein paar Neuigkeiten finden werden)
|
Worin besteht eigentlich die Optimierung bei der Auswertung logischer Ausdrücke ?
Die Optimierung (oft auch "Boolean Shortcutting" oder "Short Circuit Evaluation" genannt) basiert auf den in der folgenden Tabelle zusammengefassten Zusammenhängen:
| a | b | a .AND. b | a .OR. b |
| F | F | F | F |
| F | T | F | T |
| T | F | F | T |
| T | T | T | T |
Wie man sieht, ist im Fall, dass a TRUE ist, der Ausdruck a .OR. b immer TRUE. Wenn a FALSE ist, ist der Ausdruck a .AND. b immer FALSE. Die Optimierung bei der Auswertung BOOLE'scher Ausdrücke besteht darin, dass in diesen beiden Fällen b gar nicht mehr ausgewertet wird.
|
|
Die eindeutige Aussage im VO 2.6 Programmer's Guide (S.357) lautet: CA-Visual Objects uses a shortcut when evaluating the .AND. and .OR. operators. For .AND. the second operand is not evaluated if the first operand evaluates to FALSE. For .OR. the second operand is not evaluated if the first operand evaluates to TRUE
|
Das bedeutet, wenn b durch eine Funktion oder Methode ersetzt wird, die einen logischen Rückgabewert hat, wird diese gar nicht mehr durchlaufen. Während also in der guten, alten Schul-Algebra ein logisches UND bzw. ODER kommutativ ist (für NICHT-Mathematiker: die Operanden können beliebig vertauscht werden), kommt es in diesen zwei Fällen sehr wohl auf die Reihenfolge der Operanden a und b an! Und hätte VO wie z.B. Pascal einen Compiler-Schalter ({$B+}), um das Shortcutting ein bzw. auszuschalten, dann könnte es beim Austausch von AEFs zu erheblich unterschiedlichen Ergebnissen führen, wenn der Empfänger andere Compiler-Einstellungen hat als der Autor des Quelltextes verwendet hat.
Da VO das Shortcutting nutzt, ist es also völlig legitim folgende Zeile zu schreiben:
|
IF ok() .OR. MyErrorHandler()
|
Gibt ok() TRUE zurück, wird MyErrorHandler() NICHT abgearbeitet. Gibt ok() FALSE zurück, wird MyErrorHandler() abgearbeitet. Man überlege nur mal kurz, wie wenig Sinn es macht, hier die Operanden ok() und MyErrorHandler() zu vertauschen!
Achtung: Das in VO (wie wir unten sehen werden: fast) immer wirksame Shortcutting, kann auch ein Problem darstellen. In Debugsitzungen muss man genau aufpassen, dass auch mal der Fall eintritt, dass der zweite Operand NICHT weg optimiert wird, also die obige Funktion MyErrorhandler() auch tatsächlich mal durchlaufen wird - und nicht beim Anwender erst das erste Mal.
|
Vorsicht, Falle!
Seit VO Version 2.6 hat man nun aber auch die Möglichkeit, das Shortcutting zu umgehen! Ob das jedoch vom Entwickler immer so gewollt ist, bleibt fraglich (Auch ob es die VO-Entwickler selbst so gewollt haben, ist nicht klar, denn dies ist ein undokumentiertes "Feature"). Man tut jedenfalls gut daran, seinen eigenen Quelltext nach solchen Fallen, die im Folgenden beschrieben werden, zu durchsuchen!
|
FUNCTION Start()
IF IsItTrue(One() .AND. Two())
? "IF TRUE"
ELSE
? "IF FALSE"
ENDIF
WAIT
FUNCTION One() AS LOGIC PASCAL
? 'one'
RETURN FALSE
FUNCTION Two() AS LOGIC PASCAL
? 'two'
RETURN TRUE
FUNCTION IsItTrue(lOk AS LOGIC) AS LOGIC PASCAL
? lOk
RETURN lOk
|
|
Nichts Besonderes! Meint man! Schließlich wird zuerst eine Funktion One() aufgerufen, die FALSE zurückgibt. Die mit .AND. verknüpfte Funktion Two() sollte also nach obiger Tabelle nicht abgearbeitet werden. Die Funktion IsItTrue() wird natürlich aufgerufen und gibt den Eingangsparameter wieder zurück. Da dieser FALSE ist, wird in den ELSE-Zweig gesprungen. Fast alles richtig, wie die folgende Bildschirmausgabe zeigt:
|
|
|
Überraschenderweise wird die Funktion Two() doch abgearbeitet! Hätten wir die Funktion IsItTrue() gar nicht verwendet und nur
|
IF One() .AND. Two()
|
|
geschrieben oder auch
|
LOCAL lOk AS LOGIC
lOk := One() .AND. Two()
IF IsItTrue(lOk)
|
dann wäre bei jeder der beiden Änderungen die Funktion Two() NICHT abgearbeitet worden! Kleine Unterschiede, aber große Wirkung! Worin besteht nun der Unterschied?
Hier die Antwort (, die zu finden, einige VO-Anwender jedoch einige Stunden gekostet hat!): In der zweiten Abwandlung verwenden wir eine eigene Variable (lOK), in der wir den Rückgabewert des logischen Ausdrucks speichern. Im Originalbeispiel dagegen wird der Ausdruck direkt (oder: Inline) als Parameter übergeben. Logische Ausdrücke, die direkt in die IF-Anweisung geschrieben werden und NICHT als Funktionsparameter verwendet werden, werden mit Shortcutting abgearbeitet wie die erste Abwandlung zeigt.
Achtung: Werden logische Ausdrücke als Funktionsparameter inline übergeben, wird auf diese in VO 2.6 KEIN Shortcutting angewendet!
Übrigens wurden in älteren VO-Versionen auch als Parameter übergebene logische Ausdrücke optimiert ausgewertet. Passen Sie also auf, wenn Sie Quelltext von VO 2.0 oder VO 2.5 in VO 2.6 recompilieren.
Weitere Beispiele, die kritisch sind:
|
IF Test(i > 0 .and. a[i]) // Bound Error bei ArrayGet
|
Hier erhalten Sie einen Array-Fehler, wenn i mit 0 belegt ist. Der Grund ist wieder der gleiche: Der logische Ausdruck wird inline (direkt) als Funktionsparameter übergeben und nicht erst in einer Variablen gespeichert. Deshalb wird im Fall, dass i>0 FALSE ist (z.B. wenn i = 0) trotzdem a[i] aufgerufen - also KEIN Shortcutting.
Natürlich lauern diese Gefahren nicht nur in IF-Anweisungen oder in DO CASE-Statements, sondern z.B. auch in Codeblocks werden häufig Funktionen mit logischen Ausdrücken als Parametern verwendet.
|
b := {||IsItTrue(One() .AND. Two())}
? Eval(b)
|
|
|
Zum Schluss noch ein kleines Rätsel: Wieviele Sätze der von oServer kontrollierten Tabelle durchläuft eine Schleife, die mit folgender Zeile beginnt, wenn die Funktion Abbruch() immer FALSE zurückgibt und die Methode Pruefen() ihren Eingangsparameter !oServer:EoF .OR. Abbruch() unverändert zurückgibt?
|
DO WHILE SELF:Pruefen(!oServer:EoF .OR. Abbruch())
|
Und wieviele jetzt?:
|
DO WHILE !oServer:EoF .OR. Abbruch()
|
| Antworten dazu mit dem nächsten Update im November |
| |
|
Diese Tipps stammen von Dieter Crispien |
| |
Schicken Sie uns Ihren eigenen Tipp des Monats per Email |
Druckversion anzeigen:
|